;; ========================================================================== ;; continuations.sx — Delimited continuations (shift/reset) ;; ;; OPTIONAL EXTENSION — not required by the core evaluator. ;; Bootstrappers include this only when the target requests it. ;; ;; Delimited continuations capture "the rest of the computation up to ;; a delimiter." They are strictly less powerful than full call/cc but ;; cover the practical use cases: suspendable rendering, cooperative ;; scheduling, linear async flows, wizard forms, and undo. ;; ;; Two new special forms: ;; (reset body) — establish a delimiter ;; (shift k body) — capture the continuation to the nearest reset ;; ;; One new type: ;; continuation — a captured delimited continuation, callable ;; ;; The captured continuation is a function of one argument. Invoking it ;; provides the value that the shift expression "returns" within the ;; delimited context, then completes the rest of the reset body. ;; ;; Continuations are composable — invoking a continuation returns a ;; value (the result of the reset body), which can be used normally. ;; This is the key difference from undelimited call/cc, where invoking ;; a continuation never returns. ;; ;; Platform requirements: ;; (make-continuation fn) — wrap a function as a continuation value ;; (continuation? x) — type predicate ;; (type-of continuation) → "continuation" ;; Continuations are callable (same dispatch as lambda). ;; ========================================================================== ;; -------------------------------------------------------------------------- ;; 1. Type ;; -------------------------------------------------------------------------- ;; ;; A continuation is a callable value of one argument. ;; ;; (continuation? k) → true if k is a captured continuation ;; (type-of k) → "continuation" ;; (k value) → invoke: resume the captured computation with value ;; ;; Continuations are first-class: they can be stored in variables, passed ;; as arguments, returned from functions, and put in data structures. ;; ;; Invoking a delimited continuation RETURNS a value — the result of the ;; reset body. This makes them composable: ;; ;; (+ 1 (reset (+ 10 (shift k (k 5))))) ;; ;; k is "add 10 to _ and return from reset" ;; ;; (k 5) → 15, which is returned from reset ;; ;; (+ 1 15) → 16 ;; ;; -------------------------------------------------------------------------- ;; -------------------------------------------------------------------------- ;; 2. reset — establish a continuation delimiter ;; -------------------------------------------------------------------------- ;; ;; (reset body) ;; ;; Evaluates body in the current environment. If no shift occurs during ;; evaluation of body, reset simply returns the value of body. ;; ;; If shift occurs, reset is the boundary — the continuation captured by ;; shift extends from the shift point back to (and including) this reset. ;; ;; reset is the "prompt" — it marks where the continuation stops. ;; ;; Semantics: ;; (reset expr) where expr contains no shift ;; → (eval expr env) ;; just evaluates normally ;; ;; (reset ... (shift k body) ...) ;; → captures continuation, evaluates shift's body ;; → the result of the shift body is the result of the reset ;; ;; -------------------------------------------------------------------------- (define sf-reset (fn ((args :as list) (env :as dict)) ;; Single argument: the body expression. ;; Install a continuation delimiter, then evaluate body. ;; The implementation is target-specific: ;; - In Scheme: native reset/shift ;; - In Haskell: Control.Monad.CC or delimited continuations library ;; - In Python: coroutine/generator-based (see implementation notes) ;; - In JavaScript: generator-based or CPS transform ;; - In Rust: CPS transform at compile time (let ((body (first args))) (eval-with-delimiter body env)))) ;; -------------------------------------------------------------------------- ;; 3. shift — capture the continuation to the nearest reset ;; -------------------------------------------------------------------------- ;; ;; (shift k body) ;; ;; Captures the continuation from this point back to the nearest enclosing ;; reset and binds it to k. Then evaluates body in the current environment ;; extended with k. The result of body becomes the result of the enclosing ;; reset. ;; ;; k is a function of one argument. Calling (k value) resumes the captured ;; computation with value standing in for the shift expression. ;; ;; The continuation k is composable: (k value) returns a value (the result ;; of the reset body when resumed with value). This means k can be called ;; multiple times, and its result can be used in further computation. ;; ;; Examples: ;; ;; ;; Basic: shift provides a value to the surrounding computation ;; (reset (+ 1 (shift k (k 41)))) ;; ;; k = "add 1 to _", (k 41) → 42, reset returns 42 ;; ;; ;; Abort: shift can discard the continuation entirely ;; (reset (+ 1 (shift k "aborted"))) ;; ;; k is never called, reset returns "aborted" ;; ;; ;; Multiple invocations: k can be called more than once ;; (reset (+ 1 (shift k (list (k 10) (k 20))))) ;; ;; (k 10) → 11, (k 20) → 21, reset returns (11 21) ;; ;; ;; Stored for later: k can be saved and invoked outside reset ;; (define saved nil) ;; (reset (+ 1 (shift k (set! saved k) 0))) ;; ;; reset returns 0, saved holds the continuation ;; (saved 99) ;; → 100 ;; ;; -------------------------------------------------------------------------- (define sf-shift (fn ((args :as list) (env :as dict)) ;; Two arguments: the continuation variable name, and the body. (let ((k-name (symbol-name (first args))) (body (second args))) ;; Capture the current continuation up to the nearest reset. ;; Bind it to k-name in the environment, then evaluate body. ;; The result of body is returned to the reset. (capture-continuation k-name body env)))) ;; -------------------------------------------------------------------------- ;; 4. Interaction with other features ;; -------------------------------------------------------------------------- ;; ;; TCO (trampoline): ;; Continuations interact naturally with the trampoline. A shift inside ;; a tail-call position captures the continuation including the pending ;; return. The trampoline resolves thunks before the continuation is ;; delimited. ;; ;; Macros: ;; shift/reset are special forms, not macros. Macros expand before ;; evaluation, so shift inside a macro-expanded form works correctly — ;; it captures the continuation of the expanded code. ;; ;; Components: ;; shift inside a component body captures the continuation of that ;; component's render. The enclosing reset determines the delimiter. ;; This is the foundation for suspendable rendering — a component can ;; shift to suspend, and the server resumes it when data arrives. ;; ;; I/O primitives: ;; I/O primitives execute at invocation time, in whatever context ;; exists then. A continuation that captures a computation containing ;; I/O will re-execute that I/O when invoked. If the I/O requires ;; request context (e.g. current-user), invoking the continuation ;; outside a request will fail — same as calling the I/O directly. ;; This is consistent, not a restriction. ;; ;; In typed targets (Haskell, Rust), the type system can enforce that ;; continuations containing I/O are only invoked in appropriate contexts. ;; In dynamic targets (Python, JS), it fails at runtime. ;; ;; Lexical scope: ;; Continuations capture the dynamic extent (what happens next) but ;; close over the lexical environment at the point of capture. Variable ;; bindings in the continuation refer to the same environment — mutations ;; via set! are visible. ;; ;; -------------------------------------------------------------------------- ;; -------------------------------------------------------------------------- ;; 5. Implementation notes per target ;; -------------------------------------------------------------------------- ;; ;; The bootstrapper emits target-specific continuation machinery. ;; The spec defines semantics; each target chooses representation. ;; ;; Scheme / Racket: ;; Native shift/reset. No transformation needed. The bootstrapper ;; emits (require racket/control) or equivalent. ;; ;; Haskell: ;; Control.Monad.CC provides delimited continuations in the CC monad. ;; Alternatively, the evaluator can be CPS-transformed at compile time. ;; Continuations become first-class functions naturally. ;; ;; Python: ;; Generator-based: reset creates a generator, shift yields from it. ;; The trampoline loop drives the generator. Each yield is a shift ;; point, and send() provides the resume value. ;; Alternative: greenlet-based (stackful coroutines). ;; ;; JavaScript: ;; Generator-based (function* / yield). Similar to Python. ;; Alternative: CPS transform at bootstrap time — the bootstrapper ;; rewrites the evaluator into continuation-passing style, making ;; shift/reset explicit function arguments. ;; ;; Rust: ;; CPS transform at compile time. Continuations become enum variants ;; or boxed closures. The type system ensures continuations are used ;; linearly if desired (affine types via ownership). ;; ;; -------------------------------------------------------------------------- ;; -------------------------------------------------------------------------- ;; 6. Platform interface — what each target must provide ;; -------------------------------------------------------------------------- ;; ;; (eval-with-delimiter expr env) ;; Install a reset delimiter, evaluate expr, return result. ;; If expr calls shift, the continuation is captured up to here. ;; ;; (capture-continuation k-name body env) ;; Capture the current continuation up to the nearest delimiter. ;; Bind it to k-name in env, evaluate body, return result to delimiter. ;; ;; (make-continuation fn) ;; Wrap a native function as a continuation value. ;; ;; (continuation? x) ;; Type predicate. ;; ;; Continuations must be callable via the standard function-call ;; dispatch in eval-list (same path as lambda calls). ;; ;; --------------------------------------------------------------------------