Step 1 of 5 · Reading · ~1 min
Read
Advanced Topics
Continuations and call/cc
A continuation is "the rest of the computation". When you evaluate (+ 1 (* 2 3)), the continuation around (* 2 3) is "take this value, add 1, return".
Most languages keep continuations implicit on the call stack. Scheme makes them first-class values via call-with-current-continuation (call/cc):
(+ 1 (call/cc (lambda (k) (k 10)))) -> 11
k is "what would happen after call/cc returns". Calling k with 10 makes call/cc return 10. The + 1 part runs as the continuation of call/cc, giving 11.
Escape
The simplest use: bail out of a deep computation.
(define (find-zero lst k)
(if (null? lst) -1
(if (= (car lst) 0)
(k "found") ; jump straight out
(find-zero (cdr lst) k))))
(call/cc (lambda (k) (find-zero (list 3 4 0 5) k))) -> "found"
That's try/throw, implemented as a library.
What else they enable
Continuations are the most powerful control construct in any language:
- Exceptions — try/catch is
(call/cc (lambda (k) ... (k err))) - Generators / coroutines — pause and resume
- Backtracking — Prolog-style search
- Web sessions — the Seaside framework runs server-side continuations
Why most languages omit them
Cheap continuations require the runtime to capture stack snapshots — most VMs do not support this. JS got async/await (a limited form). Python got generators. C has setjmp/longjmp (a poor cousin). Only Scheme, Standard ML/NJ, Ruby (callcc), and a few others give you the full version.
Implementation note
In a Python interpreter we cheat: we use a Python exception to unwind the stack and resume at the call/cc call site. Real Scheme implementations use CPS-transformed code or stack-copying.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…