;; ========================================================================== ;; callcc.sx — Full first-class continuations (call/cc) ;; ;; OPTIONAL EXTENSION — not required by the core evaluator. ;; Bootstrappers include this only when the target supports it naturally. ;; ;; Full call/cc (call-with-current-continuation) captures the ENTIRE ;; remaining computation as a first-class function — not just up to a ;; delimiter, but all the way to the top level. Invoking a continuation ;; captured by call/cc abandons the current computation entirely and ;; resumes from where the continuation was captured. ;; ;; This is strictly more powerful than delimited continuations (shift/reset) ;; but harder to implement in targets that don't support it natively. ;; Recommended only for targets where it's natural: ;; - Scheme/Racket (native call/cc) ;; - Haskell (ContT monad transformer) ;; ;; For targets like Python, JavaScript, and Rust, delimited continuations ;; (continuations.sx) are more practical and cover the same use cases ;; without requiring a global CPS transform. ;; ;; One new special form: ;; (call/cc f) — call f with the current continuation ;; ;; One new type: ;; continuation — same type as in continuations.sx ;; ;; If both extensions are loaded, the continuation type is shared. ;; Delimited and undelimited continuations are the same type — ;; the difference is in how they are captured, not what they are. ;; ;; Platform requirements: ;; (make-continuation fn) — wrap a function as a continuation value ;; (continuation? x) — type predicate ;; (type-of continuation) → "continuation" ;; (call-with-cc f env) — target-specific call/cc implementation ;; ========================================================================== ;; -------------------------------------------------------------------------- ;; 1. Semantics ;; -------------------------------------------------------------------------- ;; ;; (call/cc f) ;; ;; Evaluates f (which must be a function of one argument), passing it the ;; current continuation as a continuation value. f can: ;; ;; a) Return normally — call/cc returns whatever f returns ;; b) Invoke the continuation — abandons f's computation, call/cc ;; "returns" the value passed to the continuation ;; c) Store the continuation — invoke it later, possibly multiple times ;; ;; Key difference from shift/reset: invoking an undelimited continuation ;; NEVER RETURNS to the caller. It abandons the current computation and ;; jumps back to where call/cc was originally called. ;; ;; ;; Delimited (shift/reset) — k returns a value: ;; (reset (+ 1 (shift k (+ (k 10) (k 20))))) ;; ;; (k 10) → 11, returns to the (+ ... (k 20)) expression ;; ;; (k 20) → 21, returns to the (+ 11 ...) expression ;; ;; result: 32 ;; ;; ;; Undelimited (call/cc) — k does NOT return: ;; (+ 1 (call/cc (fn (k) ;; (+ (k 10) (k 20))))) ;; ;; (k 10) abandons (+ (k 10) (k 20)) entirely ;; ;; jumps back to (+ 1 _) with 10 ;; ;; result: 11 ;; ;; (k 20) is never reached ;; ;; -------------------------------------------------------------------------- ;; -------------------------------------------------------------------------- ;; 2. call/cc — call with current continuation ;; -------------------------------------------------------------------------- (define sf-callcc (fn (args env) ;; Single argument: a function to call with the current continuation. (let ((f-expr (first args)) (f (trampoline (eval-expr f-expr env)))) (call-with-cc f env)))) ;; -------------------------------------------------------------------------- ;; 3. Derived forms ;; -------------------------------------------------------------------------- ;; ;; With call/cc available, several patterns become expressible: ;; ;; --- Early return --- ;; ;; (define find-first ;; (fn (pred items) ;; (call/cc (fn (return) ;; (for-each (fn (item) ;; (when (pred item) ;; (return item))) ;; items) ;; nil)))) ;; ;; --- Exception-like flow --- ;; ;; (define try-catch ;; (fn (body handler) ;; (call/cc (fn (throw) ;; (body throw))))) ;; ;; (try-catch ;; (fn (throw) ;; (let ((result (dangerous-operation))) ;; (when (not result) (throw "failed")) ;; result)) ;; (fn (error) (str "Caught: " error))) ;; ;; --- Coroutines --- ;; ;; Two call/cc captures that alternate control between two ;; computations. Each captures its own continuation, then invokes ;; the other's. This gives cooperative multitasking without threads. ;; ;; --- Undo --- ;; ;; (define with-undo ;; (fn (action) ;; (call/cc (fn (restore) ;; (action) ;; restore)))) ;; ;; ;; (let ((undo (with-undo (fn () (delete-item 42))))) ;; ;; (undo "anything")) → item 42 is back ;; ;; -------------------------------------------------------------------------- ;; -------------------------------------------------------------------------- ;; 4. Interaction with delimited continuations ;; -------------------------------------------------------------------------- ;; ;; If both callcc.sx and continuations.sx are loaded: ;; ;; - The continuation type is shared. (continuation? k) returns true ;; for both delimited and undelimited continuations. ;; ;; - shift inside a call/cc body captures up to the nearest reset, ;; not up to the call/cc. The two mechanisms compose. ;; ;; - call/cc inside a reset body captures the entire continuation ;; (past the reset). This is the expected behavior — call/cc is ;; undelimited by definition. ;; ;; - A delimited continuation (from shift) returns a value when invoked. ;; An undelimited continuation (from call/cc) does not return. ;; Both are callable with the same syntax: (k value). ;; The caller cannot distinguish them by type — only by behavior. ;; ;; -------------------------------------------------------------------------- ;; -------------------------------------------------------------------------- ;; 5. Interaction with I/O and state ;; -------------------------------------------------------------------------- ;; ;; Full call/cc has well-known interactions with side effects: ;; ;; Re-entry: ;; Invoking a saved continuation re-enters a completed computation. ;; If that computation mutated state (set!, I/O writes), the mutations ;; are NOT undone. The continuation resumes in the current state, ;; not the state at the time of capture. ;; ;; I/O: ;; Same as delimited continuations — I/O executes at invocation time. ;; A continuation containing (current-user) will call current-user ;; when invoked, in whatever request context exists then. ;; ;; Dynamic extent: ;; call/cc captures the continuation, not the dynamic environment. ;; Host-language context (Python's Quart request context, JavaScript's ;; async context) may not be valid when a saved continuation is invoked ;; later. Typed targets can enforce this; dynamic targets fail at runtime. ;; ;; Recommendation: ;; Use call/cc for pure control flow (early return, coroutines, ;; backtracking). Use delimited continuations for effectful patterns ;; (suspense, cooperative scheduling) where the delimiter provides ;; a natural boundary. ;; ;; -------------------------------------------------------------------------- ;; -------------------------------------------------------------------------- ;; 6. Implementation notes per target ;; -------------------------------------------------------------------------- ;; ;; Scheme / Racket: ;; Native call/cc. Zero implementation effort. ;; ;; Haskell: ;; ContT monad transformer. The evaluator runs in ContT, and call/cc ;; is callCC from Control.Monad.Cont. Natural and type-safe. ;; ;; Python: ;; Requires full CPS transform of the evaluator, or greenlet-based ;; stack capture. Significantly more invasive than delimited ;; continuations. NOT RECOMMENDED — use continuations.sx instead. ;; ;; JavaScript: ;; Requires full CPS transform. Cannot be implemented with generators ;; alone (generators only support delimited yield, not full escape). ;; NOT RECOMMENDED — use continuations.sx instead. ;; ;; Rust: ;; Full CPS transform at compile time. Possible but adds significant ;; complexity. Delimited continuations are more natural (enum-based). ;; Consider only if the target genuinely needs undelimited escape. ;; ;; -------------------------------------------------------------------------- ;; -------------------------------------------------------------------------- ;; 7. Platform interface — what each target must provide ;; -------------------------------------------------------------------------- ;; ;; (call-with-cc f env) ;; Call f with the current continuation. f is a function of one ;; argument (the continuation). If f returns normally, call-with-cc ;; returns f's result. If f invokes the continuation, the computation ;; jumps to the call-with-cc call site with the provided value. ;; ;; (make-continuation fn) ;; Wrap a native function as a continuation value. ;; (Shared with continuations.sx if both are loaded.) ;; ;; (continuation? x) ;; Type predicate. ;; (Shared with continuations.sx if both are loaded.) ;; ;; Continuations must be callable via the standard function-call ;; dispatch in eval-list (same path as lambda calls). ;; ;; --------------------------------------------------------------------------