Prompt Primitives

Guile’s primitive delimited control operators are call-with-prompt and abort-to-prompt.

Scheme Procedure: call-with-prompt tag thunk handler

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 abort-to-prompt.

Scheme Procedure: make-prompt-tag [stem]

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.

Scheme Procedure: default-prompt-tag

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 parameterize. See Parameters.

Scheme Procedure: abort-to-prompt tag val1 val2 …

Unwind the dynamic and control context to the nearest prompt named tag, also passing the given values.

C programmers may recognize call-with-prompt and abort-to-prompt as a fancy kind of setjmp and longjmp, respectively. Prompts are indeed quite useful as non-local escape mechanisms. Guile’s with-exception-handler and raise-exception are implemented in terms of prompts. Prompts are more convenient than longjmp, in that one has the opportunity to pass multiple values to the jump target.

Also unlike 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 call-with-prompt.

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
   ;; tag
   ;; 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 handling an exception (see Exceptions).

The constructs below are syntactic sugar atop prompts to simplify the use of escape continuations.

Scheme Procedure: call-with-escape-continuation proc
Scheme Procedure: call/ec proc

Call proc with an escape continuation.

In the example below, the return continuation is used to escape the continuation of the call to fold.

(use-modules (ice-9 control)
             (srfi srfi-1))

(define (prefix x lst)
  ;; Return all the elements before the first occurrence
  ;; of X in LST.
    (lambda (return)
      (fold (lambda (element prefix)
              (if (equal? element x)
                  (return (reverse prefix))  ; escape `fold'
                  (cons element prefix)))

(prefix 'a '(0 1 2 a 3 4 5))
⇒ (0 1 2)
Scheme Syntax: let-escape-continuation k body …
Scheme Syntax: let/ec k body …

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 (ice-9 control), so load up that module for suspendable-continuation?:

(use-modules (ice-9 control))
Scheme Procedure: suspendable-continuation? tag

Return #t if a call to abort-to-prompt with the prompt tag tag would produce a delimited continuation that could be resumed later.

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 #f.