Hyperscript compiler/runtime:
- query target support in set/fire/put commands
- hs-set-prolog-hook! / hs-prolog-hook / hs-prolog in runtime
- runtime log-capture cleanup
Scripts: sx-loops-up/down, sx-hs-e-up/down, sx-primitives-down
Plans: datalog, elixir, elm, go, koka, minikanren, ocaml, hs-bucket-f,
designs (breakpoint, null-safety, step-limit, tell, cookies, eval,
plugin-system)
lib/prolog/hs-bridge.sx: initial hook-based bridge draft
lib/common-lisp/tests/runtime.sx: CL runtime tests
WASM: regenerate sx_browser.bc.js from updated hs sources
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9.6 KiB
Koka-on-SX: Koka on the CEK/VM
Implement a Koka interpreter on SX. The unique angle: Koka's algebraic effects and
handlers map directly onto SX's perform/cek-resume machinery — this is the language
that will stress-test whether SX's effect system is principled enough, and expose any
gaps. Every other language in the set works around effects ad-hoc; Koka makes them the
primary abstraction.
End-state goal: core Koka programs running on the SX CEK evaluator, with algebraic
effect handlers wired through perform/cek-resume. Not a full Koka compiler — no type
inference, no row-polymorphic effect types, no LLVM backend — but a faithful runtime for
idiomatic Koka programs.
What Koka adds that nothing else covers
- Structured effect declarations:
effect state<s> { fun get() : s; fun set(s) : () }— named, typed effect operations, not just untypedperformtokens - Resumable handlers:
handler { return(x) -> x; get() -> resume(0); set(x) -> resume(()) }— multi-shot continuations, handlers as first-class values - Effect polymorphism: functions declare their effect set (
a -> <state<int>,console> b) — exposes whether SX can track which effects are in scope - Tail-resumptive handlers: most practical handlers resume exactly once, which should be optimisable — tests whether the CEK machine can detect and collapse this
- Algebraic data types as the foundation:
type maybe<a> { Nothing; Just(value: a) }— exercises the Phase 6 ADT primitive directly
Ground rules
- Scope: only touch
lib/koka/**andplans/koka-on-sx.md. Do not editspec/,hosts/,shared/, or otherlib/<lang>/. - Shared-file issues go under "Blockers" below with a minimal repro; do not fix here.
- SX files: use
sx-treeMCP tools only. - Architecture: Koka source → Koka AST → interpret directly via CEK. No separate Koka evaluator — host the semantics in SX, run on the existing CEK machine.
- Effect types: defer type inference entirely. Track effects at runtime only — an unhandled effect at the top level raises a runtime error, not a type error.
- Commits: one feature per commit. Keep
## Progress logupdated and tick boxes.
Architecture
Koka source text
│
▼
lib/koka/tokenizer.sx — keywords, operators, indent-sensitivity, type-level syntax
│
▼
lib/koka/parser.sx — Koka AST: fun, val, effect, handler, with, match, resume,
│ return clause, ADT definitions, basic type expressions
▼
lib/koka/eval.sx — Koka AST → CEK evaluation via SX primitives:
│ ADT (define-type/match from Phase 6)
│ Effects (perform/cek-resume from spec/evaluator.sx)
│ Coroutines optional (Phase 4 primitives)
▼
SX CEK evaluator (both JS and OCaml hosts)
Key semantic mappings:
| Koka construct | SX mapping |
|---|---|
fun f(x) body |
(define (f x) body) |
val x = expr |
(let ((x expr)) ...) |
effect E { fun op() : t } |
register effect tag E/op in effect env |
op() inside handler scope |
(perform (list "E" "op" args)) |
handler { return(x)->e; op()->resume(v) } |
(guard ...) + cek-resume |
with handler { body } |
install handler for duration of body |
match x { Nothing -> e1; Just(v) -> e2 } |
SX (match x ...) via Phase 6 ADT |
type maybe<a> { Nothing; Just(value:a) } |
(define-type maybe (Nothing) (Just value)) |
resume(v) in handler |
(cek-resume k v) where k is captured continuation |
return(x) -> expr |
final-value clause when no effect fires |
Koka semantics in brief
Effects and handlers
effect console
fun println(s : string) : ()
fun greet(name : string) : <console> ()
println("Hello, " ++ name)
fun main()
with handler
return(x) -> x
println(s) -> { print-string(s ++ "\n"); resume(()) }
greet("world")
effect consoledeclares an effect with one operationprintlngreetusesconsole— any call toprintlninside will look up the nearest enclosing handlerwith handler { ... }installs a handler;resume(())continues the suspended computation
Multi-shot resumption
effect choice
fun choose() : bool
fun xor(p : bool, q : bool) : <choice> bool
val a = choose()
val b = choose()
(a || b) && !(a && b)
fun all-results()
with handler
return(x) -> [x]
choose() -> resume(True) ++ resume(False)
xor(True, False)
// => [True, True, False, True]
This is the test that exposes whether cek-resume supports multi-shot (calling the
same continuation twice). SX's delimited continuations do support this — Koka will
verify it end-to-end.
Roadmap
Phase 1 — Tokenizer + parser (core expressions)
- Tokenizer: keywords (
fun,val,effect,handler,with,match,return,resume,type,alias,if,then,else,fn), operators (++,->,|>,:,<,>,,), identifiers, numbers, strings, booleans - Parser — expressions:
- literals: int, float, bool (
True/False), string -val x = ebindings -fun f(x, y) bodydefinitions -if c then e1 else e2-match x { Pat -> e; ... }- lambdafn(x) -> e- function applicationf(x, y)- infix operators:++,+,-,*,/,==,!=,<,>,&&,||- pipe|>:x |> f=f(x) - Tests:
lib/koka/tests/parse.sx— 40+ parse round-trip tests
Phase 2 — ADT definitions + match
- Parser:
type name<a> { Ctor1; Ctor2(field: t); ... }declarations - Eval: map to SX
define-type+match(requires Phase 6 primitives) - Built-in:
maybe<a>(Nothing / Just),result<a,e>(Ok / Error),list<a>(Nil / Cons) - Tests: ADT construction, matching, nested patterns — 25+ tests
Phase 3 — Core evaluator
koka-evalentry: walks Koka AST, evaluates in SX env- Arithmetic, string
++, comparison, boolean ops val/letbinding- Function definitions and application (first-class functions)
if/then/elsematchwith constructor, literal, variable, wildcard patterns- Basic list ops:
map,filter,foldl,length,head,tail - Tests:
lib/koka/tests/eval.sx— 40+ tests, pure expressions only
Phase 4 — Effect system
- Effect declaration:
(koka-declare-effect! "console" (list "println"))registers operations in a global effect registry - Effect operation call: when
println(s)is evaluated inside a handler scope, emit(perform (list :effect "console" :op "println" :args (list s))) - Handler installation:
with handler { return(x)->e; println(s)->resume(v) }installs aguard-like frame that catchesperformsignals matching the effect, binds arguments, and exposesresumeas a callable that invokescek-resume resume(v): calls(cek-resume captured-k v)wherecaptured-kis the continuation captured at theperformpointreturn(x) -> eclause: handles the normal return value when no effect fires- Tests:
lib/koka/tests/effects.sx— 30+ tests: - basic handler (state, console, exception) - unhandled effect → runtime error - nested handlers (inner shadows outer) - multi-shot resumption (choice effect — the key test) - tail-resumptive handler (resumes exactly once — verify no extra allocation)
Phase 5 — Standard effect library
consoleeffect:println,print,readline(mock)exneffect:throw,catchwrappersstate<s>effect:get,set,modifyasynceffect:awaitmapped to SXperformIO suspension- Tests: programs using each stdlib effect — 20+ tests
Phase 6 — Classic Koka programs as integration tests
counter.koka— stateful counter via state effectchoice.koka— multi-shot choice generating all resultsiterator.koka— yield-based iteration via a custom effectexception.koka— structured exception handlingcoroutine.koka— producer/consumer via two interleaved effects- Each as a self-contained test in
lib/koka/tests/programs.sx
Key blockers / dependencies
- Phase 6 ADT primitive (
define-type/match) — required before Phase 2. Track:plans/agent-briefings/primitives-loop.mdPhase 6. - Multi-shot continuations —
cek-resumemust support calling the same continuation multiple times. Verify with:(let ((k #f)) (perform 'x) ...)called twice. This should already work given the multi-shot delimited continuation work. - Effect handler stack — SX's
guardis not quite the right primitive for deep-handler semantics. May need(with-handler effect-tag handler-fn body)as a new evaluator form, or can be emulated viaguard+performreshaping.
Comparison to other languages in the set
| Language | Effect model |
|---|---|
| Lua | none (errors only) |
| Prolog | none (cuts only) |
| Erlang | message-passing (not algebraic) |
| Haskell | IO monad (monadic, not algebraic) |
| JS | promise/async-await (one-shot) |
| Ruby | exceptions + fibers |
| Koka | algebraic effects + multi-shot handlers |
Koka is the only language that uses SX's effect system as its primary computational
model. It will expose whether perform/cek-resume is sufficient or needs typed effect
tagging, scoping rules, and a handler stack distinct from guard.
Progress log
Newest first.
- (none yet)
Blockers
- ADT primitive (Phase 6 of primitives-loop) must land before Phase 2 starts.