Unicode escapes, variadic infix fix, spreads demos, scoped-effects + foundations plans

- Add \uXXXX unicode escape support to parser.py and parser.sx spec
- Add char-from-code primitive (Python chr(), JS String.fromCharCode())
- Fix variadic infix operators in both bootstrappers (js.sx, py.sx) —
  (+ a b c d) was silently dropping terms, now left-folds correctly
- Rebootstrap sx_ref.py and sx-browser.js with all fixes
- Fix 3 pre-existing map-dict test failures in shared/sx/tests/run.py
- Add live demos alongside examples in spreads essay (side-by-side layout)
- Add scoped-effects plan: algebraic effects as unified foundation for
  spread/collect/island/lake/signal/context
- Add foundations plan: CEK machine, the computational floor, three-axis
  model (depth/topology/linearity), Curry-Howard correspondence
- Route both plans in page-functions.sx and nav-data.sx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 12:03:58 +00:00
parent 28a6560963
commit 214963ea6a
15 changed files with 1323 additions and 38 deletions

661
sx/sx/plans/foundations.sx Normal file
View File

@@ -0,0 +1,661 @@
;; Foundations — The Computational Floor
;; From scoped effects to CEK: what's beneath algebraic effects and why it's the limit.
(defcomp ~plans/foundations/plan-foundations-content ()
(~docs/page :title "Foundations \u2014 The Computational Floor"
(p :class "text-stone-500 text-sm italic mb-8"
"Scoped effects unify everything SX does. But they are not the bottom. "
"Beneath them is a hierarchy of increasingly fundamental primitives, "
"terminating at three irreducible things: an expression, an environment, and a continuation. "
"This document traces that hierarchy, proves where the floor is, "
"and maps the path from where SX stands to the deepest layer.")
;; -----------------------------------------------------------------------
;; The hierarchy
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "The Hierarchy")
(p "Every layer is definable in terms of the one below. "
"No layer can be decomposed without the layer beneath it.")
(~docs/code-block :code
(str "Layer 0: CEK machine (expression + environment + continuation)\n"
"Layer 1: Continuations (shift / reset \u2014 delimited capture)\n"
"Layer 2: Algebraic effects (operations + handlers)\n"
"Layer 3: Scoped effects (+ region delimitation)\n"
"Layer 4: SX patterns (spread, provide, island, lake, signal, collect)"))
(p "SX currently has layers 0, 1, and 4. "
"Layer 3 is the scoped-effects plan (provide/context/emit!). "
"Layer 2 falls out of layers 1 and 3 and doesn't need its own representation. "
"This document is about layers 0 through 2 \u2014 the machinery beneath scoped effects.")
;; -----------------------------------------------------------------------
;; Layer 0: The CEK machine
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "Layer 0: The CEK Machine")
(p "The CEK machine (Felleisen & Friedman, 1986) is a small-step evaluator with three registers:")
(div :class "overflow-x-auto mb-6"
(table :class "min-w-full text-sm"
(thead (tr
(th :class "text-left pr-4 pb-2 font-semibold" "Register")
(th :class "text-left pr-4 pb-2 font-semibold" "Name")
(th :class "text-left pb-2 font-semibold" "Meaning")))
(tbody
(tr (td :class "pr-4 py-1 font-mono" "C") (td :class "pr-4" "Control") (td "The expression being evaluated"))
(tr (td :class "pr-4 py-1 font-mono" "E") (td :class "pr-4" "Environment") (td "The bindings in scope \u2014 names to values"))
(tr (td :class "pr-4 py-1 font-mono" "K") (td :class "pr-4" "Kontinuation") (td "What to do with the result")))))
(p "Every step of evaluation is a transition: take the current (C, E, K), "
"produce a new (C\u2032, E\u2032, K\u2032). That's it. That's all computation is.")
(p "Can you remove any register?")
(ul :class "list-disc pl-6 mb-4 space-y-1"
(li (strong "Remove C") " \u2014 nothing to evaluate. No computation.")
(li (strong "Remove E") " \u2014 names are meaningless. Lose abstraction. "
"Every value must be literal, every function must be closed. No variables, no closures, no reuse.")
(li (strong "Remove K") " \u2014 no sequencing. The result of an expression goes nowhere. "
"Lose composition \u2014 you can evaluate one thing but never connect it to the next."))
(p "Three things, all necessary, none decomposable further.")
(h3 :class "text-lg font-semibold mt-8 mb-3" "CEK in eval.sx")
(p "SX already implements CEK. It just doesn't name it:")
(~docs/code-block :code
(str ";; eval-expr IS the CEK transition function\n"
";; C = expr, E = env, K = implicit (call stack / trampoline)\n"
"(define eval-expr\n"
" (fn (expr env)\n"
" (cond\n"
" (number? expr) expr ;; literal: C \u2192 value, K unchanged\n"
" (string? expr) expr\n"
" (symbol? expr) (env-get env expr) ;; variable: C + E \u2192 value\n"
" (list? expr) ;; compound: modify K (push frame)\n"
" (let ((head (first expr)))\n"
" ...))))\n"
"\n"
";; The trampoline IS the K register made explicit:\n"
";; instead of growing the call stack, thunks are continuations\n"
"(define trampoline\n"
" (fn (val)\n"
" (let loop ((v val))\n"
" (if (thunk? v)\n"
" (loop (eval-expr (thunk-expr v) (thunk-env v)))\n"
" v))))"))
(p "The trampoline is the K register. Thunks are suspended continuations. "
"Tail-call optimization is replacing K instead of extending it. "
"SX's evaluation model is already a CEK machine \u2014 the plan is to make this explicit, "
"not to build something new.")
;; -----------------------------------------------------------------------
;; Layer 1: Delimited continuations
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "Layer 1: Delimited Continuations")
(p "Delimited continuations (Felleisen 1988, Danvy & Filinski 1990) "
"expose the K register as a first-class value:")
(~docs/code-block :code
(str ";; reset marks a point in the continuation\n"
"(reset\n"
" (+ 1 (shift k ;; k = \"the rest up to reset\"\n"
" (k (k 10)))) ;; invoke it twice: 1 + (1 + 10) = 12\n"
")"))
(p (code "reset") " says: \"here's a boundary.\" "
(code "shift") " says: \"give me everything between here and that boundary as a callable function.\" "
"The captured continuation " (em "is") " a slice of the K register.")
(p "This is already specced in SX (continuations.sx). What it gives us beyond CEK:")
(ul :class "list-disc pl-6 mb-4 space-y-1"
(li (strong "Suspendable computation") " \u2014 capture where you are, resume later")
(li (strong "Backtracking") " \u2014 capture a choice point, try alternatives")
(li (strong "Coroutines") " \u2014 two computations yielding to each other")
(li (strong "Async as a library") " \u2014 async/await is shift/reset with a scheduler"))
(h3 :class "text-lg font-semibold mt-8 mb-3" "The Filinski Embedding")
(p "Filinski (1994) proved that " (code "shift/reset") " can encode "
(em "any") " monadic effect. State, exceptions, nondeterminism, I/O, "
"continuations themselves \u2014 all expressible as shift/reset patterns. "
"This means layer 1 is already computationally complete for effects. "
"Everything above is structure, not power.")
(p "This is the key insight: "
(strong "layers 2\u20134 add no computational power. ") "They add " (em "structure") " \u2014 "
"they make effects composable, nameable, handleable. "
"But anything you can do with scoped effects, "
"you can do with raw shift/reset. You'd just hate writing it.")
;; -----------------------------------------------------------------------
;; Layer 2: Algebraic effects
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "Layer 2: Algebraic Effects")
(p "Plotkin & Pretnar (2009) observed that most effects have algebraic structure: "
"an operation (\"perform this effect\") and a handler (\"here's what that effect means\"). "
"The handler receives the operation's argument and a continuation to resume the program.")
(~docs/code-block :code
(str ";; Pseudocode \u2014 algebraic effect style\n"
"(handle\n"
" (fn () (+ 1 (perform :ask \"what number?\")))\n"
" {:ask (fn (prompt resume)\n"
" (resume 41))})\n"
";; => 42"))
(p (code "perform") " is shift. " (code "handle") " is reset. "
"But with names and types. The handler pattern gives you:")
(ul :class "list-disc pl-6 mb-4 space-y-1"
(li (strong "Named effects") " \u2014 not just \"capture here\" but \"I need state / logging / auth\"")
(li (strong "Composable handlers") " \u2014 stack handlers, each handling different effects")
(li (strong "Effect signatures") " \u2014 a function declares what effects it needs; "
"the type system ensures all effects are handled"))
(p "Plotkin & Power (2003) proved that this captures: "
"state, exceptions, nondeterminism, I/O, cooperative concurrency, "
"probability, and backtracking. All as instances of one algebraic structure.")
(h3 :class "text-lg font-semibold mt-8 mb-3" "What algebraic effects cannot express")
(p "Standard algebraic effects have a limitation: their operations are " (em "first-order") ". "
"An operation takes a value and produces a value. But some effects need operations that "
"take " (em "computations") " as arguments:")
(ul :class "list-disc pl-6 mb-4 space-y-1"
(li (code "catch") " \u2014 takes a computation that might throw, runs it with a handler")
(li (code "local") " \u2014 takes a computation, runs it with modified state")
(li (code "once") " \u2014 takes a nondeterministic computation, commits to its first result")
(li (code "scope") " \u2014 takes a computation, runs it within a delimited region"))
(p "These are " (strong "higher-order effects") ". They need computations as arguments, "
"not just values. This is precisely what the scoped-effects plan addresses.")
;; -----------------------------------------------------------------------
;; Layer 3: Scoped effects (the bridge)
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "Layer 3: Scoped and Higher-Order Effects")
(p "Wu, Schrijvers, and Hinze (2014) introduced " (em "scoped effects") " \u2014 "
"algebraic effects extended with operations that delimit regions. "
"Pirog, Polesiuk, and Sieczkowski (2018) proved these are "
(strong "strictly more expressive") " than standard algebraic effects.")
(p "Bach Poulsen and van der Rest (2023) generalized further with "
(em "hefty algebras") " \u2014 a framework that captures " (em "all") " known higher-order effects, "
"with scoped effects as a special case. This is the current state of the art.")
(p "SX's " (code "provide") " is a scoped effect. It creates a region (the body), "
"makes a value available within it (context), and collects contributions from within it (emit/emitted). "
"This is why it can express things that plain algebraic effects can't: "
"the region boundary is part of the effect, not an accident of the call stack.")
;; -----------------------------------------------------------------------
;; The floor proof
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "Why Layer 0 Is the Floor")
(p "Two independent arguments:")
(h3 :class "text-lg font-semibold mt-8 mb-3" "1. Church\u2013Turing")
(p "The Church\u2013Turing thesis: any effectively computable function can be computed by "
"a Turing machine (equivalently, by the lambda calculus, equivalently, by the CEK machine). "
"This has withstood 90 years of attempts to disprove it. "
"No formal system has ever been shown to compute more.")
(p "This means CEK captures " (em "all") " computation. Adding more primitives doesn't let you "
"compute anything new. It only changes what's " (em "convenient") " to express.")
(h3 :class "text-lg font-semibold mt-8 mb-3" "2. Irreducibility of C, E, K")
(p "The three registers are independently necessary:")
(ul :class "list-disc pl-6 mb-4 space-y-1"
(li (strong "C without E") " = combinatory logic. Turing-complete but inhumane. "
"No named bindings \u2014 everything via S, K, I combinators. "
"Proves you can drop E in theory, but the resulting system "
"can't express abstraction (which is what E provides).")
(li (strong "C without K") " = single expression evaluation. "
"You can compute one thing but can't compose it with anything. "
"Technically you can encode K into C (CPS transform), "
"but this transforms the expression to include the continuation explicitly \u2014 "
"K hasn't been removed, just moved into C.")
(li (strong "E without C") " = a phone book with no one to call.")
(li (strong "K without C") " = a to-do list with nothing on it."))
(p "You can " (em "encode") " any register into another (CPS eliminates K, "
"De Bruijn indices eliminate E), but encoding isn't elimination. "
"The information is still there, just hidden in a different representation. "
"Three independent concerns; three registers.")
;; -----------------------------------------------------------------------
;; Open questions
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "Open Questions")
(p "The hierarchy above is well-established for " (em "sequential") " computation. "
"But there are orthogonal axes where the story is incomplete:")
(h3 :class "text-lg font-semibold mt-8 mb-3" "Concurrency")
(p "Scoped effects assume tree-shaped execution: one thing happens, then the next. "
"But real computation forks:")
(ul :class "list-disc pl-6 mb-4 space-y-1"
(li "Video processing \u2014 pipeline stages run in parallel, frames are processed concurrently")
(li "CI/CD \u2014 test suites fork, builds parallelize, deployment is staged")
(li "Web rendering \u2014 async I/O, streaming SSE, suspense boundaries")
(li "Art DAG \u2014 the entire engine is a DAG of dependent transforms"))
(p "The \u03c0-calculus (Milner 1999) handles concurrency well but not effects. "
"Effect systems handle effects well but not concurrency. "
"Combining them is an open problem. "
"Brachth\u00e4user, Schuster, and Ostermann (2020) have partial results for "
"algebraic effects with multi-shot handlers (where the continuation can be invoked "
"on multiple concurrent threads), but a full synthesis doesn't exist yet.")
(p "For SX, this matters because the Art DAG is fundamentally a concurrent execution engine. "
"If SX ever specifies DAG execution natively, it'll need something beyond scoped effects.")
(h3 :class "text-lg font-semibold mt-8 mb-3" "Linearity")
(p "Can an effect handler be invoked more than once? Must a scope be entered? "
"Must every emitted value be consumed?")
(ul :class "list-disc pl-6 mb-4 space-y-1"
(li (strong "Unrestricted") " \u2014 use as many times as you want (current SX)")
(li (strong "Affine") " \u2014 use at most once (Rust's ownership model)")
(li (strong "Linear") " \u2014 use exactly once (quantum no-cloning, exactly-once delivery)"))
(p "Linear types constrain the continuation: if a handler is linear, "
"it " (em "must") " resume the computation exactly once. No dropping (resource leak), "
"no duplicating (nondeterminism). This connects to:")
(ul :class "list-disc pl-6 mb-4 space-y-1"
(li "Resource management \u2014 file handles that " (em "must") " be closed")
(li "Protocol correctness \u2014 a session type that must complete")
(li "Transaction semantics \u2014 exactly-once commit/rollback")
(li "Quantum computing \u2014 no-cloning theorem as a type constraint"))
(p "Benton (1994) established the connection between linear logic and computation. "
"Adding linear effects to SX would constrain what handlers can do, "
"enabling stronger guarantees about resource safety. "
"This is orthogonal to depth \u2014 it's about " (em "discipline") ", not " (em "power") ".")
(h3 :class "text-lg font-semibold mt-8 mb-3" "Higher-order effects beyond hefty algebras")
(p "Nobody has proved that hefty algebras capture " (em "all possible") " higher-order effects. "
"The hierarchy might continue. This is active research with no clear terminus. "
"The question \"is there a structured effect that no framework can express?\" "
"is analogous to G\u00f6del's incompleteness \u2014 it may be that every effect framework "
"has blind spots, and the only \"complete\" system is raw continuations (layer 1), "
"which are universal but unstructured.")
;; -----------------------------------------------------------------------
;; The three-axis model
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "The Three-Axis Model")
(p "The deepest primitive is not a single thing. "
"It's a point in a three-dimensional space:")
(~docs/code-block :code
(str "depth: CEK \u2192 continuations \u2192 algebraic effects \u2192 scoped effects\n"
"topology: sequential \u2192 concurrent \u2192 distributed\n"
"linearity: unrestricted \u2192 affine \u2192 linear"))
(p "Each axis is independent. You can have:")
(ul :class "list-disc pl-6 mb-4 space-y-1"
(li "Scoped effects + sequential + unrestricted \u2014 " (strong "SX today"))
(li "Scoped effects + concurrent + unrestricted \u2014 the Art DAG integration")
(li "Scoped effects + sequential + linear \u2014 resource-safe SX")
(li "Algebraic effects + concurrent + linear \u2014 something like Rust + Tokio + effect handlers")
(li "CEK + distributed + unrestricted \u2014 raw Erlang/BEAM"))
(p "SX's current position and trajectory:")
(div :class "overflow-x-auto mb-6"
(table :class "min-w-full text-sm"
(thead (tr
(th :class "text-left pr-4 pb-2 font-semibold" "Axis")
(th :class "text-left pr-4 pb-2 font-semibold" "Current")
(th :class "text-left pr-4 pb-2 font-semibold" "Next")
(th :class "text-left pb-2 font-semibold" "Eventual")))
(tbody
(tr (td :class "pr-4 py-1" "Depth")
(td :class "pr-4" "Layer 4 (patterns) + Layer 1 (continuations)")
(td :class "pr-4" "Layer 3 (scoped effects)")
(td "Layer 0 (explicit CEK)"))
(tr (td :class "pr-4 py-1" "Topology")
(td :class "pr-4" "Sequential")
(td :class "pr-4" "Async I/O (partial concurrency)")
(td "DAG execution (Art DAG)"))
(tr (td :class "pr-4 py-1" "Linearity")
(td :class "pr-4" "Unrestricted")
(td :class "pr-4" "Unrestricted")
(td "Affine (resource safety)")))))
;; -----------------------------------------------------------------------
;; What explicit CEK gives SX
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "What Explicit CEK Gives SX")
(p "Making the CEK machine explicit in the spec (rather than implicit in eval-expr) enables:")
(h3 :class "text-lg font-semibold mt-8 mb-3" "Stepping")
(p "A CEK machine transitions one step at a time. "
"If the transition function is explicit, you can:")
(ul :class "list-disc pl-6 mb-4 space-y-1"
(li "Single-step through evaluation (debugger)")
(li "Pause and serialize mid-evaluation (suspend to disk, resume on another machine)")
(li "Instrument each step (profiling, tracing, time-travel debugging)")
(li "Interleave steps from multiple computations (cooperative scheduling without OS threads)"))
(h3 :class "text-lg font-semibold mt-8 mb-3" "Serializable computation")
(p "If C, E, and K are all data structures (not host stack frames), "
"the entire computation state is serializable:")
(~docs/code-block :code
(str ";; Freeze a computation mid-flight\n"
"(let ((state (capture-cek)))\n"
" (send-to-worker state) ;; ship to another machine\n"
" ;; or: (store state) ;; persist to disk\n"
" ;; or: (fork state) ;; run the same computation twice\n"
" )"))
(p "This connects to content-addressed computation: "
"a CID identifying a CEK state is a pointer to a computation in progress. "
"Resume it anywhere. Verify it. Cache it. Share it.")
(h3 :class "text-lg font-semibold mt-8 mb-3" "Formal verification")
(p "An explicit CEK machine is a state machine. State machines are verifiable. "
"You can prove properties about all possible execution paths: "
"termination, resource bounds, effect safety. "
"The theorem prover (prove.sx) could verify CEK transitions directly.")
;; -----------------------------------------------------------------------
;; SX's existing layers
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "SX's Existing Layers")
(p "SX already has most of the hierarchy, specced or planned:")
(div :class "overflow-x-auto mb-6"
(table :class "min-w-full text-sm"
(thead (tr
(th :class "text-left pr-4 pb-2 font-semibold" "Layer")
(th :class "text-left pr-4 pb-2 font-semibold" "SX has")
(th :class "text-left pr-4 pb-2 font-semibold" "Where")
(th :class "text-left pb-2 font-semibold" "Status")))
(tbody
(tr (td :class "pr-4 py-1" "0 \u2014 CEK")
(td :class "pr-4" "eval-expr + trampoline")
(td :class "pr-4" "eval.sx")
(td "Implicit. Works, but CEK is hidden in the host call stack."))
(tr (td :class "pr-4 py-1" "1 \u2014 Continuations")
(td :class "pr-4" "shift / reset")
(td :class "pr-4" "continuations.sx")
(td "Specced. Bootstraps to Python and JavaScript."))
(tr (td :class "pr-4 py-1" "2 \u2014 Algebraic effects")
(td :class "pr-4" "\u2014")
(td :class "pr-4" "\u2014")
(td "Falls out of layers 1 + 3. No dedicated spec needed."))
(tr (td :class "pr-4 py-1" "3 \u2014 Scoped effects")
(td :class "pr-4" "provide / context / emit!")
(td :class "pr-4" "scoped-effects plan")
(td "Planned. Implements over eval.sx + adapters."))
(tr (td :class "pr-4 py-1" "4 \u2014 Patterns")
(td :class "pr-4" "spread, collect, island, lake, signal, store")
(td :class "pr-4" "various .sx files")
(td "Implemented. Will be redefined in terms of layer 3.")))))
;; -----------------------------------------------------------------------
;; Implementation path
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "Implementation Path")
(h3 :class "text-lg font-semibold mt-8 mb-3" "Phase 1: Scoped effects (Layer 3)")
(p "This is the scoped-effects plan. Immediate next step.")
(ul :class "list-disc pl-6 mb-4 space-y-1"
(li "Spec " (code "provide") ", " (code "context") ", " (code "emit!") ", " (code "emitted") " in eval.sx")
(li "Implement in all adapters (HTML, DOM, SX wire, async)")
(li "Redefine spread, collect, and reactive-spread as instances")
(li "Prove existing tests still pass"))
(h3 :class "text-lg font-semibold mt-8 mb-3" "Phase 2: Effect signatures (Layer 2)")
(p "Add optional effect annotations to function definitions:")
(~docs/code-block :code
(str ";; Declare what effects a function uses\n"
"(define fetch-user :effects [io auth]\n"
" (fn (id) ...))\n"
"\n"
";; Pure function \u2014 no effects\n"
"(define add :effects []\n"
" (fn (a b) (+ a b)))\n"
"\n"
";; Scoped effect \u2014 uses context + emit\n"
"(define themed-heading :effects [context]\n"
" (fn (text)\n"
" (h1 :style (str \"color:\" (get (context \"theme\") :primary))\n"
" text)))"))
(p "Effect signatures are checked at registration time. "
"A function that declares " (code ":effects []") " cannot call " (code "emit!") " or " (code "context") ". "
"This is layer 2 \u2014 algebraic effect structure \u2014 applied as a type discipline.")
(h3 :class "text-lg font-semibold mt-8 mb-3" "Phase 3: Explicit CEK (Layer 0)")
(p "Refactor eval.sx to expose the CEK registers as data:")
(~docs/code-block :code
(str ";; The CEK state is a value\n"
"(define-record CEK\n"
" :control expr ;; the expression\n"
" :env env ;; the bindings\n"
" :kont kont) ;; the continuation stack\n"
"\n"
";; One step\n"
"(define step :effects []\n"
" (fn (cek)\n"
" (case (type-of (get cek :control))\n"
" :literal (apply-kont (get cek :kont) (get cek :control))\n"
" :symbol (apply-kont (get cek :kont)\n"
" (env-get (get cek :env) (get cek :control)))\n"
" :list (let ((head (first (get cek :control))))\n"
" ...))))\n"
"\n"
";; Run to completion\n"
"(define run :effects []\n"
" (fn (cek)\n"
" (if (final? cek)\n"
" (get cek :control)\n"
" (run (step cek)))))"))
(p "This makes computation " (em "inspectable") ". A CEK state can be:")
(ul :class "list-disc pl-6 mb-4 space-y-1"
(li "Serialized to a CID (content-addressed frozen computation)")
(li "Single-stepped by a debugger")
(li "Forked (run the same state with different inputs)")
(li "Migrated (ship to another machine, resume there)")
(li "Verified (prove properties about all reachable states)"))
(h3 :class "text-lg font-semibold mt-8 mb-3" "Phase 4: Concurrent effects (topology axis)")
(p "Extend the CEK machine to support multiple concurrent computations:")
(~docs/code-block :code
(str ";; Fork: create two CEK states from one\n"
"(define fork :effects [concurrency]\n"
" (fn (cek)\n"
" (list (step cek) (step cek))))\n"
"\n"
";; Join: merge results from two computations\n"
"(define join :effects [concurrency]\n"
" (fn (cek-a cek-b combine)\n"
" (combine (run cek-a) (run cek-b))))\n"
"\n"
";; Channel: typed communication between concurrent computations\n"
"(define channel :effects [concurrency]\n"
" (fn (name)\n"
" {:send (fn (v) (emit! name v))\n"
" :recv (fn () (shift k (handle-recv name k)))}))"))
(p "This is where scoped effects meet the \u03c0-calculus. "
"The Art DAG is the natural first consumer \u2014 "
"its execution model is already a DAG of dependent computations.")
(h3 :class "text-lg font-semibold mt-8 mb-3" "Phase 5: Linear effects (linearity axis)")
(p "Add resource-safety constraints:")
(~docs/code-block :code
(str ";; Linear scope: must be entered, must complete\n"
"(define-linear open-file :effects [io linear]\n"
" (fn (path)\n"
" (provide \"file\" (fs-open path)\n"
" ;; body MUST consume the file handle exactly once\n"
" ;; compiler error if handle is dropped or duplicated\n"
" (yield (file-read (context \"file\")))\n"
" ;; cleanup runs unconditionally\n"
" )))"))
(p "This is the furthest horizon. "
"Linear effects connect SX to session types, protocol verification, "
"and the kind of safety guarantees that Rust provides at the type level.")
;; -----------------------------------------------------------------------
;; The Curry-Howard correspondence
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "The Curry\u2013Howard Correspondence")
(p "At the deepest level, computation and logic are the same thing:")
(div :class "overflow-x-auto mb-6"
(table :class "min-w-full text-sm"
(thead (tr
(th :class "text-left pr-4 pb-2 font-semibold" "Logic")
(th :class "text-left pr-4 pb-2 font-semibold" "Computation")
(th :class "text-left pb-2 font-semibold" "SX")))
(tbody
(tr (td :class "pr-4 py-1" "Proposition")
(td :class "pr-4" "Type")
(td "Effect signature"))
(tr (td :class "pr-4 py-1" "Proof")
(td :class "pr-4" "Program")
(td "Implementation"))
(tr (td :class "pr-4 py-1" "Proof normalization")
(td :class "pr-4" "Evaluation")
(td "CEK transitions"))
(tr (td :class "pr-4 py-1" "Hypothesis")
(td :class "pr-4" "Variable binding")
(td "Environment (E register)"))
(tr (td :class "pr-4 py-1" "Implication (A \u2192 B)")
(td :class "pr-4" "Function type")
(td "Lambda"))
(tr (td :class "pr-4 py-1" "Conjunction (A \u2227 B)")
(td :class "pr-4" "Product type")
(td "Dict / record"))
(tr (td :class "pr-4 py-1" "Disjunction (A \u2228 B)")
(td :class "pr-4" "Sum type / union")
(td "Case dispatch"))
(tr (td :class "pr-4 py-1" "Universal (\u2200x.P)")
(td :class "pr-4" "Polymorphism")
(td "Generic components"))
(tr (td :class "pr-4 py-1" "Existential (\u2203x.P)")
(td :class "pr-4" "Abstract type")
(td "Opaque scope"))
(tr (td :class "pr-4 py-1" "Double negation (\u00ac\u00acA)")
(td :class "pr-4" "Continuation")
(td "shift/reset")))))
(p "A program is a proof that a computation is possible. "
"An effect signature is a proposition about what the program does to the world. "
"A handler is a proof that the effect can be realized. "
"Evaluation is proof normalization \u2014 the systematic simplification of a proof to its normal form.")
(p "This is why CEK is the floor: it " (em "is") " logic. "
"Expression = proposition, environment = hypotheses, continuation = proof context. "
"You can't go beneath logic and still be doing computation. "
"The Curry\u2013Howard correspondence is not a metaphor. It's an isomorphism.")
;; -----------------------------------------------------------------------
;; Summary
;; -----------------------------------------------------------------------
(h2 :class "text-xl font-bold mt-12 mb-4" "Summary")
(p "The path from where SX stands to the computational floor:")
(div :class "overflow-x-auto mb-6"
(table :class "min-w-full text-sm"
(thead (tr
(th :class "text-left pr-4 pb-2 font-semibold" "Step")
(th :class "text-left pr-4 pb-2 font-semibold" "What")
(th :class "text-left pr-4 pb-2 font-semibold" "Enables")
(th :class "text-left pb-2 font-semibold" "Depends on")))
(tbody
(tr (td :class "pr-4 py-1" "1")
(td :class "pr-4" "Scoped effects")
(td :class "pr-4" "Unify spread/collect/island/lake/context")
(td "eval.sx + adapters"))
(tr (td :class "pr-4 py-1" "2")
(td :class "pr-4" "Effect signatures")
(td :class "pr-4" "Static effect checking, pure/IO boundary")
(td "types.sx + scoped effects"))
(tr (td :class "pr-4 py-1" "3")
(td :class "pr-4" "Explicit CEK")
(td :class "pr-4" "Stepping, serialization, migration, verification")
(td "eval.sx refactor"))
(tr (td :class "pr-4 py-1" "4")
(td :class "pr-4" "Concurrent effects")
(td :class "pr-4" "DAG execution, parallel pipelines")
(td "CEK + channels"))
(tr (td :class "pr-4 py-1" "5")
(td :class "pr-4" "Linear effects")
(td :class "pr-4" "Resource safety, protocol verification")
(td "Effect signatures + linear types")))))
(p "Each step is independently valuable. The first is immediate. "
"The last may be years out or may never arrive. "
"The hierarchy exists whether we traverse it or not \u2014 "
"it's the structure of computation itself, "
"and SX is better for knowing where it sits within it.")
(p :class "text-stone-500 text-sm italic mt-12"
"The true foundation of any language is not its syntax or its runtime "
"but the mathematical structure it participates in. "
"SX is an s-expression language, which makes it a notation for the lambda calculus, "
"which is a notation for logic, which is the structure of thought. "
"The floor is thought itself. We can't go deeper, because there's no one left to dig.")))

View File

@@ -0,0 +1,444 @@
;; Scoped Effects — The Deepest Primitive
;; Algebraic effects as the unified foundation for spreads, islands, lakes, and context.
(defcomp ~plans/scoped-effects/plan-scoped-effects-content ()
(~docs/page :title "Scoped Effects — The Deepest Primitive"
(p :class "text-stone-500 text-sm italic mb-8"
"Everything SX does — spreads, signals, islands, lakes, morph, context, collect — "
"is an instance of one pattern: a named scope with a value flowing down, "
"contributions flowing up, and a propagation mode determining when effects are realised. "
"This plan traces that pattern to its foundation in algebraic effects "
"and proposes " (code "scope") " as the single primitive underneath everything.")
;; =====================================================================
;; I. The Observation
;; =====================================================================
(~docs/section :title "The observation" :id "observation"
(p "SX has accumulated several mechanisms that all do variations of the same thing:")
(table :class "w-full text-sm border-collapse mb-6"
(thead
(tr :class "border-b border-stone-300"
(th :class "text-left py-2 pr-4" "Mechanism")
(th :class "text-left py-2 pr-4" "Direction")
(th :class "text-left py-2 pr-4" "When")
(th :class "text-left py-2" "Boundary")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "spread")
(td :class "py-2 pr-4" "child → parent")
(td :class "py-2 pr-4" "render time")
(td :class "py-2" "element"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "collect! / collected")
(td :class "py-2 pr-4" "child → ancestor")
(td :class "py-2 pr-4" "render time")
(td :class "py-2" "render tree"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "provide / context")
(td :class "py-2 pr-4" "ancestor → child")
(td :class "py-2 pr-4" "render time")
(td :class "py-2" "render subtree"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "signal / effect")
(td :class "py-2 pr-4" "value → subscribers")
(td :class "py-2 pr-4" "signal change")
(td :class "py-2" "reactive scope"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "defisland")
(td :class "py-2 pr-4" "server → client")
(td :class "py-2 pr-4" "hydration")
(td :class "py-2" "server/client"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "lake")
(td :class "py-2 pr-4" "server → client slot")
(td :class "py-2 pr-4" "navigation/morph")
(td :class "py-2" "client/server"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "reactive-spread")
(td :class "py-2 pr-4" "child → parent")
(td :class "py-2 pr-4" "signal change")
(td :class "py-2" "element"))
(tr
(td :class "py-2 pr-4 font-mono text-xs" "def-store / use-store")
(td :class "py-2 pr-4" "island ↔ island")
(td :class "py-2 pr-4" "signal change")
(td :class "py-2" "cross-island"))))
(p "Eight mechanisms. Each invented separately for a real use case. "
"Each with its own API surface, its own implementation path, its own section in the spec.")
(p "But look at the table. Every row is: " (em "a named region, "
"a direction of data flow, a moment when effects are realised, "
"and a boundary that determines what crosses") ". They are all the same shape."))
;; =====================================================================
;; II. Three propagation modes
;; =====================================================================
(~docs/section :title "Three propagation modes" :id "propagation"
(p "The \"when\" column has exactly three values. These are the three "
"propagation modes of the render tree:")
(~docs/subsection :title "Render propagation"
(p "Effects are realised " (strong "during the render pass") ". "
"Synchronous, one-shot, deterministic. The renderer walks the tree, "
"encounters effects, handles them immediately.")
(p "Instances: " (code "provide/context/emit!") ", " (code "spread") ", "
(code "collect!/collected") ".")
(p "Characteristic: by the time rendering finishes, all render-time effects "
"are fully resolved. The output is static HTML with everything baked in."))
(~docs/subsection :title "Reactive propagation"
(p "Effects are realised " (strong "when signals change") ". "
"Fine-grained, incremental, potentially infinite. A signal notifies "
"its subscribers, each subscriber updates its piece of the DOM.")
(p "Instances: " (code "signal/effect/computed") ", " (code "reactive-spread") ", "
(code "defisland") ", " (code "def-store/use-store") ".")
(p "Characteristic: the initial render produces a snapshot. "
"Subsequent changes propagate through the signal graph without re-rendering. "
"Each signal write touches only the DOM nodes that actually depend on it."))
(~docs/subsection :title "Morph propagation"
(p "Effects are realised " (strong "when the server sends new HTML") ". "
"Network-bound, server-initiated, structural. The morph algorithm "
"compares old and new DOM trees and surgically updates.")
(p "Instances: " (code "lake") ", full-page morph, " (code "sx-get/sx-post") " responses.")
(p "Characteristic: the server is the source of truth for content. "
"The client has reactive state that must be " (em "protected") " during morph. "
"Lakes mark the boundaries: morph enters islands, finds lake elements by ID, "
"updates their children, leaves reactive attrs untouched.")))
;; =====================================================================
;; III. The scope primitive
;; =====================================================================
(~docs/section :title "The scope primitive" :id "scope"
(p "A " (code "scope") " is a named region of the render tree with three properties:")
(ol :class "space-y-2 text-stone-600"
(li (strong "Value") " — data flowing " (em "downward") " to descendants (readable via " (code "context") ")")
(li (strong "Accumulator") " — data flowing " (em "upward") " from descendants (writable via " (code "emit!") ")")
(li (strong "Propagation mode") " — " (em "when") " effects on this scope are realised"))
(~docs/code :code (highlight "(scope \"name\"\n :value expr ;; downward (readable by descendants)\n :propagation :render ;; :render | :reactive | :morph\n body...)" "lisp"))
(p "Every existing mechanism is a scope with a specific configuration:")
(table :class "w-full text-sm border-collapse mb-6"
(thead
(tr :class "border-b border-stone-300"
(th :class "text-left py-2 pr-4" "Mechanism")
(th :class "text-left py-2 pr-4" "Scope equivalent")
(th :class "text-left py-2" "Propagation")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "provide")
(td :class "py-2 pr-4 font-mono text-xs" "(scope name :value v body)")
(td :class "py-2" ":render"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "defisland")
(td :class "py-2 pr-4 font-mono text-xs" "(scope name :propagation :reactive body)")
(td :class "py-2" ":reactive"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "lake")
(td :class "py-2 pr-4 font-mono text-xs" "(scope name :propagation :morph body)")
(td :class "py-2" ":morph"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "spread")
(td :class "py-2 pr-4 font-mono text-xs" "(emit! :element-attrs dict)")
(td :class "py-2" ":render (implicit)"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "collect!")
(td :class "py-2 pr-4 font-mono text-xs" "(emit! name value)")
(td :class "py-2" ":render"))
(tr
(td :class "py-2 pr-4 font-mono text-xs" "def-store")
(td :class "py-2 pr-4 font-mono text-xs" "(scope name :value (signal v) :propagation :reactive)")
(td :class "py-2" ":reactive (cross-scope)"))))
(~docs/subsection :title "Scopes compose by nesting"
(~docs/code :code (highlight ";; An island with a theme context and a morphable lake\n(scope \"my-island\" :propagation :reactive\n (let ((colour (signal \"violet\")))\n (scope \"theme\" :value {:primary colour} :propagation :render\n (div\n (h1 :style (str \"color:\" (deref (get (context \"theme\") :primary)))\n \"Themed heading\")\n (scope \"product-details\" :propagation :morph\n ;; Server morphs this on navigation\n ;; Reactive attrs on the h1 are protected\n (~product-card :id 42))))))" "lisp"))
(p "Three scopes, three propagation modes, nested naturally. "
"The reactive scope manages signal lifecycle. "
"The render scope provides downward context. "
"The morph scope marks where server content flows in. "
"Each scope handles its own effects without knowing about the others.")))
;; =====================================================================
;; IV. The 2×3 matrix
;; =====================================================================
(~docs/section :title "The 2×3 matrix" :id "matrix"
(p "Direction (up/down) × propagation mode (render/reactive/morph) gives six cells. "
"Every mechanism in SX occupies one:")
(table :class "w-full text-sm border-collapse mb-6"
(thead
(tr :class "border-b border-stone-300"
(th :class "text-left py-2 pr-4" "")
(th :class "text-left py-2 pr-4" "Render-time")
(th :class "text-left py-2 pr-4" "Reactive")
(th :class "text-left py-2" "Morph")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-semibold" "Down ↓")
(td :class "py-2 pr-4 font-mono text-xs" "context")
(td :class "py-2 pr-4 font-mono text-xs" "reactive context")
(td :class "py-2 font-mono text-xs" "server props"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-semibold" "Up ↑")
(td :class "py-2 pr-4 font-mono text-xs" "emit! / spread")
(td :class "py-2 pr-4 font-mono text-xs" "reactive-spread")
(td :class "py-2 font-mono text-xs" "lake contributions"))
(tr
(td :class "py-2 pr-4 font-semibold" "Both ↕")
(td :class "py-2 pr-4 font-mono text-xs" "provide")
(td :class "py-2 pr-4 font-mono text-xs" "island")
(td :class "py-2 font-mono text-xs" "full morph"))))
(p "Some cells are already implemented. Some are new. "
"The point is that " (code "scope") " fills them all from " (strong "one primitive") ". "
"You don't need to invent a new mechanism for each cell — you configure the scope.")
(~docs/subsection :title "The new cell: reactive context"
(p "The (Down ↓, Reactive) cell — " (strong "reactive context") " — "
"is the most interesting gap. It's React's Context + signals, but without "
"the re-render avalanche that makes React Context slow.")
(~docs/code :code (highlight ";; Reactive context: value is a signal, propagation is reactive\n(scope \"theme\" :value (signal {:primary \"violet\"}) :propagation :reactive\n ;; Any descendant reads with context + deref\n ;; Only the specific DOM node that uses the value updates\n (h1 :style (str \"color:\" (get (deref (context \"theme\")) :primary))\n \"This h1 updates when the theme signal changes\")\n ;; Deep nesting doesn't matter — it's O(1) per subscriber\n (~deeply-nested-component-tree))" "lisp"))
(p "React re-renders " (em "every") " component that reads a changed context. "
"SX's reactive context updates " (em "only") " the DOM nodes that " (code "deref") " the signal. "
"Same API, fundamentally different performance characteristics.")))
;; =====================================================================
;; V. Algebraic effects
;; =====================================================================
(~docs/section :title "Algebraic effects: the deepest layer" :id "algebraic-effects"
(p "The scope primitive has a name in programming language theory: "
(strong "algebraic effects with handlers") ".")
(~docs/subsection :title "The correspondence"
(table :class "w-full text-sm border-collapse mb-6"
(thead
(tr :class "border-b border-stone-300"
(th :class "text-left py-2 pr-4" "Algebraic effects")
(th :class "text-left py-2 pr-4" "SX")
(th :class "text-left py-2" "What it does")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "perform effect")
(td :class "py-2 pr-4 font-mono text-xs" "emit!")
(td :class "py-2" "Raise an effect upward through the tree"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "handle effect")
(td :class "py-2 pr-4 font-mono text-xs" "scope / provide")
(td :class "py-2" "Catch and interpret the effect"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "ask")
(td :class "py-2 pr-4 font-mono text-xs" "context")
(td :class "py-2" "Read the nearest handler's value"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "handler nesting")
(td :class "py-2 pr-4 font-mono text-xs" "scope nesting")
(td :class "py-2" "Inner handlers shadow outer ones"))
(tr
(td :class "py-2 pr-4 font-mono text-xs" "resumption")
(td :class "py-2 pr-4 font-mono text-xs" "reactive propagation")
(td :class "py-2" "Handler can re-invoke the continuation"))))
(p "The last row is the deep one. In algebraic effect theory, a handler can "
(strong "resume") " the computation that performed the effect — optionally with "
"a different value. This is exactly what reactive propagation does: when a signal "
"changes, the effect handler (the island scope) re-invokes the subscribed "
"continuations (the effect callbacks) with the new value."))
(~docs/subsection :title "Why this matters"
(p "Algebraic effects are " (strong "the most general control flow abstraction known") ". "
"Exceptions, generators, async/await, coroutines, backtracking, and "
"nondeterminism are all instances of algebraic effects with different handler strategies.")
(p "SX's three propagation modes are three handler strategies:")
(ul :class "list-disc pl-5 space-y-2 text-stone-600"
(li (strong "Render") " = " (em "one-shot") " handler — handle the effect immediately, "
"don't resume. Like " (code "try/catch") " for rendering.")
(li (strong "Reactive") " = " (em "multi-shot") " handler — handle the effect, "
"then re-handle it every time the value changes. Like a generator that yields "
"whenever a signal fires.")
(li (strong "Morph") " = " (em "external resumption") " handler — the server decides "
"when to resume, sending new content over the network. Like an async/await "
"where the promise is resolved by a different machine.")))
(~docs/subsection :title "The effect hierarchy"
(p "Effects form a hierarchy. Lower-level effects are handled by higher-level scopes:")
(~docs/code :code (highlight ";; Effect hierarchy (innermost handled first)\n;;\n;; spread → element scope handles (merge attrs)\n;; emit! → nearest provide handles (accumulate)\n;; signal write → reactive scope handles (notify subscribers)\n;; morph → morph scope handles (diff + patch)\n;;\n;; If no handler is found, the effect bubbles up.\n;; Unhandled effects at the root are errors (like uncaught exceptions).\n;;\n;; This is why (emit! \"cssx\" rule) without a provider\n;; should error — there's no handler for the effect." "lisp"))
(p "The current " (code "collect!") " is a global accumulator — effectively an "
"implicit top-level handler. Under the scope model, it would be an explicit "
(code "provide") " in the layout that handles " (code "\"cssx\"") " effects.")))
;; =====================================================================
;; VI. What scope enables
;; =====================================================================
(~docs/section :title "What scope enables" :id "enables"
(~docs/subsection :title "1. New propagation modes"
(p "The three modes (render, reactive, morph) are not hardcoded — they are "
"handler strategies. New strategies can be added without new primitives:")
(ul :class "list-disc pl-5 space-y-2 text-stone-600"
(li (strong ":animation") " — effects realised on requestAnimationFrame, "
"batched per frame, interruptible by higher-priority updates")
(li (strong ":idle") " — effects deferred to requestIdleCallback, "
"used for non-urgent background updates (the transition pattern)")
(li (strong ":stream") " — effects realised as server-sent events arrive, "
"for live data (scores, prices, notifications)")
(li (strong ":worker") " — effects computed in a Web Worker, "
"results propagated back to the main thread")))
(~docs/subsection :title "2. Effect composition"
(p "Because scopes nest, effects compose naturally:")
(~docs/code :code (highlight ";; Animation scope inside a reactive scope\n(scope \"island\" :propagation :reactive\n (let ((items (signal large-list)))\n (scope \"smooth\" :propagation :animation\n ;; Updates to items are batched per frame\n ;; The reactive scope tracks deps\n ;; The animation scope throttles DOM writes\n (for-each (fn (item)\n (div (get item \"name\"))) (deref items)))))" "lisp"))
(p "The reactive scope notifies when " (code "items") " changes. "
"The animation scope batches the resulting DOM writes to the next frame. "
"Neither scope knows about the other. They compose by nesting."))
(~docs/subsection :title "3. Server/client as effect boundary"
(p "The deepest consequence: the server/client boundary becomes " (em "just another "
"scope boundary") ". What crosses it is determined by the propagation mode:")
(~docs/code :code (highlight ";; The server renders this:\n(scope \"page\" :propagation :render\n (scope \"header\" :propagation :reactive\n ;; Client takes over — reactive scope\n (island-body...))\n (scope \"content\" :propagation :morph\n ;; Server controls — morphed on navigation\n (page-content...))\n (scope \"footer\" :propagation :render\n ;; Static — rendered once, never changes\n (footer...)))" "lisp"))
(p "The renderer walks the tree. When it hits a reactive scope, it serialises "
"state and emits a hydration marker. When it hits a morph scope, it emits "
"a lake marker. When it hits a render scope, it just renders. "
"The three scope types naturally produce the islands-and-lakes architecture — "
"not as a special case, but as the " (em "only") " case."))
(~docs/subsection :title "4. Cross-scope communication"
(p "Named stores (" (code "def-store/use-store") ") are scopes that transcend "
"the render tree. Two islands sharing a signal is two reactive scopes "
"referencing the same named scope:")
(~docs/code :code (highlight ";; Two islands, one shared scope\n(scope \"cart\" :value (signal []) :propagation :reactive :global true\n ;; Any island can read/write the cart\n ;; The scope transcends the render tree\n ;; Signal propagation handles cross-island updates)" "lisp"))
(p "The " (code ":global") " flag lifts the scope out of the tree hierarchy "
"into a named registry. " (code "def-store") " is syntax sugar for this.")))
;; =====================================================================
;; VII. Implementation path
;; =====================================================================
(~docs/section :title "Implementation path" :id "implementation"
(p "The path from current SX to the scope primitive follows the existing plan "
"and adds two phases:")
(~docs/subsection :title "Phase 1: provide/context/emit! (immediate)"
(p "Already planned. Implement render-time dynamic scope. Four primitives: "
(code "provide") " (special form), " (code "context") ", " (code "emit!") ", "
(code "emitted") ". Platform provides " (code "provide-push!/provide-pop!") ".")
(p "This is " (code "scope") " with " (code ":propagation :render") " only. "
"No change to islands or lakes. Pure addition.")
(p (strong "Delivers: ") "render-time context, scoped accumulation, "
"spread and collect reimplemented as sugar over provide/emit."))
(~docs/subsection :title "Phase 2: scope as the common form (next)"
(p "Introduce " (code "scope") " as the general form. "
(code "provide") " becomes sugar for " (code "(scope ... :propagation :render)") ". "
(code "defisland") " becomes sugar for " (code "(scope ... :propagation :reactive)") ". "
(code "lake") " becomes sugar for " (code "(scope ... :propagation :morph)") ".")
(p "The sugar forms remain — nobody writes " (code "scope") " directly in page code. "
"But the evaluator, adapters, and bootstrappers all dispatch through one mechanism.")
(p (strong "Delivers: ") "unified internal representation, reactive context (the new cell), "
"simplified adapter code (one scope handler instead of three separate paths)."))
(~docs/subsection :title "Phase 3: effect handlers (future)"
(p "Make propagation modes extensible. A " (code ":propagation") " value is a "
"handler function that determines when and how effects are realised:")
(~docs/code :code (highlight ";; Custom propagation mode\n(define :debounced\n (fn (emit-fn delay)\n ;; Returns a handler that debounces effect realisation\n (let ((timer nil))\n (fn (effect)\n (when timer (clear-timeout timer))\n (set! timer (set-timeout\n (fn () (emit-fn effect)) delay))))))\n\n;; Use it\n(scope \"search\" :propagation (:debounced 300)\n ;; Effects in this scope are debounced by 300ms\n (input :on-input (fn (e)\n (emit! \"search\" (get-value e)))))" "lisp"))
(p (strong "Delivers: ") "user-defined propagation modes (animation, debounce, throttle, "
"worker, stream), effect composition by nesting, "
"the full algebraic effects model.")))
;; =====================================================================
;; VIII. What this means for the spec
;; =====================================================================
(~docs/section :title "What this means for the spec" :id "spec"
(p "The self-hosting spec currently has separate code paths for each mechanism. "
"Under the scope model, they converge:")
(table :class "w-full text-sm border-collapse mb-6"
(thead
(tr :class "border-b border-stone-300"
(th :class "text-left py-2 pr-4" "Spec file")
(th :class "text-left py-2 pr-4" "Current")
(th :class "text-left py-2" "After scope")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "eval.sx")
(td :class "py-2 pr-4" "provide + defisland as separate special forms")
(td :class "py-2" "scope as one special form, sugar for provide/defisland/lake"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "adapter-html.sx")
(td :class "py-2 pr-4" "provide, island, lake as separate dispatch cases")
(td :class "py-2" "one scope dispatch, mode determines serialisation"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "adapter-dom.sx")
(td :class "py-2 pr-4" "render-dom-island, render-dom-lake, reactive-spread")
(td :class "py-2" "one render-dom-scope, mode determines lifecycle"))
(tr :class "border-b border-stone-100"
(td :class "py-2 pr-4 font-mono text-xs" "engine.sx")
(td :class "py-2 pr-4" "morph-island-children, sync-attrs, data-sx-reactive-attrs")
(td :class "py-2" "morph-scope (scope boundary determines skip/update)"))
(tr
(td :class "py-2 pr-4 font-mono text-xs" "signals.sx")
(td :class "py-2 pr-4" "standalone signal runtime")
(td :class "py-2" "unchanged — signals are the value layer, scopes are the structure layer"))))
(p "Signals remain orthogonal. A scope's " (code ":value") " can be a signal or a plain "
"value — the scope doesn't care. The propagation mode determines whether the scope "
"re-runs effects when the value changes, not whether the value " (em "can") " change."))
;; =====================================================================
;; IX. The deepest thing
;; =====================================================================
(~docs/section :title "The deepest thing" :id "deepest"
(p "Go deep enough and there is one operation:")
(blockquote :class "border-l-4 border-violet-300 pl-4 my-6"
(p :class "text-lg font-semibold text-stone-700"
"Evaluate this expression in a scope. Handle effects that cross scope boundaries."))
(p "Rendering is evaluating expressions in a scope where " (code "emit!") " effects "
"are handled by accumulating HTML. Reactivity is re-evaluating expressions "
"when signals fire. Morphing is receiving new expressions from the server "
"and re-evaluating them in existing scopes.")
(p "Every SX mechanism — every one — is a specific answer to three questions:")
(ol :class "space-y-2 text-stone-600"
(li (strong "What scope") " am I in? (element / subtree / island / lake / global)")
(li (strong "What direction") " does data flow? (down via context, up via emit)")
(li (strong "When") " are effects realised? (render / signal change / network)"))
(p (code "scope") " is the primitive that makes all three questions explicit "
"and composable. It's the last primitive SX needs.")
(~docs/note
(p (strong "Status: ") "Phase 1 (" (code "provide/context/emit!") ") is specced and "
"ready to build. Phase 2 (" (code "scope") " unification) follows naturally once "
"provide is working. Phase 3 (extensible handlers) is the research frontier — "
"it may turn out that three modes are sufficient, or it may turn out that "
"user-defined modes unlock something unexpected.")))))