The most common way to use the Machine Query Interface is to find a
library that wraps and exposes it as a native part of another
programming language such as the Python swiplserver
library
(section 1.1). This section describes
how to build one if there isn't yet a library for your language. To do
this, you'll need to familiarize yourself with the MQI protocol as
described in the mqi_start/1
documentation. However, to give an idea of the scope of work required,
below is a typical interaction done (invisibly to the user) in the
implementation of any programming language library:
swipl mqi --write_connection_values=true
.
To work, the swipl
Prolog executable will need to be on the
path or the path needs to be specified in the command. This launches SWI
Prolog, starts the MQI, and writes the chosen port and password to
STDOUT. This way of launching invokes the mqi_start/0
predicate that turns off the int
(i.e. Interrupt/SIGINT)
signal to Prolog. This is because some languages (such as Python) use
that signal during debugging and it would be otherwise passed to the
client Prolog process and switch it into the debugger. See the mqi_start/0
predicate for more information on other command line options.\n
’.$ swipl mqi --write_connection_values=true 54501 185786669688147744015809740744888120144
Now the server is started. To create a connection:
<stringByteLength>.\n<stringBytes>.\n
where stringByteLength
includes the .\n
from
the string. For example: 7.\nhello.\n
More information on
the message format (section 1.6.1)
is below.true([[threads(Comm_Thread_ID, Goal_Thread_ID), version(Major, Minor)]])
(which will be in JSON form) indicating successful creation of the
connection. Comm_Thread_ID and Goal_Thread_ID are
the internal Prolog IDs of the two threads that are used for the
connection. They are sent solely for monitoring and debugging purposes. version
was introduced in version 1.0 of the protocol to allow for detecting the
protocol version and should be checked to ensure the protocol version is
supported by your library. See mqi_version/2
for more information and a version history.
We can try all of this using the Unix tool nc
(netcat)
(also available for Windows) to interactively connect to the MQI. In nc
hitting enter
sends \n
which is what the
message format requires. The server responses are show indented inline.
We'll use the port and password that were sent to STDOUT above:
$ nc 127.0.0.1 54501 41. 185786669688147744015809740744888120144. 173. { "args": [ [ [ { "args": ["mqi1_conn2_comm", "mqi1_conn2_goal" ], "functor":"threads" }, { "args": ["1", "0" ], "functor":"version" } ] ] ], "functor":"true" }
Now the connection is established. To run queries and shutdown:
run(atom(a), -1)
to run the synchronous
query atom(a)
with no timeout and wait for the response
message. It will be true([[]])
(in JSON form).close
,
waiting for the response message of true([[]])
(in JSON
form), and then closing the socket using the socket API of the language.
If the socket is closed (or fails) before the close
message
is sent, the default behavior of the MQI is to exit the SWI Prolog
process to avoid leaving the process around. This is to support
scenarios where the user is running and halting their language debugger
without cleanly exiting.quit
message and waiting for the response
message of true([[]])
(in JSON form). This will cause an
orderly shutdown and exit of the process.
Continuing with the nc
session (the quit
message isn't shown since the close
message closes the
connection):
18. run(atom(a), -1). 39. {"args": [ [ [] ] ], "functor":"true"} 7. close. 39. {"args": [ [ [] ] ], "functor":"true"}
Note that Unix Domain Sockets can be used instead of a TCP/IP port. How to do this is described with mqi_start/1.
Here's the same example running in the R language. Note that this is not an example of how to use the MQI from R, it just shows the first code a developer would write as they begin to build a nice library to connect R to Prolog using the MQI:
# Server run with: swipl mqi.pl --port=40001 --password=123 # R Source print("# Establish connection") sck = make.socket('localhost', 40001) print("# Send password") write.socket(sck, '5.\n') # message length write.socket(sck, '123.\n') # password print(read.socket(sck)) print("# Run query") query = 'run(member(X, [1, 2, 3]), -1).\n' write.socket(sck, paste(nchar(query), '.\n', sep='')) # message length write.socket(sck, query) # query print(read.socket(sck)) print("# Close session") close.socket(sck)
And here's the output:
[1] "# Establish connection" [1] "# Send password" [1] "172.\n{\n "args": [\n [\n [\n\t{\n\t "args": ["mqi1_conn1_comm", "mqi1_conn1_goal" ],\n\t "functor":"threads"\n\t}\n ]\n ]\n ],\n "functor":"true"\n}" [1] "# Run query" [1] "188.\n{\n "args": [\n [\n [ {"args": ["X", 1 ], "functor":"="} ],\n [ {"args": ["X", 2 ], "functor":"="} ],\n [ {"args": ["X", 3 ], "functor":"="} ]\n ]\n ],\n "functor":"true"\n}" [1] "# Close session"
Other notes about creating a new library to communicate with the MQI: