How to write/edit own coroutines in Prolog?

818 Views Asked by At

I would like to build my own coroutines in Prolog. I'd like to add some extra functionalities.

2

There are 2 best solutions below

6
On BEST ANSWER

One possible solution would be to use the term-expansion mechanism provided by some Prolog systems and Logtalk to rewrite calls to e.g. the freeze/2 predicate to do the extra steps you want. One must be careful, however, to not expand a call to a predicate into another goal that calls the same predicate as goal-expansion is recursively applied until a fixed-point is reached. The Logtalk implementation of the term-expansion mechanism makes it easy to avoid this trap (with the additional advantage of portability as you can use Logtalk with most Prolog systems) by using a compiler bypass control construct, {}/1. A silly example would be:

:- object(my_expansions,
    implements(expanding)).

    goal_expansion(
        freeze(Var,Goal),
        (   write('If you instantiate me, I will run away!\n'),
            {freeze(Var,Goal)},  % goal will not be further expanded
            write('Bye!\n')
        )
    ).

:- end_object.

This object can then be used as an hook object for the compilation of source files containing calls to freeze/2 that you want to expand. Something like (assuming that the object above is saved in a file with the name my_expansions.lgt and that the source file that you want to expand is named source.lgt):

?- logtalk_load(my_expansions), logtalk_load(source, [hook(my_expansions)]).

For full details see the Logtalk documentation and examples.

There might be a clean way that I'm not aware of doing the same using the a Prolog system own term-expansion mechanism implementation. Anyone?

1
On

Writing a vanilla interpreter for coroutines should be on the teaching list of every Prolog course. It is quite simple, here you see the normal vanilla interpreter, simplified:

% solve(+Term)
solve(true).
solve((A,B)) :- solve(A), solve(B).
solve(H) :- clause(H, B), solve(B).

Now for corouting, in the sense of suspending goals via freeze/2, just add an additional input output parameter pair with the delayed goals, for a specification of select/3 see (*):

% solve(+Term, +List, -List)
solve(G, L, R) :- select(freeze(V, F), L, H), 
   nonvar(V), !, 
   solve((F,G), H, R).
solve(freeze(V, G), L, [freeze(V,G)|L]) :- var(V), !.
solve(freeze(_, G), L, R) :- solve(G, L, R).
solve(true, L, L).
solve((A,B), L, R) :- solve(A, L, H), solve(B, H, R).
solve(H, L, R) :- clause(H, B), solve(B, L, R).

You can use the above vanilla interpreter to study different wake-up strategies. I am not sure whether it captures existing Prolog systems. But you could run examples such as:

?- freeze(X, member(X, [the(1), the(2)])), X = the(Y).

successfully, by posing the following question:

?- solve((freeze(X, member(X, [the(1), the(2)])), X = the(Y), true), [], L).

The ", true" is needed to check a last time for woken up goals. If L is returned empty then all frozen goals where woken up. Otherwise there are some pending frozen goals. Which is sometimes called floundering.

The above prototype also leads to a natural implementation of coroutines via thin attributes, the undo/1 and some little support of the interpreter by a goal injection queue. I will post about this soon somewhere else.

Bye

(*) https://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue