Swiplserver: store the result in a string Prolog can handle?

I search some way to translate the output of swiplserver queries into some result string SWI-Prolog can handle with

E.g. the query:

Example 1

from swiplserver import *

with PrologMQI() as mqi:
    with mqi.create_thread() as prolog_thread:
        result = prolog_thread.query("A = point{x:1, y:2}.put([x=3,z=0]).")
        print(result)

results in:
Python output

[{‘A’: {‘args’: [{‘x’: 1, ‘y’: 2}, {‘args’: [[{‘args’: [‘x’, 3], ‘functor’: ‘=’}, {‘args’: [‘z’, 0], ‘functor’: ‘=’}]], ‘functor’: ‘put’}], ‘functor’: ‘.’}}]

Output in SWI-Prolog

A = point{x:3, y:2, z:0}.

If I now want to change the result (maybe with A=point{x:3, y:2, z:0}.put([z=4]).), my first task is, to translate the result in some string prolog is able to accept as parameter (in an easy way).

The easiest way to do this is maybe to store the result string in some .txt-file and read that out with python.

This can be done for an arbitrary predicate in SWI-Prolog by

Output in SWI-Prolog

?- protocol('text.txt').
true.

?- A = point{x:1, y:2}.put([x=3,z=0]).
A = point{x:3, y:2, z:0}.

?- noprotocol.
true.

But for some reason the simulation in swiplserver doesn’t generate some file, means if I try to simulate it with code:

Example 2

from swiplserver import *

with PrologMQI() as mqi:
    with mqi.create_thread() as prolog_thread:
        prolog_thread.query("protocol('text.txt').")
        result = prolog_thread.query("A = point{x:1, y:2}.put([x=3,z=0]).")
        prolog_thread.query("noprotocol.")
        print(result)

The server generates some file “test.txt” but doesn’t store the result of my query inside of it. In summary my two questions are:

  1. Is their some simple way to manage the outputs swiplserver gives me for further inputs in prolog predicates (see result of example 1)?
  2. What is the reason the result of my query in example 2 is not stored in the file “text.txt”?

Here you seem to want the Prolog textual representation of an answer. You can use e.g. term_string/2,3. In the toplevel REPL running a query proves the query and prints the result.

protocol/1 logs the output that is sent to the user_output and user_error streams. Running a query doesn’t send anything there unless the query uses write/format/…

Thank your for your reply, how exactly can I call term_string/2, if my prolog queryin swiplserver is

A = point{x:1, y:2}.put([x=3,z=0]).

means

from swiplserver import *

with PrologMQI() as mqi:
    with mqi.create_thread() as prolog_thread:
        result = prolog_thread.query("A = point{x:1, y:2}.put([x=3,z=0]).")
        print(result)

and my result should be
`

A = point{x:3, y:2, z:0}.
`

I just tried as left and right hand parameter both the query itself and the result string but it is not the desired result.

At least part of the question here is why:

A = point{x:1, y:2}.put([x=3,z=0]).

returns this from the top level:

A = point{x:3, y:2, z:0}.

but this from the MQI:

[{‘A’: {‘args’: [{‘x’: 1, ‘y’: 2}, {‘args’: [[{‘args’: [‘x’, 3], ‘functor’: ‘=’}, {‘args’: [‘z’, 0], ‘functor’: ‘=’}]], ‘functor’: ‘put’}], ‘functor’: ‘.’}}]

which is the JSON form of:

A = point{x:1, y:2}.put([x=3,z=0])

i.e. the top level seems to be evaluating the right side before unifying with A but the MQI is not.

@jan do you have insight into what the top level is doing here that is different than a basic query like A = term(a, b, c)?

Absolutely not sure this will help, but did you noticed that MQI loses ‘point’ (i.e. the dict tag) ?
Could be an indicator of a glitch in the interface ?

Most likely that you need to call expand_goal/2 on the goal you receive before calling it.

Well spotted. It isn’t the cause of this problem though. The dict put operation does not depend on the tag.

1 Like

Yes, this fixes the first problem so that the MQI now returns [{'A': {'x': 3, 'y': 2, 'z': 0}}] from result = prolog_thread.query("A = point{x:1, y:2}.put([x=3,z=0])."). Thanks @Jan!

The fix for losing the dict tag (thanks for pointing this out @CapelliC) is different. It looks like I need to pass an option to json_write/2 to get that to be output (example below from the docs for that predicate).

Is it not on in json_write/2 by default since the type JSON dict entry it generates could conflict with an existing entry called type? The JSON format allows (but discourages) duplicate keys. Python (and C#, I don’t know about others) accept it fine and just throw out one of the duplicates. It seems to me that the confusion of losing a duplicate key is worse than the confusion of losing the dict type, so I’m inclined to change the MQI to always included. However, I’m not a user of dicts and don’t know the history of why json_write/2 chose to go this way.

Any thoughts?

?- json_write(current_output, point{x:1,y:2}).
{
  "x":1,
  "y":2
}

?- json_write(current_output, point{x:1,y:2}, [tag(type)]).
{
  "type":"point",
  "x":1,
  "y":2
}

Dicts map pretty much naturally to JSON, but JSON does not have a notion of a tag (which was added first of all to resolve syntax ambiguity). Just adding the tag as “type” is dubious. The dict tag often has no (useful) meaning. You could also add it as e.g. "$tag"?

Ahh! I didn’t realize the name ‘type’ was user settable from the docs on json_write/3.

If knowing tag is not that useful, I see two options:

  1. Just leave it off (as it is now) and see if there are actual requests to include it
  2. Provide an (off by default) option to include it and allow the user to set the name (same approach as json_write/3). I could start by having this be an option on mqi_start/1. If users end up wanting more granularity we can add overrides to the setting on the connection, or even per query, later.

Seems like #1 is the easiest approach since I haven’t seen a real request for this yet, but let me know if you think it is important enough to add the option.

I have just submitted a fix for your original issue @Losbarthos:

from swiplserver import *

with PrologMQI() as mqi:
    with mqi.create_thread() as prolog_thread:
        result = prolog_thread.query("A = point{x:1, y:2}.put([x=3,z=0]).")
        print(result)

now works like the top level and does goal expansion. So it prints:

[{'A': {'x': 3, 'y': 2, 'z': 0}}]

It doesn’t change the protocol so you don’t need a new swiplserver Python library, just a new version of MQI.pl which you can get from here. Download that version and install it (see the documentation for the full instructions), like this on the command line:

swipl -s mqi.pl -g "mqi:install_to_library('mqi.pl')" -t halt

I went with option 1 (above) for how dicts get written out, so no changes there.