Guile’s primitive delimited control operators are
Set up a prompt, and call thunk within that prompt.
During the dynamic extent of the call to thunk, a prompt named tag
will be present in the dynamic context, such that if a user calls
abort-to-prompt (see below) with that tag, control rewinds back to the
prompt, and the handler is run.
handler must be a procedure. The first argument to handler will be
the state of the computation begun when thunk was called, and ending with
the call to
abort-to-prompt. The remaining arguments to handler are
those passed to
Make a new prompt tag. A prompt tag is simply a unique object. Currently, a prompt tag is a fresh pair. This may change in some future Guile version.
Return the default prompt tag. Having a distinguished default prompt
tag allows some useful prompt and abort idioms, discussed in the next
section. Note that
default-prompt-tag is actually a parameter,
and so may be dynamically rebound using
Unwind the dynamic and control context to the nearest prompt named tag, also passing the given values.
C programmers may recognize
as a fancy kind of
longjmp, respectively. Prompts are
indeed quite useful as non-local escape mechanisms. Guile’s
throw are implemented in terms of prompts. Prompts are more convenient
longjmp, in that one has the opportunity to pass multiple values to
the jump target.
longjmp, the prompt handler is given the full state of the
process that was aborted, as the first argument to the prompt’s handler. That
state is the continuation of the computation wrapped by the prompt. It is
a delimited continuation, because it is not the whole continuation of the
program; rather, just the computation initiated by the call to
The continuation is a procedure, and may be reinstated simply by invoking it, with any number of values. Here’s where things get interesting, and complicated as well. Besides being described as delimited, continuations reified by prompts are also composable, because invoking a prompt-saved continuation composes that continuation with the current one.
Imagine you have saved a continuation via call-with-prompt:
(define cont (call-with-prompt ;; tag 'foo ;; thunk (lambda () (+ 34 (abort-to-prompt 'foo))) ;; handler (lambda (k) k)))
The resulting continuation is the addition of 34. It’s as if you had written:
(define cont (lambda (x) (+ 34 x)))
So, if we call
cont with one numeric value, we get that number,
incremented by 34:
(cont 8) ⇒ 42 (* 2 (cont 8)) ⇒ 84
The last example illustrates what we mean when we say, "composes with the
current continuation". We mean that there is a current continuation – some
remaining things to compute, like
(lambda (x) (* x 2)) – and that
calling the saved continuation doesn’t wipe out the current continuation, it
composes the saved continuation with the current one.
We’re belaboring the point here because traditional Scheme continuations, as discussed in the next section, aren’t composable, and are actually less expressive than continuations captured by prompts. But there’s a place for them both.
Before moving on, we should mention that if the handler of a prompt is a
lambda expression, and the first argument isn’t referenced, an abort to
that prompt will not cause a continuation to be reified. This can be an
important efficiency consideration to keep in mind.
One example where this optimization matters is escape continuations. Escape continuations are delimited continuations whose only use is to make a non-local exit—i.e., to escape from the current continuation. A common use of escape continuations is when throwing an exception (see Exceptions).
The constructs below are syntactic sugar atop prompts to simplify the use of escape continuations.
Call proc with an escape continuation.
In the example below, the return continuation is used to escape
the continuation of the call to
(use-modules (ice-9 control) (srfi srfi-1)) (define (prefix x lst) ;; Return all the elements before the first occurrence ;; of X in LST. (call/ec (lambda (return) (fold (lambda (element prefix) (if (equal? element x) (return (reverse prefix)) ; escape `fold' (cons element prefix))) '() lst)))) (prefix 'a '(0 1 2 a 3 4 5)) ⇒ (0 1 2)
Bind k within body to an escape continuation.
This is equivalent to
(call/ec (lambda (k) body …)).
Additionally there is another helper primitive exported by
control), so load up that module for
(use-modules (ice-9 control))
#t if a call to
abort-to-prompt with the prompt tag
tag would produce a delimited continuation that could be resumed
Almost all continuations have this property. The exception is where
some code between the
call-with-prompt and the
abort-to-prompt recursed through C for some reason, the
abort-to-prompt will succeed but any attempt to resume the
continuation (by calling it) would fail. This is because composing a
saved continuation with the current continuation involves relocating the
stack frames that were saved from the old stack onto a (possibly) new
position on the new stack, and Guile can only do this for stack frames
that it created for Scheme code, not stack frames created by the C
compiler. It’s a bit gnarly but if you stick with Scheme, you won’t
have any problem.
If no prompt is found with the given tag, this procedure just returns