ciao-lang / ciao

Ciao is a modern Prolog implementation that builds up from a logic-based simple kernel designed to be portable, extensible, and modular.
https://ciao-lang.org
GNU Lesser General Public License v3.0
268 stars 20 forks source link

Calling Ciao Prolog from Elixir #96

Open aherranz opened 5 days ago

aherranz commented 5 days ago

Dear all we are planning to implement an Elixir library to interface Ciao Prolog programs. We need to load a Prolog/CLP program and (lazily) get every solution (including variable substitutions and/or constraints on them).

aherranz commented 4 days ago

I am sandboxing by starting an engine and using stdin/stdout. Maybe this is enough for an initial version of our library. I am curious about the way the promt "?- " since I cannot read it from the stdout of the engine. Is the prompt emited through the stdout, is it directly emited in the tty, do you have other approach?

jfmc commented 4 days ago

Hi @aherranz!

Yes, starting a new Ciao process and interacting via stdin/stdout is a good approach for simple query/answer solutions. Some example from bash:

$ query='append([1,2],[3,4],_X),writeq(_X).'; answer=$(echo "$query" | ciaosh -q); printf "query:%s answer:%s\n" "$query" "$answer"
query:append([1,2],[3,4],_X),writeq(_X). answer:[1,2,3,4]

Obviously this is really weak (see that we use _X so that the toplevel do not show the binding and shows the secondary prompt). A better approach is creating a small Ciao program that acts as a server, receiving messages (e.g., queries) and reacting (e.g., sending the solutions as an output). With some minor work, it is possible to compose the input and/or send the output in JSON (using library(json)) so facilitate the communication with other languages.

More or less, this is the approach that was taken in the Ciao Java interface, but using a more involved communication protocol. We did an experimental port of the same approach for a Python interface and it works fine, if serializing data for each query is not an issue.

Another approach would be a more tight interaction using the C interface (useful if you can preserve references to previously created terms and do a lot of queries). I have no experience with Erlang/Elixir but I guess that it'd possible to interface Elixir->C->Prolog in this way. I'd take this approach only when performance becomes a problem.

Please let us know if the server approach (using stdin/stdout) fits your application and we can send you some pointers to similar code in other interfaces.

aherranz commented 4 days ago

Thank you!

As a prototype we will follow the most simple approach for us: we will start a Ciao engine from Elixir and we will feed it with queries and we will parse the answers directly reading and parsing the stdout of the engine. I'll keep you informed.

aherranz commented 4 days ago

Let me show you our first session, I am using the low level functions that will not be public in the first protype. It seems that the stdin/out approach is promising:

iex(1)> alias Prolixir.Engine
Prolixir.Engine
iex(2)> engine = Engine.start "ciao", nil
%Porcelain.Process{pid: #PID<0.189.0>, out: {:send, #PID<0.188.0>}, err: :out}
iex(3)> Engine.read engine
{:ok, :out, "Ciao 1.23.0 [LINUXx86_64]\n"}
iex(4)> Engine.read engine
{:error, :empty}
iex(5)> Engine.write engine, "append(A,B,[1,2,3])"
{:input, "append(A,B,[1,2,3])"}
iex(6)> Engine.read engine
{:error, :empty}
iex(7)> Engine.write engine, ".\n"
{:input, ".\n"}
iex(8)> Engine.read engine
{:ok, :out, "\nA = [],\nB = [1,2,3] ? "}
iex(9)> Engine.write engine, ";\n"
{:input, ";\n"}
iex(10)> Engine.read engine
{:ok, :out, "\nA = [1],\nB = [2,3] ? "}
iex(11)> Engine.read engine
{:error, :empty}
iex(12)> Engine.write engine, ";\n"
{:input, ";\n"}
iex(13)> Engine.read engine
{:ok, :out, "\nA = [1,2],\nB = "}
iex(14)> Engine.write engine, ";\n"
{:input, ";\n"}
iex(15)> Engine.read engine
{:ok, :out, "[3] ? "}
iex(16)> Engine.write engine, ";\n"
{:input, ";\n"}
iex(17)> Engine.read engine
{:ok, :out, "\nA = [1,2,3],\nB = [] ? "}
iex(18)> Engine.read engine
{:ok, :out, "\nno"}
iex(19)> Engine.read engine
{:error, :empty}
iex(20)> Engine.write engine, "halt.\n"
{:input, "halt.\n"}
iex(21)> Engine.read engine
{:ok, :out, "\n"}
iex(22)> Engine.read engine
{:error, %Porcelain.Result{status: 0, out: {:send, #PID<0.188.0>}, err: :out}}
iex(23)>