Delimited control is a computational effect that allows capture of delimited (or partial) continuations,
which contrasts with operations like call/cc
that capture undelimited continuations,
meaning the whole context that surrounds the operation.
The continuation is the rest of the computation, represented by the context of the current expression being evaluated.
e.g. consider an evaluation process of the following program: (2 + f(3)) + 1
.
- To compute (2 + f(3)) + 1, we need to compute 2 + f(3) first,
- To compute 2 + f(3) , we need to compute f(3) first,
- Once we evaluate
f(3)
, we can continue with the rest of the computation that is (2 +) and then (+ 1), - so the continuation can be written as
(2 + []) + 1
that is a program with one hole[]
called a context.
Instead of a single operation like call/cc
, delimited control is usually introduced using two operators, for example shift/reset
:
shift
used to capture the (delimited) context; written as a binderS f. e
reset
used as a delimiter that restricts the part of the continuation to be captured; written with brackets< e >
e ::= x | e e | λ x. e | S x. e | < e >
K ::= [] | K e | v K
T ::= [] | < K[T] >
(λ x. e) v ~> e [x := v]
< v > ~> v
< K [S f. e] > ~> < e [ f := λ x. < K[x] > ] >
e ~> e'
-------------------- (eval)
K[T[e]] --> K[T[e']]
The 3rd contraction (~>) rule describes the semantic interaction between the two operators
where the context K
delimited by the inner-most reset
gets captured and bound to f
as a lambda.
Note that:
- the evaluation context
K
does not cross thereset
boundary, meaning we won't go under a delimiter (reset
) when plugging a term, - whereas the context
K[T]
is an evaluation context that may cross thereset
boundary, and it's used in the single (eval) rule that defines the evaluation.
Consider the following program (assuming we've extended the calculus with numbers and (+))
1 + < 2 + (S f. 3 + f (f 4)) >
Which after one (eval) step is:
1 + < 3 + f (f 4) >
where f = λ x. < 2 + x >
And finally computes to an equivalent of 1 + 3 + 2 + 2 + 4
Notice that reset
(<>) occurs twice in the result of the 3rd contraction rule:
- outer
reset
keeps the outer context from being captured during evaluation ("reset makes any piece of code appear pure to the outside, that is, devoid of control effects") - inner
reset
makes the captured continuation pure
There are variations that drop either one of these delimiters:
- The
control
operator; written as a binderC f. e
; drops the inner delimiter - and two other operators
shift0
(S₀ f. e) andcontrol0
(C₀ f. e) that also drop the outer delimiter
< K [S f. e] > ~> < e [ f := λ x. < K[x] > ] >
< K [C f. e] > ~> < e [ f := λ x. K[x] ] >
< K [S₀ f. e] > ~> e [ f := λ x. < K[x] > ]
< K [C₀ f. e] > ~> e [ f := λ x. K[x] ]
These variations are usually studied separately and in literature are referred to as:
shift/reset
,control/prompt
andshift0/reset0
,control0/prompt0
< let x = S f. 1 + f 2 in S g. x > --> < 1 + (λ x. < S g. x >) 2 > --> < 1 + < S g. 2 > > --> < 1 + < 2 > > -->* 3
< let x = C f. 1 + f 2 in C g. x > --> < 1 + (λ x. C g. x ) 2 > --> < 1 + C g. 2 > --> < 2 > -->* 2
TODO