A “continuation” is the code that will execute when a given function or expression returns. For example, consider
(define (foo) (display "hello\n") (display (bar)) (newline) (exit))
The continuation from the call to bar
comprises a
display
of the value returned, a newline
and an
exit
. This can be expressed as a function of one argument.
(lambda (r) (display r) (newline) (exit))
In Scheme, continuations are represented as special procedures just like this. The special property is that when a continuation is called it abandons the current program location and jumps directly to that represented by the continuation.
A continuation is like a dynamic label, capturing at run-time a point in program execution, including all the nested calls that have lead to it (or rather the code that will execute when those calls return).
Continuations are created with the following functions.
Capture the current continuation and call (proc
cont)
with it. The return value is the value returned by
proc, or when (cont value)
is later invoked,
the return is the value passed.
Normally cont should be called with one argument, but when the
location resumed is expecting multiple values (see Returning and Accepting Multiple Values) then they should be passed as multiple arguments, for
instance (cont x y z)
.
cont may only be used from the same side of a continuation barrier as it was created (see Continuation Barriers), and in a multi-threaded program only from the thread in which it was created.
The call to proc is not part of the continuation captured, it runs only when the continuation is created. Often a program will want to store cont somewhere for later use; this can be done in proc.
The call
in the name call-with-current-continuation
refers to the way a call to proc gives the newly created
continuation. It’s not related to the way a call is used later to
invoke that continuation.
call/cc
is an alias for call-with-current-continuation
.
This is in common use since the latter is rather long.
Here is a simple example,
(define kont #f) (format #t "the return is ~a\n" (call/cc (lambda (k) (set! kont k) 1))) ⇒ the return is 1 (kont 2) ⇒ the return is 2
call/cc
captures a continuation in which the value returned is
going to be displayed by format
. The lambda
stores this
in kont
and gives an initial return 1
which is
displayed. The later invocation of kont
resumes the captured
point, but this time returning 2
, which is displayed.
When Guile is run interactively, a call to format
like this has
an implicit return back to the read-eval-print loop. call/cc
captures that like any other return, which is why interactively
kont
will come back to read more input.
C programmers may note that call/cc
is like setjmp
in
the way it records at runtime a point in program execution. A call to
a continuation is like a longjmp
in that it abandons the
present location and goes to the recorded one. Like longjmp
,
the value passed to the continuation is the value returned by
call/cc
on resuming there. However longjmp
can only go
up the program stack, but the continuation mechanism can go anywhere.
When a continuation is invoked, call/cc
and subsequent code
effectively “returns” a second time. It can be confusing to imagine
a function returning more times than it was called. It may help
instead to think of it being stealthily re-entered and then program
flow going on as normal.
dynamic-wind
(see Dynamic Wind) can be used to ensure setup
and cleanup code is run when a program locus is resumed or abandoned
through the continuation mechanism.
Continuations are a powerful mechanism, and can be used to implement almost any sort of control structure, such as loops, coroutines, or exception handlers.
However the implementation of continuations in Guile is not as efficient as one might hope, because Guile is designed to cooperate with programs written in other languages, such as C, which do not know about continuations. Basically continuations are captured by a block copy of the stack, and resumed by copying back.
For this reason, continuations captured by call/cc
should be used only
when there is no other simple way to achieve the desired result, or when the
elegance of the continuation mechanism outweighs the need for performance.
Escapes upwards from loops or nested functions are generally best handled with prompts (see Prompts). Coroutines can be efficiently implemented with cooperating threads (a thread holds a full program stack but doesn’t copy it around the way continuations do).