How to set a global value during execution of a predicate

I want to implement a tracing predicate that outputs which predicate is being “currently resolved” from within any predicates that get called during its resolution.

To do this I am trying to find a way to set global state to a value only:

  • during execution of a predicate
    or
  • during any of the children called to “prove” it.

Here’s an example that uses an unimplemented setGlobalDuringPredicate/2 predicate as an example:

:- dynamic(globalValue/1).
globalValue(none).

writeGlobal(Tag) :-
    globalValue(X),
    writeln(value(Tag, X)).

parentPredicate :-
    % Unclear how to implement setGlobalDuringPredicate 
    % so that I get the output below
    setGlobalDuringPredicate(value1, childPredicate),   
    writeGlobal(parentPredicate).

childPredicate :-
    writeGlobal(childPredicate).

?- parentPredicate.
value(childPredicate, value1)
value(parentPredicate, none)

I tried implementing setGlobalDuringPredicate using setup_call_cleanup. It almost worked. The problem was that cleanup is only called after all backtracking is done in call. So if childPredicate and parentPredicate were written so that execution backtracks once it would execute like this:

:- dynamic(globalValue/1).
globalValue(none).

setGlobalDuringPredicate(Value, Goal) :-
    setup_call_cleanup(
        (
            globalValue(PreviousValue),
            retractall(globalValue(_)),
            assert(globalValue(Value))
        ),
        (
            Goal
        ),
        (
            retractall(globalValue(_)),
            assert(globalValue(PreviousValue))
        )
    ).

writeGlobal(Tag) :-
    globalValue(X),
    writeln(value(Tag, X)).

parentPredicate :-
    % Unclear how to implement setGlobalDuringPredicate 
    % so that I get the output below
    setGlobalDuringPredicate(value1, childPredicate),   
    writeGlobal(parentPredicate),
    fail.

backtrack(a).
backtrack(b).

childPredicate :-
    backtrack(X),
    writeln(X),
    writeGlobal(childPredicate).

?- parentPredicate.
a
value(childPredicate,value1)
value(parentPredicate,value1) % not what I want. I want `none` 
b
value(childPredicate,value1)
value(parentPredicate,none)
false.

Is there a way to get the behavior I want (outside of making every predicate take an argument where I pass along the “current predicate”)?

One way to “pass along” an argument without having to type it all the time, is to use DCGs, see the section titled " Implicitly passing states around" in this DCG tutorial

1 Like

Why do you want to use a global variable for that? It seems that this is what meta-interpreters are for.

EDIT: in particular, “The Art of Prolog” by Sterling and Shapiro (PDF available on the same site), in Chapter 17. Interpreters, has sections on “Meta-Interpreters” and “Enhanced Meta-Interpreters for Debugging” (pp. 320-340). Did you already take a look at that?

On the other hand, how is the current debugger/tracer not good enough for your use case? (and what is really your use case?)

1 Like

Yet another is prolog_frame_attribute/3 which allows inspecting the environment stack. You do need to kill last call optimization, e.g., by running the program in debug mode (debug/0).

Using global variables to set something (in other languages) “during the execution of a function” is a lot harder in Prolog. You need to wrap the call by watching all 5 ports (call, fail, exit, redo and exception). It can be done, but is ugly and slows down the execution a lot.

2 Likes

Thanks for the reference @swi, I haven’t spent a lot of time with DCGs yet. Unless I’m missing something, though, to get the cool feature of implicitly passing state, I’ll need to change all the predicates to use the DCG termName --> format, and use {} when calling “normal” Prolog predicates. For my case, I think it would be simpler and more readable just to tack on an argument to every predicate if I end up going this way.

Another way you can do this (as @Boris mentioned) is to write a small meta-interpreter, which will process your program (instead of the regular prolog semantics you can add the pre- and post- execution code that you want after executing each goal), see https://www.metalevel.at/acomip/ in the same tutorial above.

If you use Extended DCGs, you don’t need to use {} for “normal” predicates, but you do need to declare the predicates that have extra parameters (you can use a “wild card” notation) and use -->>

@Boris, my use case is implementing a heuristic for reporting, in human terms, why a Prolog predicate failed in a narrow case: when it represents a natural language predicate. Here’s the description of what I’ve built so far.

The part I’m asking about here: the meta-predicate which indicates that a goal is “top-level” (i.e. represents something the user will understand errors coming from), is called reportError/2 in that writeup. Also: here’s the overview of the whole system for context. (EDIT: And, if you run the demo, you can type /show to see the Prolog getting executed).

Surprisingly, the heuristic worked pretty well even with the code I have there which doesn’t properly clean up after itself when a goal fails.

I’ve got the 1991 edition of that book and re-looked at that section. It certainly seems a simple meta-interpreter might be able to be bent to do what I want. I’ll take a deeper look. Thanks!

2 Likes

OK, after some research I feel like meta interpreters are not as straightforward as “The Art of Prolog” would lead you to believe… Even the sample code from that book uses ancestral cuts which sound like they leak stack space in some cases and are strongly warned against in SWI Prolog. I played with the code at “https://www.metalevel.at/acomip/” and it wasn’t able to process system predicates, do cuts, etc. All I’m finding are either simplified examples or posts of unfinished code with comments about missed corner cases.

Anyone know of a pointer to a complete SWI Prolog meta-interpreter that can run arbitrary SWI Prolog (or SWI Prolog with limitations that most programs don’t care about)?

While Jan W. will be able to give an exact answer, in the mean time I will suggest that if you search the source code for SWI-Prolog on GitHub you will find a few places where meta-interpreters are being used, e.g. IIRC the trace and debug predicates. As I don’t have exact working knowledge of them, I suspect that some of the corner cases might be handled with C code.

Also there is a place holder for a Wiki on Meta-interpreters on this site, the part of value is the related discussion topic which has some information.

Also some of the SWI-Prolog packs use meta-interpreters but I don’t recall which.

HTH


EDIT

Another place to look is in other GitHub repositories with Prolog code. While there are hundreds of such repositories, the better ones are noted here.

1 Like

This is the problem :slight_smile: you do realize that this is strictly more difficult than making an SWI-Prolog, right?

I don’t really understand what you are doing, so it is easy to give advice. Your best bet is to come up with something much simpler that still solves the problem you are solving.

I think I would try to tackle it with shift/reset - sorry I have no time to go deeper right now, and I could be totally wrong…
edit
library(intercept) is built on shift/reset, and could be of help for your problem.

Properly meta-interpreting an arbitrary body term is defined in ‘$meta_call’/1, which you find in boot/init.pl. This is used for call/1 inside shift/reset as the latter cannot handle the usual compilation based meta-calling of SWI-Prolog.

Then there are the tricks using stack inspection, etc. What to use depends a lot on what you are actually after though and so far we have no clue.

1 Like

I’m sorry I haven’t been able to describe this clearly enough. The write-up I linked to was my best effort at describing what I am actually doing. Maybe this will help:

The mechanism I wish I had was this: I want a pair of terms: callWithContext/2 and getContext/1.

callWithContext(Goal, Context) behaves just like call/1 except that it allows the Context argument to be retrieved within the Goal (or any term resolved by the goal) by calling getContext/1.

It also needs to support nesting. Meaning that getContext/1 always retrieves the closest Context on the stack.

Writing it like that makes me realize that I might be able to reuse the exception handling infrastructure to do what I want. Is there a way to get the (not sure the right terminology) “nearest” exception frame that is active and inspect it?

EDIT: Or to just add data to the stack in a way that can be inspected with prolog_frame_attribute?

Typically, by far the most efficient way is to misuse the stack inspection. This is the first step. The no_lco/1 call ensures call_with_context/2 remains on the stack and passing the argument ensures Prolog will not garbage collect the argument.

call_with_context(Goal, Context) :-
    call(Goal),
    no_lco(Context).

no_lco(_).

The next step is to retrieve the context. You do that using

get_context(Context) :-
    prolog_current_frame(Me),
    prolog_frame_attribute(Me, parent_goal, call_with_context(_, Context)).

Not tested. Should be pretty close though. This is fairly efficient. It slows down if you call get_context/1 inside deeply nested goals that could not be optimized using LCO (Last Call Optimization). In most scenarios this will be a lot faster than trying to maintain a global variable though.

1 Like

Ahhh! This is great. Thanks so much. I’ll post if there are any tweaks needed once tested, for posterity.

I’ve seen the need for this so many times that I’m still considering something really efficient in the VM to support this scenario. The rough idea is something close to what I outlined, but using dedicated stack frames that are linked together, so deeply nested goals to not affect the performance. Each frame should contain a set of Name=Var and fetching a context should return the matching binding from the innermost frame that holds Name. You get e.g.

- call_with_context(Goal, _{a:1, b:42})
- context(a, X)

An alternative would be to allow lexical nesting of predicates. I fear it is hard to come up with an acceptable syntax for that. Somewhere in the past a notion of module parameters has been proposed. I don’t recall the details. If anyone knows, please share. Logtalk provides Parametric Objects for this type of problems.

The mechanism will remain the same. A good proposal for an API is welcome!

2 Likes

parentages

demo 1


a(FOO) :-
system:format("in a~n") ,
report_about_parentages ,
b(FOO)
.

b(_FOO) :-
system:format("in b~n") ,
report_about_parentages ,
c
.

c :-
system:format("in c~n") ,
report_about_parentages
.

/*
?- a('hello') .
in a
parentage report is [a(hello)]
in b
parentage report is [b(hello),a(hello)]
in c
parentage report is [c,b(hello),a(hello)]
.
*/

demo 2


a(FOO)
:-
state(STATE) ,
system:format("STATE from a is ~q~n",STATE) ,
b(FOO) .

b(_FOO)
:-
state(STATE) ,
ancestor(ANCESTOR) ,
system:format("STATE from b is ~q~n",STATE) ,
system:format("ANCESTOR from b is ~q~n",ANCESTOR) ,
c .

c
:-
state(STATE) ,
ancestor(ANCESTOR) ,
system:format("STATE from c is ~q~n",STATE) ,
system:format("ANCESTOR from c is ~q~n",ANCESTOR)
.

/*
?- a('hello') .
STATE from a is a(hello)
STATE from b is b(hello)
ANCESTOR from b is a(hello)
STATE from c is c
ANCESTOR from c is b(hello)
.
*/

source


%! parentages(-SIGNATUREs)
%% `SIGNATUREs` is an most-recent-first list of signatures of
%% predikats on the call stack .

parentages(SIGNATUREs)
:-
prolog:bagof(SIGNATURE,parentage(SIGNATURE),SIGNATUREs)
.
%! report_about_parentages
% dump an report about parentages to stdout .

report_about_parentages
:-
parentages(SIGNATUREs) ,
system:format("~Nparentage report is ~q~n",[SIGNATUREs])
.

%! ancestor(-ANCESTOR)
%% true if `ANCESTOR` is the signature of the calling predicate ;
%% false if there is no calling predicate in the stack .

ancestor(ANCESTOR)
:-
prolog:findnsols(2,PARENT,parentage(PARENT),PARENTs) ,
! ,
prolog:nth1(2,PARENTs,ANCESTOR)
.

%! state(-STATE)
%% `STATE` is the signature of the current callee predicate .

state(STATE)
:-
parentages(STATE) ,
!
.

term_expansion((A :- B),C)
:-
SETUP=(prolog:asserta(parentage(A),REF)) ,
CALL=(B) ,
CLEANUP=(prolog:erase(REF)) ,
C = (A :- prolog:setup_call_cleanup(SETUP,CALL,CLEANUP))
.

@kintalken