Delimited continuation
Encyclopedia
In programming language
s, a delimited continuation, composable continuation or partial continuation, is a "slice" of a continuation
frame that has been reified
into a function. Unlike regular continuations, delimited continuations return
a value, and thus may be reused and composed
.
One proposal offers two operators,
(reset (* 2 (shift k CODE)))
whenever
This is equivalent to the following:
(let ((k (lambda (x) (* 2 x)))) CODE)
Furthermore, once the entire computation within
(reset (* 2 (shift k (k (k 4)))))
invokes
Everything that happens outside the
(+ 1 (reset (* 2 (shift k (k (k 4))))))
Delimited continuations were first described independently by Felleisen et al. and Johnson. They have since been used in a large number of domains, particularly in defining new control operators; see Queinnec for a survey.
Let's take a look at a more complicated example.
(reset
(begin
(shift k (cons 1 (k (void)))) ;; (1)
null))
Since the context captured by
Making this example more complicated, add a line:
(reset
(begin
(shift k (cons 1 (k (void))))
(shift k (cons 2 (k (void))))
null))
If we comment out the first
(reset
(begin
(shift k (cons 1 (k (void))))
(list 2)))
This is pretty familiar, and can be rewritten as
We can define
(define (yield x) (shift k (cons x (k (void)))))
and use it in building lists:
(reset (begin
(yield 1)
(yield 2)
(yield 3)
null)) ;; (list 1 2 3)
If we replace
(define (stream-yield x) (shift k (stream-cons x (k (void)))))
(define lazy-example
(reset (begin
(stream-yield 1)
(stream-yield 2)
(stream-yield 3)
stream-null)))
We can generalize this and convert lists to stream, in one fell swoop:
(define (list->stream xs)
(reset (begin
(for-each stream-yield xs)
stream-null)))
In a more complicated example below the continuation can be safely wrapped into a body of a lambda, and be used as such:
(define (for-each->stream-maker for-each)
(stream-lambda (collection)
(reset (begin
(for-each (lambda (element)
(shift k
(stream-cons element (k 'ignored))))
collection)
stream-null))))
The part between
Delimited continuations are also useful in linguistics
: see Continuations in linguistics for details.
Programming language
A programming language is an artificial language designed to communicate instructions to a machine, particularly a computer. Programming languages can be used to create programs that control the behavior of a machine and/or to express algorithms precisely....
s, a delimited continuation, composable continuation or partial continuation, is a "slice" of a continuation
Continuation
In computer science and programming, a continuation is an abstract representation of the control state of a computer program. A continuation reifies the program control state, i.e...
frame that has been reified
Reification (computer science)
Reification is the process by which an abstract idea about a computer program is turned into an explicit data model or other object created in a programming language. A computable/addressable object — a resource — is created in a system as a proxy for a non computable/addressable object...
into a function. Unlike regular continuations, delimited continuations return
Return statement
In computer programming, a return statement causes execution to leave the current subroutine and resume at the point in the code immediately after where the subroutine was called, known as its return address. The return address is saved, usually on the process's call stack, as part of the operation...
a value, and thus may be reused and composed
Function composition (computer science)
In computer science, function composition is an act or mechanism to combine simple functions to build more complicated ones...
.
Examples
Various operators for delimited continuations have been proposed in the research literature.One proposal offers two operators,
shift
and reset
, to work with such continuations.reset
sets the limit for the continuation. shift
captures the current continuation up to the innermost enclosing reset
limit, takes a function argument and passes the captured continuation to it. If the delimited continuation is invoked with parameter p
, the computation is suspended and p
is returned by shift
. However, if the function passed to shift
returns normally (without invoking the delimited continuation), then its value becomes the return value of reset
. When the entire computation within reset
is completed, the result is returned by the delimited continuation. For example, in this Scheme code:(reset (* 2 (shift k CODE)))
whenever
CODE
invokes (k N)
, (* 2 N)
is evaluated and returned.This is equivalent to the following:
(let ((k (lambda (x) (* 2 x)))) CODE)
Furthermore, once the entire computation within
shift
is completed, the continuation is discarded, and execution restarts outside reset
. Therefore,(reset (* 2 (shift k (k (k 4)))))
invokes
(k 4)
first (which returns 8), and then (k 8)
(which returns 16). At this point, the shift
expression has terminated, and the rest of the reset
expression is discarded. Therefore, the final result is 16.Everything that happens outside the
reset
expression is hidden, i.e. not influenced by the control transfer. For example, this returns 17:(+ 1 (reset (* 2 (shift k (k (k 4))))))
Delimited continuations were first described independently by Felleisen et al. and Johnson. They have since been used in a large number of domains, particularly in defining new control operators; see Queinnec for a survey.
Let's take a look at a more complicated example.
(reset
(begin
(shift k (cons 1 (k (void)))) ;; (1)
null))
Since the context captured by
shift
consists of (begin [*] null)
(where [*]
is the hole for parameter injection), the first call of k
inside shift
evaluates to null
, and the body of shift determines the value of the expression, we get (1)
as a result.Making this example more complicated, add a line:
(reset
(begin
(shift k (cons 1 (k (void))))
(shift k (cons 2 (k (void))))
null))
If we comment out the first
shift
, we already know the result, it is (2)
; so we can as well rewrite the expression like this:(reset
(begin
(shift k (cons 1 (k (void))))
(list 2)))
This is pretty familiar, and can be rewritten as
(cons 1 (list 2))
, that is, (list 1 2)
.We can define
yield
using this trick:(define (yield x) (shift k (cons x (k (void)))))
and use it in building lists:
(reset (begin
(yield 1)
(yield 2)
(yield 3)
null)) ;; (list 1 2 3)
If we replace
cons
with stream-cons
, we can build lazy streams:(define (stream-yield x) (shift k (stream-cons x (k (void)))))
(define lazy-example
(reset (begin
(stream-yield 1)
(stream-yield 2)
(stream-yield 3)
stream-null)))
We can generalize this and convert lists to stream, in one fell swoop:
(define (list->stream xs)
(reset (begin
(for-each stream-yield xs)
stream-null)))
In a more complicated example below the continuation can be safely wrapped into a body of a lambda, and be used as such:
(define (for-each->stream-maker for-each)
(stream-lambda (collection)
(reset (begin
(for-each (lambda (element)
(shift k
(stream-cons element (k 'ignored))))
collection)
stream-null))))
The part between
reset
and shift
includes control functions like lambda
and for-each
; this is impossible to rephrase using lambdas.Delimited continuations are also useful in linguistics
Linguistics
Linguistics is the scientific study of human language. Linguistics can be broadly broken into three categories or subfields of study: language form, language meaning, and language in context....
: see Continuations in linguistics for details.
External links
- Composable continuations tutorial at SchemeWiki
- Delimited continuations in operating systems, by Oleg Kiselyov and Chung-chieh Shan
- Native delimited continuations in (byte-code and native-code) OCaml
- Shift/reset для самых маленьких (Russian)
- Some nice paperz on delimited continuations and first-class macros