I’ve had a bit of an “ah-ha!” with delimited continuations, so here’s my attempt to explain them. I think that the “get/put” example (the “state monad”[*]) is a bit better for explaining than the DCG example (but if my explanation doesn’t help, I can try again with DCGs).
The trick (for me) was to not think about the specific semantics of reset/3 and shift/1 but rather how they’re used – reset/3 is used by a “server” and shift/1 is used by a “client”. The “server” is just a loop over a Goal
that can send a “command” to the server (using shift/1), with each iteration passing the “state” and receiving a Command
from the “client”. When there are no more commands to the server, the loop terminates:
%! run_state(:Goal, +StateIn, -StateOut) is det.
run_state(Goal, StateIn, StateOut) :-
reset(Goal, Command, Cont),
( Cont == 0 % no more commands from Goal?
-> StateOut = StateIn
; run_state_(Command, Cont, StateIn, StateOut)
).
The “server” interprets the Command
. There are two commands: “get” the current state’s value and “put” (or set) the state’s value. The Cmd
argument contains a new value with put_cmd(Value)
; get_cmd(Value)
receives (unifies) the current value. Note that the run_state_/4
predicate recursively tail-calls run_state/3
with a new continuation as the Goal
– this continuation resumes execution immediately after the command to the server.
%! run_state(?Cmd, +Cont, +StateIn, -StateOut) is det.
run_state_(get_cmd(S), Cont, StateIn, StateOut) =>
S = StateIn,
run_state(Cont, StateIn, StateOut).
run_state_(put_cmd(S), Cont, _StateIn, StateOut) =>
run_state(Cont, S, StateOut).
We now define wrappers for the client:
%! get(-S) is det.
get(S) :- shift(get_cmd(S)).
%! put(+S) is det.
put(S) :- shift(put_cmd(S)).
Now, let’s put it together. We first define an inc(I)
predicate that increments the “state”:
inc(I) :-
get(S0),
S is S0 + I,
format('~w + ~w => ~w~n', [S0, I, S]), % for debugging
put(S).
and let’s run it with the goal (inc(1), inc(2), inc(4))
:
?- run_state((inc(1), inc(2), inc(4)), 0, S).
0 + 1 => 1
1 + 2 => 3
3 + 4 => 7
S = 7.
This style of client/server reminds me of Erlang, except that the full power of Prolog is available, including backtracking.
Of course, there are other uses of reset/3 and shift/1, but the client-server paradigm helped me understand the “state monad” and DCGs using delimited continuations.
[*] Don’t worry about the term “monad”. After all, a monad in X is just a monoid in the category of endofunctors of X, with product ×
replaced by composition of endofunctors and unit
set by the identity endofunctor.