10.4 Continuations
Continuations in The Racket Guide introduces continuations.
See Sub-expression Evaluation and Continuations and Prompts, Delimited Continuations, and Barriers for general information about continuations. Racket’s support for prompts and composable continuations most closely resembles Dorai Sitaram’s % and fcontrol operator [Sitaram93].
Racket installs a continuation barrier around evaluation in the following contexts, preventing full-continuation jumps into the evaluation context protected by the barrier:
applying an exception handler, an error escape handler, or an error display handler (see Exceptions);
applying a macro transformer (see Syntax Transformers), evaluating a compile-time expression, or applying a module name resolver (see Resolving Module Names);
applying a custom-port procedure (see Custom Ports), an event guard procedure (see Events), or a parameter guard procedure (see Parameters);
applying a security-guard procedure (see Security Guards);
applying a will procedure (see Wills and Executors); or
evaluating or loading code from the stand-alone Racket command line (see Running Racket or GRacket).
In addition, extensions of Racket may install barriers in additional contexts. Finally, call-with-continuation-barrier applies a thunk barrier between the application and the current continuation.
procedure
(call-with-continuation-prompt proc [ prompt-tag handler] arg ...) → any proc : procedure?
prompt-tag : continuation-prompt-tag? = (default-continuation-prompt-tag) handler : (or/c procedure? #f) = #f arg : any/c
The handler argument specifies a handler procedure to be called in tail position with respect to the call-with-continuation-prompt call when the installed prompt is the target of an abort-current-continuation call with prompt-tag; the remaining arguments of abort-current-continuation are supplied to the handler procedure. If handler is #f, the default handler accepts a single abort-thunk argument and calls (call-with-continuation-prompt abort-thunk prompt-tag #f); that is, the default handler re-installs the prompt and continues with a given thunk.
procedure
(abort-current-continuation prompt-tag v ...) → any prompt-tag : any/c v : any/c
The protocol for vs supplied to an abort is specific to the prompt-tag. When abort-current-continuation is used with (default-continuation-prompt-tag), generally, a single thunk should be supplied that is suitable for use with the default prompt handler. Similarly, when call-with-continuation-prompt is used with (default-continuation-prompt-tag), the associated handler should generally accept a single thunk argument.
Each thread’s continuation starts with a prompt for (default-continuation-prompt-tag) that uses the default handler, which accepts a single thunk to apply (with the prompt intact).
procedure
(make-continuation-prompt-tag name) → continuation-prompt-tag? name : symbol?
Changed in version 7.9.0.13 of package base: The name argument gives the name of the prompt tag.
procedure
(default-continuation-prompt-tag) → continuation-prompt-tag?
procedure
(call-with-current-continuation proc [ prompt-tag]) → any proc : (continuation? . -> . any)
prompt-tag : continuation-prompt-tag? = (default-continuation-prompt-tag)
The captured continuation is delivered to proc, which is called in tail position with respect to the call-with-current-continuation call.
If the continuation argument to proc is ever applied, then it
removes the portion of the current continuation up to the nearest
prompt tagged by prompt-tag (not including the prompt; if no
such prompt exists, the exn:fail:contract:continuation exception is raised), or
up to the nearest continuation frame (if any) shared by the current
and captured continuations—
The arguments supplied to an applied procedure become the result values for the restored continuation. In particular, if multiple arguments are supplied, then the continuation receives multiple results.
If, at application time, a continuation barrier would be introduced by replacing the current continuation with the applied one, then the exn:fail:contract:continuation exception is raised.
A continuation can be invoked from the thread (see Threads) other than the one where it was captured.
procedure
proc : (continuation? . -> . any)
prompt-tag : continuation-prompt-tag? = (default-continuation-prompt-tag)
procedure
(call-with-composable-continuation proc [ prompt-tag]) → any proc : (continuation? . -> . any)
prompt-tag : continuation-prompt-tag? = (default-continuation-prompt-tag)
When call-with-composable-continuation is called, if a continuation barrier appears in the continuation before the closest prompt tagged by prompt-tag, the exn:fail:contract:continuation exception is raised (because attempting to apply the continuation would always fail).
procedure
(call-with-escape-continuation proc) → any
proc : (continuation? . -> . any)
A continuation obtained from call-with-escape-continuation is actually a kind of prompt. Escape continuations are provided mainly for backwards compatibility, since they pre-date general prompts in Racket. In the BC implementation of Racket, call-with-escape-continuation is implemented more efficiently than call-with-current-continuation, so call-with-escape-continuation can sometimes replace call-with-current-continuation to improve performance in those older Racket variants.
procedure
proc : (continuation? . -> . any)
procedure
(call-in-continuation k proc) → any
k : continuation? proc : (-> any)
> (+ 1 (call/cc (lambda (k) (call-in-continuation k (lambda () 4))))) 5
> (+ 1 (call/cc (lambda (k) (let ([n 0]) (dynamic-wind void (lambda () ; n accessed after post thunk (call-in-continuation k (lambda () n))) (lambda () (set! n 4))))))) 5
> (+ 1 (with-continuation-mark 'n 4 (call/cc (lambda (k) (with-continuation-mark 'n 0 (call-in-continuation k (lambda () ; 'n mark accessed in continuation (continuation-mark-set-first #f 'n)))))))) 5
Added in version 7.6.0.17 of package base.
syntax
(let/cc k body ...+)
syntax
(let/ec k body ...+)
procedure
(call-with-continuation-barrier thunk) → any
thunk : (-> any)
procedure
(continuation-prompt-available? prompt-tag [ cont]) → any prompt-tag : continuation-prompt-tag? cont : continuation? = (call/cc values)
procedure
(continuation? v) → boolean?
v : any/c
procedure
v : any/c
procedure
(dynamic-wind pre-thunk value-thunk post-thunk) → any pre-thunk : (-> any) value-thunk : (-> any) post-thunk : (-> any)
When dynamic-wind calls pre-thunk for normal evaluation of value-thunk, the continuation of the pre-thunk application calls value-thunk (with dynamic-wind’s special jump handling) and then post-thunk. Similarly, the continuation of the post-thunk application returns the value of the preceding value-thunk application to the continuation of the entire dynamic-wind application.
When pre-thunk is called due to a continuation jump, the continuation of pre-thunk
jumps to a more deeply nested pre-thunk, if any, or jumps to the destination continuation; then
continues with the context of the pre-thunk’s dynamic-wind call.
Normally, the second part of this continuation is never reached, due to a jump in the first part. However, the second part is relevant because it enables jumps to escape continuations that are contained in the context of the dynamic-wind call. Furthermore, it means that the continuation marks (see Continuation Marks) and parameterization (see Parameters) for pre-thunk correspond to those of the dynamic-wind call that installed pre-thunk. The pre-thunk call, however, is parameterize-breaked to disable breaks (see also Breaks).
Similarly, when post-thunk is called due to a continuation jump, the continuation of post-thunk jumps to a less deeply nested post-thunk, if any, or jumps to a pre-thunk protecting the destination, if any, or jumps to the destination continuation, then continues from the post-thunk’s dynamic-wind application. As for pre-thunk, the parameterization of the original dynamic-wind call is restored for the call, and the call is parameterize-breaked to disable breaks.
In both cases, the target for a jump is recomputed after each pre-thunk or post-thunk completes. When a prompt-delimited continuation (see Prompts, Delimited Continuations, and Barriers) is captured in a post-thunk, it might be delimited and instantiated in such a way that the target of a jump turns out to be different when the continuation is applied than when the continuation was captured. There may even be no appropriate target, if a relevant prompt or escape continuation is not in the continuation after the restore; in that case, the first step in a pre-thunk or post-thunk’s continuation can raise an exception.
> (let ([v (let/ec out (dynamic-wind (lambda () (display "in ")) (lambda () (display "pre ") (display (call/cc out)) #f) (lambda () (display "out "))))]) (when v (v "post "))) in pre out in post out
> (let/ec k0 (let/ec k1 (dynamic-wind void (lambda () (k0 'cancel)) (lambda () (k1 'cancel-canceled))))) 'cancel-canceled
> (let* ([x (make-parameter 0)] [l null] [add (lambda (a b) (set! l (append l (list (cons a b)))))]) (let ([k (parameterize ([x 5]) (dynamic-wind (lambda () (add 1 (x))) (lambda () (parameterize ([x 6]) (let ([k+e (let/cc k (cons k void))]) (add 2 (x)) ((cdr k+e)) (car k+e)))) (lambda () (add 3 (x)))))]) (parameterize ([x 7]) (let/cc esc (k (cons void esc))))) l) '((1 . 5) (2 . 6) (3 . 5) (1 . 5) (2 . 6) (3 . 5))
10.4.1 Additional Control Operators
(require racket/control) | package: base |
The racket/control library provides various control operators from the research literature on higher-order control operators, plus a few extra convenience forms. These control operators are implemented in terms of call-with-continuation-prompt, call-with-composable-continuation, etc., and they generally work sensibly together. Many are redundant; for example, reset and prompt are aliases.
procedure
(call/prompt proc [prompt-tag handler] arg ...) → any
proc : procedure?
prompt-tag : continuation-prompt-tag? = (default-continuation-prompt-tag) handler : (or/c procedure? #f) = #f arg : any/c
procedure
proc : (continuation? . -> . any)
prompt-tag : continuation-prompt-tag? = (default-continuation-prompt-tag)
That is, (abort v ...) is equivalent to
(abort-current-continuation (default-continuation-prompt-tag) (lambda () (values v ...)))
syntax
(% expr)
(% expr handler-expr) (% expr handler-expr #:tag tag-expr)
procedure
v : any/c prompt-tag : (default-continuation-prompt-tag)
Sitaram’s operators [Sitaram93].
The essential reduction rules are:
(% val proc) => val (% E[(fcontrol val)] proc) => (proc val (lambda (x) E[x])) ; where E has no %
When handler-expr is omitted, % is the same as prompt. If prompt-tag is provided, % uses specific prompt tags like prompt-at.
(prompt val) => val (prompt E[(control k expr)]) => (prompt ((lambda (k) expr) (lambda (v) E[v]))) ; where E has no prompt
> (prompt (+ 2 (control k (k 5)))) 7
> (prompt (+ 2 (control k 5))) 5
> (prompt (+ 2 (control k (+ 1 (control k1 (k1 6)))))) 7
> (prompt (+ 2 (control k (+ 1 (control k1 (k 6)))))) 8
> (prompt (+ 2 (control k (control k1 (control k2 (k2 6)))))) 6
syntax
(prompt-at prompt-tag-expr expr ...+)
syntax
(control-at prompt-tag-expr id expr ...+)
(prompt-at tag val) => val (prompt-at tag E[(control-at tag k expr)]) => (prompt-at tag ((lambda (k) expr) (lambda (v) E[v]))) ; where E has no prompt-at for tag
The essential reduction rules are:
(reset val) => val (reset E[(shift k expr)]) => (reset ((lambda (k) expr) (lambda (v) (reset E[v])))) ; where E has no reset
The reset and prompt forms are interchangeable.
syntax
(prompt0 expr ...+)
syntax
(reset0 expr ...+)
syntax
(control0 id expr ...+)
syntax
(shift0 id expr ...+)
The essential reduction rules are:
(prompt0 val) => val (prompt0 E[(control0 k expr)]) => ((lambda (k) expr) (lambda (v) E[v])) (reset0 val) => val (reset0 E[(shift0 k expr)]) => ((lambda (k) expr) (lambda (v) (reset0 E[v])))
The reset0 and prompt0 forms are interchangeable. Furthermore, the following reductions apply:
(prompt E[(control0 k expr)]) => (prompt ((lambda (k) expr) (lambda (v) E[v]))) (reset E[(shift0 k expr)]) => (reset ((lambda (k) expr) (lambda (v) (reset0 E[v])))) (prompt0 E[(control k expr)]) => (prompt0 ((lambda (k) expr) (lambda (v) E[v]))) (reset0 E[(shift k expr)]) => (reset0 ((lambda (k) expr) (lambda (v) (reset E[v]))))
That is, both the prompt/reset and control/shift sites must agree for 0-like behavior, otherwise the non-0 behavior applies.
syntax
(prompt0-at prompt-tag-expr expr ...+)
syntax
(reset0-at prompt-tag-expr expr ...+)
syntax
(control0-at prompt-tag-expr id expr ...+)
syntax
(shift0-at prompt-tag-expr id expr ...+)
The essential reduction rules are:
(prompt-at tag obj) => obj (spawn proc) => (prompt tag (proc (lambda (x) (abort tag x)))) (prompt-at tag E[(abort tag proc)]) => (proc (lambda (x) (prompt-at tag E[x]))) ; where E has no prompt-at for tag
(splitter proc) => (prompt-at tag (proc (lambda (thunk) (abort tag thunk)) (lambda (proc) (control0-at tag k (proc k))))) (prompt-at tag E[(abort tag thunk)]) => (thunk) ; where E has no prompt-at for tag (prompt-at tag E[(control0-at tag k expr)]) => ((lambda (k) expr) (lambda (x) E[x])) ; where E has no prompt-at for tag
procedure
(new-prompt) → any
syntax
(set prompt-expr expr ...+)
syntax
(cupto prompt-expr id expr ...+)
In this library, new-prompt is an alias for make-continuation-prompt-tag, set is an alias for prompt0-at, and cupto is an alias for control0-at.