Skip to content
Lesson 12 of 13

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.

Up nextA Robust REPLAdvanced Topics

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…