title: Delimited continuations date: 2018-12--2 00:00 tags: programming languages, scheme --- A continuation can be understood as a context that represents the “rest of the program”. Notice that formulated in this way, continuation is a meta-level abstraction. Ability to manipulate such continuations as functions is obtained using *control operators*. The Scheme programming language has traditionally been a ripe field for research on control operators such as `(call/cc)`. Call-with-current-continuation is an example of an unbounded control operator. A different and a more general class of control operators are *delimited control operators*. The basic idea is that rather to capture the whole rest of the program as a continuation, delimited control operators capture delimited continuations, i.e. some part of the program with a hole. In order to run the examples from this document in [DrRacket](http://racket-lang.org) make sure to put `(require racket/control)` in your file. ## Prompt/control One of the first (and simplest) delimited control operations is prompt/control. `prompt` delimits the continuation, and `control` captures the current continuation (up to the innermost `prompt`). (prompt (+ 1 (control k (k 3)))) The code is evaluated as follows: (prompt (+ 1 (control k (k 3)))) => (prompt ((λ (k) (k 3)) (λ (x) (+ 1 x)))) => (prompt ((λ (x) (+ 1 x)) 3)) => (prompt (+ 1 3)) => (prompt 4) => 4 Here `(λ (x) (+ 1 x))` represents the delimited continuation (the program inside `prompt` without the `control` part) on an object level. When `(control k body)` is evaluated inside a `prompt`, it gets replaced with `(λ (k) body)` (capturing `k` inside `body`) and applied to the continuation. In terms of reduction semantics one would have the [following reduction rules](https://docs.racket-lang.org/reference/cont.html#%28form._%28%28lib._racket%2Fcontrol..rkt%29._prompt%29%29): (prompt v) -> v (prompt E[(control k body)]) -> (prompt ((λ (k) body) (λ (x) E[x]))) where the evaluation context E does not contain `prompt` and `x` is a free variable in E. In particular, if continuation is not invoked in `body`, it is just discarded, as in the following example: (prompt (+ 1 (control k 3))) A captured delimited continuation can be invoked multiple times: (prompt (+ 1 (control k (let ([x (k 1)] [y (k 2)]) (k (* x y)))))) In this case the continuation `k = (λ (x) (+ 1 x))`, so when it is invoked in the body of `control`, x and y get set to 2 and 3, respectively. Hence, the final result of that program is 1+2\*3=7. Nested continuations (prompt (+ 2 (control k (+ 1 (control k1 (k1 6)))))) => (prompt ((λ (x) (+ 2 (control k (+ 1 x)))) 6)) => (prompt (+ 2 (control k (+ 1 6)))) => (prompt (+ 2 (control k 7))) => (prompt 7) => 7 ## While loops with breaks We can utilize the prompt/control operators to implement a simple macro for a while loop with an ability to break out of it, sort of similar to the while/break statements in C. (define-syntax-rule (break) (control k '())) (define-syntax-rule (while cond body ...) (prompt (let loop () (when cond body ... (loop))))) We define the `(while)` construct as a simple recursive loop with just one caveat – we wrap the whole thing in a `prompt`. Then `(break)`, when evaluated, just discards the whole continuation with is bound to the `(while)` loop. We can test this macro by writing a procedure that multiplies all the elements in the list. (define (multl lst) (define i 0) (define fin (length lst)) (define res 1) (while (< i fin) (let ([val (list-ref lst i)]) (printf "The value of lst[~a] is ~a\n" i val) (set! res (* res val)) (when (= val 0) (begin (set! res 0) (break)))) (set! i (+ i 1))) res) By running this program we can observe that it finishes early whenever it encounters a zero in the list: > (multl '(1 2 3)) The value of lst[0] is 1 The value of lst[1] is 2 The value of lst[2] is 3 6 > (multl '(1 2 0 3 4 5)) The value of lst[0] is 1 The value of lst[1] is 2 The value of lst[2] is 0 0 > *The situation with `break` in C is slightly different; as it has been pointed out to me, break is a *statement*, whereas in our toy example `break` is an expression. Due to the dichotomy of statements and expressions in C this example is not very faithful to C semantics.* ### Prompt tags The `(while)` macro that we have is actually buggy. The problem arises when the body of the while loop contains an additional prompt delimiter: (while (< i 3) (writeln "Hi") (prompt (break)) (set! i (+ i 1))) When running this example “Hi” is written to the standard output three time. Oops. To circumvent this problem we can use the [tagged prompt-at/control-at operators](https://docs.racket-lang.org/reference/cont.html#%28form._%28%28lib._racket%2Fcontrol..rkt%29._prompt-at%29%29) with the following reduction semantics: (prompt-at tag v) -> v (prompt-at tag E[(control-at tag' k body)]) -> (prompt-at tag ((λ (k) body) (λ (x) E[x]))) where `tag = tag'` and `E` does not contain `prompt-at tag`. So the only difference between prompt-at/control-at and prompt/control is the presence of [prompt tags](https://docs.racket-lang.org/reference/cont.html#%28def._%28%28quote._~23~25kernel%29._continuation-prompt-tag~3f%29%29) which allows for a more fine-grained matching between delimiters and control operators. We can sort of fix our while macro by creating a special tag (define while-tag (make-continuation-prompt-tag 'tagje)) (define-syntax-rule (break) (control-at while-tag k '())) (define-syntax-rule (while cond body ...) (prompt-at while-tag (let loop () (when cond body ... (loop))))) The following program then prints “Hi” only once. (define i 0) (while (< i 3) (writeln "Hi") (prompt (break)) (set! i (+ i 1))) ## Reset/shift The other pair of delimited control operators are `shift` and `reset`. [The reductions](https://docs.racket-lang.org/reference/cont.html#%28form._%28%28lib._racket%2Fcontrol..rkt%29._reset%29%29) for `reset/shift` are as follows. (reset v) -> v (reset E[(shift k body)]) -> (reset ((λ (k) body) (λ (x) (reset E[x])))) where the evaluation context E does not contain `reset` and `x` is a free variable in E. Contrast this with the reduction rules for `prompt/control` (prompt v) -> v (prompt E[(control k body)]) -> (prompt ((λ (k) body) (λ (x) E[x]))) As you can notice, the difference between the `prompt/control` reductions is that in the case when the continuation is captured, it is wrapped in an additional `reset`. Thus, any invocation of a bound delimited continuation `k` cannot escape to the outer scope. To observe the practical difference, consider the following example. (define (skip) '()) (define (bye) (println "Capturing and discarding the continuation...") 42) (prompt (let ([act (control k (begin (k skip) (k (λ () (control _ (bye)))) (k skip)))]) (act) (println "Doing stuff"))) If we were to remove the second line `(k (λ () (control _ (bye))))` in the begin block, then this program would print “Doing stuff” twice (as invoking `(k skip)` binds the dummy function `skip` to `act` and executes `(begin (act) (println "Doing stuff"))`). With such a line present, during the invocation of `k`, `act` gets bound to `(λ () (control _ (bye)))`. Therefore, when `act` is evaluated the continuation is of the form `(prompt E[(control _ bye)])`, which just reduces to `(prompt (bye))` and `(prompt 42)`. So, the output of the program above is "Doing stuff" "Capturing and discarding the continuation..." 42 If we replace `prompt` with `reset` and `control` with `shift`, as in the code snippet below, then every invocation of `k` wraps the continuation in another `reset`. (reset (let ([act (shift k (begin (k skip) (k (λ () (shift _ (bye)))) (k skip)))]) (act) (println "Doing stuff"))) After some reductions, the terms is (reset ((λ (k) (begin (k skip) (k (λ () (shift _ (bye)))) (K skip))) (λ (x) (reset (begin (x) (println "Doing stuff")))))) As you can see, the second invocation of `k` results in `(reset (begin (shift _ (bye)) (println "Doing stuff")))`, and thus 1. “Doing stuff” does not get printed (as this part of the term gets discarded. 2. The shift operator discards the *inner* continuation, not the outer continuation. In particular, that means that the call to `(k (λ () (shift _ (bye))))` returns! The output of the snippet is thus "Doing stuff" "Capturing and discarding the continuation..." "Doing stuff" ## Comments and further reading See references at . Some interesting papers: - [Shift to control](http://homes.soic.indiana.edu/ccshan/recur/recur.pdf) by C.C. Shan shows how to express shift/reset and control/prompt in terms of each other. - [On the Dynamic Extent of Delimited Continuations](http://www.brics.dk/RS/05/2/BRICS-RS-05-2.pdf) by Dariusz Biernacki and Olivier Danvy present the difference between shift and control using breadth-first traversal as an example. It is also explained in which sense `shift` is a static delimited control operator and `control` is a dynamic delimited control operator. - A bibliography of [Continuations and Continuation Passing Style](http://library.readscheme.org/page6.html) at the readscheme.org