The keystone validation of the universal-algebra thesis. lib/host/execute.sx is a SECOND
interpreter over the SAME seq/alt/each composition algebra as the render-fold — but a
different fold: leaves are EFFECTS, seq = steps in order, alt+when = branch, each =
for-each, and the accumulator is an effect log instead of an HTML string. It REUSES
compose.sx's shared machinery verbatim — host/comp--pred? (when), host/comp--field
(field/value), host/comp--source (each source) — so the predicate set, context-environment,
and iteration source are domain-agnostic; only the leaf semantics + accumulator are new.
KEYSTONE (tested): ONE (alt (when (has "auth") …) …) skeleton + ONE context folds two ways
— render picks the branch → "<b>in</b>", execute picks the SAME branch → {:verb "enter"}.
A publish workflow (validate → branch-on-status → notify-each) runs as one execute-fold over
a composition object. So the behaviour model (Slice 9) is "an execute-fold over a composition
object", not a separate system — the way the recursive tree proved recursion, this proves the
algebra is domain-agnostic. host/exec-run; 13/13 (new execute suite); wired into conformance
+ serve. Full host conformance 371/373 in 42s (warm); the 2 fails are the pre-existing
relate-picker pair.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
77 lines
3.9 KiB
Plaintext
77 lines
3.9 KiB
Plaintext
;; lib/host/execute.sx — the EXECUTE-fold: a SECOND interpreter over the SAME composition
|
|
;; algebra (seq/alt/each) as the render-fold (lib/host/compose.sx), proving the algebra is
|
|
;; domain-agnostic (plans/composition-objects.md step 7 — "prove universality with a second
|
|
;; fold"). What changes between folds is only what the combinators + leaves MEAN:
|
|
;;
|
|
;; domain fold seq alt+when each leaf
|
|
;; content render -> block order choose map items markup -> HTML string
|
|
;; behaviour execute -> steps in order branch for-each effect -> effect log
|
|
;;
|
|
;; Crucially this REUSES compose.sx's shared machinery — the `when` predicate set
|
|
;; (host/comp--pred?), the field/value resolver (host/comp--field), and the `each` source
|
|
;; (host/comp--source). So the predicate set, the context-environment, and the iteration
|
|
;; source are domain-agnostic; ONLY the leaf semantics (effect vs markup) and the fold's
|
|
;; accumulator (a list of effects vs a string) are new. The behaviour model (Slice 9) is
|
|
;; therefore "an execute-fold over a composition object", not a separate system.
|
|
|
|
;; resolve an effect argument against the context: (field K) reads the :item/ctx value via
|
|
;; the SAME resolver the render-fold uses; anything else is a literal.
|
|
(define host/exec--arg
|
|
(fn (a ctx)
|
|
(if (and (= (type-of a) "list") (= (str (first a)) "field"))
|
|
(host/comp--field (first (rest a)) ctx)
|
|
a)))
|
|
|
|
;; a leaf effect: (effect VERB ARG…) -> one effect record {:verb :args}. The execute-fold's
|
|
;; analogue of a render leaf — it performs (records) an effect rather than emitting markup.
|
|
(define host/exec--effect
|
|
(fn (verb args ctx)
|
|
(list {:verb (str verb) :args (map (fn (a) (host/exec--arg a ctx)) args)})))
|
|
|
|
;; seq: run every step IN ORDER, concatenating their effects (the sequential strategy).
|
|
(define host/exec--run-all
|
|
(fn (nodes ctx) (reduce (fn (acc n) (concat acc (host/exec--run n ctx))) (list) nodes)))
|
|
|
|
;; alt: BRANCH — run the FIRST branch whose `when` holds (reusing the render-fold's
|
|
;; predicate host/comp--pred?), else `else`. This is if/cond for the behaviour domain.
|
|
(define host/exec--alt
|
|
(fn (branches ctx)
|
|
(if (empty? branches)
|
|
(list)
|
|
(let ((br (first branches)) (bh (str (first (first branches)))))
|
|
(cond
|
|
((= bh "else") (host/exec--run (first (rest br)) ctx))
|
|
((= bh "when") (if (host/comp--pred? (first (rest br)) ctx)
|
|
(host/exec--run (first (rest (rest br))) ctx)
|
|
(host/exec--alt (rest branches) ctx)))
|
|
(else (host/exec--alt (rest branches) ctx)))))))
|
|
|
|
;; each: FOR-EACH — run the body per item from the (reused) source, :item bound, in order;
|
|
;; depth guard backstops runaway recursion, same as the render-fold.
|
|
(define host/exec--each
|
|
(fn (src body ctx)
|
|
(let ((depth (or (get ctx "depth") 0)))
|
|
(if (> depth 40)
|
|
(list {:verb "max-depth" :args (list)})
|
|
(reduce
|
|
(fn (acc item)
|
|
(concat acc (host/exec--run body (merge ctx {"item" item "depth" (+ depth 1)}))))
|
|
(list) (host/comp--source src ctx))))))
|
|
|
|
;; the execute-fold (the interpreter): same combinator dispatch shape as host/comp--render,
|
|
;; but leaves are effects and the accumulator is an effect log.
|
|
(define host/exec--run
|
|
(fn (node ctx)
|
|
(if (not (= (type-of node) "list"))
|
|
(list)
|
|
(let ((h (str (first node))) (args (rest node)))
|
|
(cond
|
|
((= h "seq") (host/exec--run-all args ctx))
|
|
((= h "alt") (host/exec--alt args ctx))
|
|
((= h "each") (host/exec--each (first args) (first (rest args)) ctx))
|
|
((= h "effect") (host/exec--effect (first args) (rest args) ctx))
|
|
(else (list)))))))
|
|
|
|
;; public entry: execute a composition node against a context -> the effect log (the run).
|
|
(define host/exec-run (fn (node ctx) (host/exec--run node ctx)))
|