sCASP: calling prolog within scasp

In order for sCASP to be useful in practice, we need to be able to process information from the network, the user, files, sensors, etc. SWI-Prolog already provides all these facilities, so here is a small proposal to allow sCASP to access the prolog functionality.

Scasp receives as input a set of terms describing a program, and it builds the possible models (list of terms) that this program can produce. My proposal is to provide the ability to produce some of the input terms by calling prolog predicates.

  • My proposal is to add a { Prolog Call, Prolog Call, … } notation to sCASP.
  1. Each Prolog call can be:
    1.1. PredicateCall(X,Y,Z)
    1.2. PredicateCall(X,Y,Z) -> ScaspTerm(X,Y,Z)

Example, given the following sCASP program:

:- use_module('../../prolog/scasp/embed').
:- use_module('../../prolog/scasp/human').

% something is a moth if it does not fly during daylight.
%
:- begin_scasp(moth, [moth_scasp/1]).

moth_scasp(X) :- not flies_during_day(X).

flies_during_day(B) :- bird(B).

bird(eagle).
bird(hummingbird).
bird(bluejay).

:- end_scasp.
:- begin_scasp(prolog_embed,

We can write it embedding prolog in the following way:

% something is a moth if it does not fly during daylight.
%
:- begin_scasp(moth, [moth_scasp/1]).

moth_scasp(X) :- not flies_during_day(X).

flies_during_day(B) :- bird(B).

{ bird(X) }. % this will add to the scasp program the term bird(X)
             %  for all cases in which bird(X) is true. 


:- end_scasp.

% this is prolog code
bird(eagle).
bird(hummingbird).
bird(bluejay).

A more useful case, suppose we ask the user if X is a bird (something that can’t be done in sCASP since it doesn’t have a way to ask a question to the user):

:- begin_scasp(moth, [moth_scasp/1]).

moth_scasp(X) :- not flies_during_day(X).

flies_during_day(B) :-
   { ask_user_if_bird(B) -> bird(B) },  % this will add to the scasp program the term bird(X)
                                        %  if ask_user_if_bird(X) is true. It could also be
                                        % placed after the next line, order doesn't matter.
   bird(X).
:- end_scasp.

% This is the prolog code, we could also get it from the network, another system, etc.
ask_user_if_bird(X) :-
    format('Is ~w a bird? (y/n)?',[X]),
    get_single_char(0'y).

If there is backtracking, then all the terms are added to the sCASP program (e.g. using findall/3 to get all the answers).

These are just my quick thoughts for now, but wanted to put it out there to spot problems and for comments.

Thanks. That might work. Here is another line of thinking:

  • Do not provide :- begin_scasp, etc.
  • Instead, add scasp(Goal). This will:
    • Collect the reachable call tree
    • Verify all reached predicates can be used with s(CASP)
    • Generate the s(CASP) precompiled representation
    • Solve the query.

Now we can do a bit of cashing. SWI-Prolog has a last_modified_generation property for predicates that make this a bit simpler. I have some code lying around that uses this for the purpose of creating a signature of the code relevant to a query.

This would mean you can write normal Prolog (respecting s(CASP)'s restrictions). All the usual Prolog reuse (i.e., modules) and dynamic code handling will work.

There are two issues I’m not really satisfied with

  • How to deal with global constraints. I.e., how do we declare them and how do we decide that a global constraint it relevant to a query? One option could be to include global constraints if its call graph overlaps with the query.
  • We can collect the program from the actual query (with partly instantiated arguments) or from the generalized query (with only free arguments). In the latter case we end up with a smaller program and may miss (avoid?) clauses that cause global constraints and result in “no model”. While for the initial query it is fairly simple, it is hard to be precise about specialization for indirect goals. This would result in unpredictable behavior. So, I think we should generalize?

This looks like a good idea to me, with a slight variation:

  • keep :- begin_scasp, but simply to provide lexical scoping and options for sCASP compilation
    • This will solve the :- Constraint problem, as they will be recognized inside the lexical scope delimited by begin_scap/end_scasp
    • It will also allow lexical scoping for #pred, #show, #include, etc directives preventing pollution of the global lexical scope

Then you can do as you mentioned above when an sCASP goal is called:

  • collecting the reachable call tree
  • verifying all reached predicates can be used with sCASP,
  • etc.

Another advantage of keeping :- begin_scasp, etc is that the mental model for sCASP is different from prolog. An sCASP program tries to create possible worlds of terms (the models) that are stable and compatible with the program. This is different from prolog. For example, in sCASP:

p :- q.
q.

will give you [p,q] as the model if you query p. But if you simply add a predicate (which is not called by p) something counter-intuitive for a prolog mind happens:

p :- q.
q.
z :- p, not z.

now p is false, there is no model (when querying p) because the only way to satisfy p,not z == false is by taking p out of the model. This is counter-intuitive for a prolog programmer.

For this reason I think it is very useful to provide visual (lexical) scoping for the sCASP program, so that the develper can more easily switch the mental model in his mind. :- begin_scasp and friends provide this visual feature.

Like you, I think for now we should generalize the queries, sCASP predicate interaction is very difficult to predict, so I think to put specialization at this early stage will just give headaches. I think it is better left for a later time, after the system gets used more and specific usage patterns can be identified.

One important point I did not mention in my previous reply. We need something to declare that a specific prolog predicate is not meant to be converted into scasp code.

This is needed because many times we would want to call a prolog predicate simply due to a side effect, for example:

:- begin_scasp([p/1]).
   p(X) :- 
      http_get('https://my.server/api/query',X,[]), format('Got X from network',[X]), 
      q(X).

   q(X) :- ...
:- end_scasp. 

In the above case we don’t want http_get/3 or format/2 to generate any sCASP code, we simply want them to bind X and to have the side effect of printing to the console. Perhaps
enclosing them in {} ? Not sure what would be the best remedy.