Leaving a Computation Early

Leave nested calls once with call/cc and follow cleanup with dynamic-wind.

Use call/cc to leave nested calls and return a result to an enclosing call. Use dynamic-wind to run cleanup when that exit occurs. The exit point that call/cc passes to your procedure is called a continuation.

Run each complete example in the Playground. For an ordinary choice, use if or cond. For a helper that finishes normally, return its result.

Exit before the next expression

LISPEX
(call/cc (lambda (escape) (escape 'done) 'unreachable))

Result

OUTPUT
done

Follow the call in three steps.

  1. call/cc calls the lambda with an escape procedure. The parameter escape names that procedure.
  2. (escape 'done) stops the lambda's remaining work and makes the enclosing call/cc return done.
  3. The expression 'unreachable is skipped. Evaluation continues after call/cc.

A continuation is the work remaining after a computation finishes. Here, escape represents the continuation of the enclosing call/cc. Calling it leaves the calls inside that call/cc. This is an upward escape.

Lispex allows this escape once, while its enclosing call/cc is still active. The escape procedure becomes used before cleanup starts. A later call to it reports E340, even if cleanup is still running.

Run entry, work, and cleanup

LISPEX
(dynamic-wind
  (lambda () (display "before "))
  (lambda () (display "during "))
  (lambda () (display "after")))

Output

OUTPUT
before during after

dynamic-wind takes three procedures with no arguments.

ProcedureWhen it runs
beforeOn entry
thunkAfter before, to perform the main work
afterAfter the main work, including an error or escape out of it

A thunk is a procedure called with no arguments. On normal completion, dynamic-wind returns the main procedure's values. It discards the values returned by before and after.

Follow cleanup during an escape

LISPEX
(write
  (call/cc
    (lambda (escape)
      (dynamic-wind
        (lambda () (display "before "))
        (lambda ()
          (display "during ")
          (escape 'done)
          (display "unreachable "))
        (lambda () (display "after "))))))
(newline)

Output

OUTPUT
before during after done

The output shows the order.

  1. before prints before .
  2. The main procedure prints during and calls escape.
  3. Its remaining display is skipped. The after procedure prints after .
  4. call/cc returns done. The surrounding write prints it, and newline ends the line.

Put cleanup in after, where it also runs on an error or escape from the main procedure. If several dynamic-wind calls are exited, their cleanup procedures run from innermost to outermost.

If cleanup raises a new error or calls another active escape, the new error or escape replaces the one being handled. Calling the already-used escape again from cleanup instead produces E340.

Use an escape only while its call is active

LISPEX
(let ((saved #f))
  (call/cc (lambda (escape) (set! saved escape) 'ready))
  (saved 'late))

Diagnostic

OUTPUT
E340 — escape continuation is no longer active

Here, call/cc returns ready normally. That non-final result is discarded. Calling saved then fails because its enclosing call/cc has finished, even though the escape has never been used.

The same rule prevents re-entering a completed computation. before runs on entry and is not run again by an escape. To repeat work, use a loop or another ordinary procedure call.

Lispex passes escape and error signals through its evaluator to the enclosing call. These signals are separate from exceptions and continuations in the calling application's language.

See conditionals for local choices and diagnostics for reading errors.