Scheme was the first widely used language to make the rest of a computation, its continuation, a value a program can hold. This page describes call-with-current-continuation and dynamic-wind as R7RS-small defines them (section 6.10); R5RS and R6RS define the same two procedures.
What a continuation is
Whenever an expression is evaluated there is a continuation waiting for its result. It stands for everything that will happen next: if the expression is typed at a REPL, the continuation might print the result, prompt for more input, evaluate that, and so on without end. Most of the time it also includes the actions of the program, such as multiplying the result by a local variable and adding seven. Normally continuations stay hidden.
call-with-current-continuation
(call-with-current-continuation proc), abbreviated call/cc in R7RS, packages the current continuation as an escape procedure and passes it to proc, which must accept one argument. If the escape procedure is called later, with any number of values the original continuation accepts, whatever continuation is then in effect is abandoned and the saved one is used instead. The procedure never returns to its caller, like raise.
The escape procedure has unlimited extent: it can be stored in a variable or a data structure and called many times, even after the call to call/cc returned. Each call re-enters the saved computation from that point. call/cc is therefore both the escape of other languages (break, return, throw) and a way to go back.
Calling proc is a tail call. A continuation created by call-with-values (including those of define-values, let-values and let*-values) takes as many values as the consumer expects, and the continuation of every non-final expression of a body, begin, cond, case, when, unless, guard and parameterize accepts any number of values. What a continuation does with another number of values is unspecified.
(call-with-current-continuation
(lambda (exit)
(for-each (lambda (x)
(if (negative? x)
(exit x)))
'(54 0 37 -3 245 19))
#t)) ; -3
(define list-length
(lambda (obj)
(call-with-current-continuation
(lambda (return)
(letrec ((r (lambda (obj)
(cond ((null? obj) 0)
((pair? obj) (+ (r (cdr obj)) 1))
(else (return #f))))))
(r obj))))))
(list-length '(1 2 3 4)) ; 4
(list-length '(a b . c)) ; #f
call/cc (R7RS section 6.10).Re-entering a continuation
Because the escape procedure outlives the call, it can be used to return into a computation that has already finished. A common use is a generator or a coroutine: two computations that hand control to each other, each saving its own continuation before it switches.
(define k #f)
(define n 0)
(display (+ 1 (call/cc (lambda (c) (set! k c) 1))))
(newline)
(set! n (+ n 1))
(if (< n 3) (k n)) ; prints 2, then 2, then 3
Since a continuation can be called more than once, an expression can return more than once. Code that assumes it returns once (it allocates a resource and frees it afterwards, or updates a variable twice) breaks, which is what dynamic-wind and parameterize are for. The structured mechanisms, raise and guard (see Error handling), are preferred for exits; R7RS says so in the rationale of call/cc.
dynamic-wind
(dynamic-wind before thunk after) calls thunk and returns its values. before is called whenever execution enters the dynamic extent of the call to thunk, and after whenever it leaves. Without continuations that is once each, in that order. With continuations, extent can be entered and left many times.
The dynamic extent of a call is the time between its start and its return, but because of call/cc it need not be a single period. It is entered when the body of the called procedure begins, and again when a continuation captured during that extent is invoked from outside. It is left when the procedure returns, and when a continuation captured outside the extent is invoked from inside it.
When a continuation is invoked, the rules for several nested dynamic-wind calls are these:
- if two
afterthunks are to be called, the inner one is called first; - if two
beforethunks are to be called, the outer one is called first; - if both an
afterand abeforeare to be called, theafteris called first.
The before and after thunks run in the dynamic environment of the call to dynamic-wind. The effect of entering or leaving the extent of before or after themselves with a captured continuation is unspecified.
(let ((path '())
(c #f))
(let ((add (lambda (s) (set! path (cons s path)))))
(dynamic-wind
(lambda () (add 'connect))
(lambda ()
(add (call-with-current-continuation
(lambda (c0) (set! c c0) 'talk1))))
(lambda () (add 'disconnect)))
(if (< (length path) 4)
(c 'talk2)
(reverse path))))
; (connect talk1 disconnect connect talk2 disconnect)
dynamic-wind example of R7RS section 6.10: connect and disconnect are run again each time the extent is entered and left.Where else the extent of a call matters
- Parameters.
parameterizebinds for the dynamic extent of its body and unwinds when a continuation leaves it, as if it were adynamic-wind. - Exception handlers.
with-exception-handlerinstalls a handler for the dynamic extent of its thunk, and a handler is called in the dynamic environment of theraiseexcept that the current handler is the outer one.guarduses a continuation to escape and then re-enters theraisecontext to try the next handler byraise-continuablewhen no clause matches. - Promises. A delayed expression runs with the parameters and handler of the
forcecall that first demanded it. - Ports.
call-with-portcloses its port when the procedure returns. If the procedure does not return, R7RS says the port must not be closed automatically unless it can be proved never to be used again, because an escape procedure can resume the computation later; the rationale gives exactly this as the reason. Code that must close a port on any exit wraps the work indynamic-wind.
Delimited continuations
Full continuations capture the whole rest of the program. Several implementations also offer delimited continuations, which capture only up to a marked point (reset and shift, call-with-prompt, call-with-escape-continuation, call/ec, call-with-composable-continuation). They are not in R5RS, R6RS or R7RS-small; SRFI 226 (Control Features) and SRFI 248 (Minimal delimited continuations) specify some of them. The viewer colours call/ec and call-with-escape-continuation as functions.
Not described
Known, not yet covered
- Reference: the formal semantics of
call/ccanddynamic-windin the report’s last chapter, and the semantics of continuations in implementations that compile to the stack of a host machine (the cost of capture and re-entry), are not described. - Reference: the delimited-continuation operators are named but their semantics, which differ between implementations and SRFIs, are not described.
Unknown
- Reference: the REPL example above (continuing with the next form rather than re-running the line) depends on the implementation; it was not run in any.
See also
- Control and tail calls – parameters and multiple values
- Error handling –
guardandwith-exception-handler