The prolog kontinuations behaves vary much like ordinary goals, they can fail and also
one can backtrack to them yielding a new solution e.g. one can easally produce very cool parameterized goals
where one setup the goal and then issue an abort_to_prompt ... hence can do just a minor modification
of an already present prolog program in stead of redesigning the hole lot.
The background is that we tout that one of the cool features of guile and many other schemes is that it has proper
delimited continuations, get this concept into prolog as well.
The interface, here we goooo,
Dependencies:
So fire up guile-log and iso-prolog dependencies. In your file that will produce a compiled prolog program e.g. using a macro that takes a prolog files or prolog string and outputs scheme code, make sure that the dependecy,
(use-modules (logic guile-log guile-prolog continuations))
Then you can use the ideoms
abort_to_prompt, with_prompt, re_prompt
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
abort_to_prompt(tag,data,feed)
As with normal abort-to-prompt, this will unwind the stack back to handler that matches tag with returning
'data' as one argument, 'feed' will be the data past back when the the continuation is instantiated.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
with_prompt(tag,code,handler-data,handler)
Creates a handler point on the prompt stack where it will set up the handler and execute code. if
abort_to_prompt is executed and matched with tag the handler will be executed with handler data as
an argument. Handler data is a list of the format format [tag, next, k, data] that will be matched against
the corresponding incoming data, tag will be the tag send by abort_to_prompt, next if used as next()
will continue to the next handler in the prompt stack that matches tag, k will be the continuation and data
will be matched to the data send with abort_to_prompt.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
re-prompt is similar, but reuses the old handler in stead of creating a new one which may cause the stack to
grow too much, use them with e.g. generators, they have else the same semantic as with_prompt.
So how do it work in practice? Lets make a generator framework
(compile-string "yield(X) :- abort_to_prompt(generator,X,_).")
(compile-string "eat(X) :- abort_to_prompt(generator,_,X).")
(compile-string "generator(Goal,F) :-
with_prompt(generator, Goal,[generator,_,K,X],
F=[K,X]).")
yield will be a data producer, eat a data consumer and generator the initial construction of the generator
The code that will use the generators should use
(compile-string
"
next([K,X],X,F) :- re_prompt(K,[generator,_,K2,XX],F=[K2,XX],_).
feed([K,_],Y,F) :- re_prompt(K,[generator,_,K2,_ ],F=[K2,_ ],Y).
translate([K,X],X,Y,F) :- re_prompt(K,[generator,_,K2,XX],F=[K2,XX],Y).
")
E.g.
next(Generator, OutputData , NextGenerator)
feed(Generator, InputData , NextGenerator)
translate(Generator, OutputData, InputData , NextGenerator)
And as an example to try
(compile-string
"
sum(S) :- write(sum(S)),nl,eat(X),write(y(X)),nl,SS is S + X,sum(SS).
run :- generator(iter(0),F),generator(sum(0),S),pr(F,S).
pr(F,S) :- next(F,X,FF) -> write(n(X)),nl, feed(S,X,SS),pr(FF,SS).
iter(N) :- write(iter(N)),nl,N < 10 -> (yield(N),N2 is N + 1, iter(N2)).
")
;; Example 2 (run2)
(compile-string
"
iter2(N) :- write(iter2(N)),nl,N < 10 -> (yield(N) ; N2 is N + 1, iter2(N2)).
run2 :- generator(iter2(0),F),pr2(F,S).
pr2(F,S) :- next(F,X,FF),fail.
")
;; Example 3 (run3)
(compile-string
"
iter3(S,N) :- N < 10 -> (write(iter3(S,N)),nl,N2 is N + 1, iter3(S,N2)) ; true.
run3 :- generator((eat(X),iter3(X,X)),F),pr3(F).
pr3(F) :- call_k(F,_,0),write('--------'),nl,call_k(F,_,5).
")
;; Example 4 (run4)
(compile-string
"
iter4(S,N) :- N < 10 -> (write(iter4(S,N)),nl;N2 is N + 1, iter4(S,N2)).
run4 :- generator((eat(X),iter4(X,X)),F),pr4(F).
pr4(F) :- (call_k(F,_,0);call_k(F,_,5)),fail.
")
//Have fun
Stefan