core: Phase 5 Option A — lazy interleave/reductions/tree-seq via letfn

These three were the last eager transformers, blocked by jolt-r81: a self-
recursive lazy-seq in the overlay leaks its macro expansion under :compile? when
recursion goes through a top-level name or (fn name …) self-name. Rewriting the
recursion as letfn-bound (the form partition-by/mapcat/dedupe already use, which
compiles cleanly) sidesteps the bug. All three are now lazy in interpret,
compile, and self-host — completing Option A for every transformer.

interleave: canonical lazy cons-recursion (2-arity) + map/concat (n-arity).
reductions: letfn step accumulator. tree-seq: letfn walk + lazy mapcat.

Gate: conformance 246x3, lazy-infinite 40/40 (+interleave/reductions/tree-seq
infinite cases), fixpoint, self-host, specs+unit green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yogthos 2026-06-08 17:28:55 -04:00
parent 3a42438b68
commit c7e162add4
2 changed files with 49 additions and 40 deletions

View file

@ -151,36 +151,34 @@
(defn comparator [pred] (defn comparator [pred]
(fn [a b] (cond (pred a b) -1 (pred b a) 1 :else 0))) (fn [a b] (cond (pred a b) -1 (pred b a) 1 :else 0)))
;; Eager (Jolt has no laziness yet): a vector of the running accumulators. ;; Lazy: the running accumulators, one at a time (matches Clojure). Recursion is
;; letfn-bound (NOT top-level self-call) so the lazy-seq body compiles cleanly in
;; the overlay — see jolt-r81.
(defn reductions (defn reductions
([f coll] ([f coll]
(let [s (seq coll)] (lazy-seq
(if s (let [s (seq coll)]
(reductions f (first s) (rest s)) (if s
(list (f))))) (reductions f (first s) (rest s))
(list (f))))))
([f init coll] ([f init coll]
(loop [acc init xs (seq coll) out [init]] (letfn [(step [acc s]
(if xs (cons acc
(let [a (f acc (first xs))] (recur a (next xs) (conj out a))) (lazy-seq
out)))) (let [s (seq s)]
(when s
(step (f acc (first s)) (rest s)))))))]
(step init coll))))
;; The lazy tree-seq (using lazy-seq/make-lazy-seq) correctly implements ;; Lazy pre-order DFS (matches Clojure). letfn-bound walk (not (fn walk …)) so it
;; Clojure semantics but triggers compile-mode issues in self-hosted compilation. ;; compiles cleanly in the overlay under :compile? — see jolt-r81.
;; When compile mode is fixed, replace the eager version below with:
;; (defn tree-seq [branch? children root]
;; (let [walk (fn walk [node]
;; (lazy-seq
;; (cons node
;; (when (branch? node)
;; (mapcat walk (children node))))))]
;; (walk root)))
(defn tree-seq [branch? children root] (defn tree-seq [branch? children root]
(let [walk (fn walk [acc node] (letfn [(walk [node]
(let [acc (conj acc node)] (lazy-seq
(if (branch? node) (cons node
(reduce walk acc (children node)) (when (branch? node)
acc)))] (mapcat walk (children node))))))]
(walk [] root))) (walk root)))
;; Canonical flatten via tree-seq: the leaves (non-sequential nodes) in order. ;; Canonical flatten via tree-seq: the leaves (non-sequential nodes) in order.
;; Flattens lists too (sequential?), matching Clojure/CLJS. ;; Flattens lists too (sequential?), matching Clojure/CLJS.
@ -191,19 +189,29 @@
(defn xml-seq [root] (defn xml-seq [root]
(tree-seq (complement string?) (comp seq :content) root)) (tree-seq (complement string?) (comp seq :content) root))
;; Eager interleave: round-robin one element from each coll until any exhausts. ;; Lazy interleave: round-robin one element from each coll until any exhausts.
;; A lazy version (canonical Clojure cons-recursion) hits the same compile-mode ;; letfn-bound recursion (not top-level self-call) so the lazy-seq body compiles
;; overlay bug as reductions/tree-seq — a self-recursive lazy-seq leaks its macro ;; cleanly in the overlay — see jolt-r81.
;; expansion under :compile? (see jolt-r81). Eager until that's fixed. (defn interleave
(defn interleave [& colls] ([] ())
(if (empty? colls) ([c1] (lazy-seq c1))
(list) ([c1 c2]
(let [cs (mapv vec colls) (letfn [(step [s1 s2]
n (apply min (map count cs))] (lazy-seq
(loop [i 0 out []] (let [s1 (seq s1) s2 (seq s2)]
(if (< i n) (when (and s1 s2)
(recur (inc i) (reduce (fn [o c] (conj o (nth c i))) out cs)) (cons (first s1)
out))))) (cons (first s2)
(step (rest s1) (rest s2))))))))]
(step c1 c2)))
([c1 c2 & cs]
(letfn [(step [ss]
(lazy-seq
(let [ss (map seq ss)]
(when (every? identity ss)
(concat (map first ss)
(step (map rest ss)))))))]
(step (list* c1 c2 cs)))))
;; No ratio type on Jolt, so rationalize is identity. ;; No ratio type on Jolt, so rationalize is identity.
(defn rationalize [x] x) (defn rationalize [x] x)

View file

@ -47,8 +47,9 @@
["first filter even? drop range" "4" "(first (filter even? (drop 3 (range))))"] ["first filter even? drop range" "4" "(first (filter even? (drop 3 (range))))"]
["take 3 remove odd? range" "(quote (0 2 4))" "(take 3 (remove odd? (range)))"] ["take 3 remove odd? range" "(quote (0 2 4))" "(take 3 (remove odd? (range)))"]
["take 3 drop-while <5 range" "(quote (5 6 7))" "(take 3 (drop-while (fn [x] (< x 5)) (range)))"] ["take 3 drop-while <5 range" "(quote (5 6 7))" "(take 3 (drop-while (fn [x] (< x 5)) (range)))"]
# interleave stays eager in overlay (lazy-seq macro breaks compile mode). ["take 4 interleave range iterate" "(quote (0 10 1 11))" "(take 4 (interleave (range) (iterate inc 10)))"]
# ["take 4 interleave range iterate" "(quote (0 10 1 11))" "(take 4 (interleave (range) (iterate inc 10)))"] ["take 4 reductions + range" "(quote (0 1 3 6))" "(take 4 (reductions + (range)))"]
["take 3 tree-seq infinite" "(quote (0 0 0))" "(take 3 (tree-seq (fn [_] true) (fn [n] [n]) 0))"]
["take 3 partition 2 range" "(quote ((0 1) (2 3) (4 5)))" "(take 3 (partition 2 (range)))"] ["take 3 partition 2 range" "(quote ((0 1) (2 3) (4 5)))" "(take 3 (partition 2 (range)))"]
["take 3 partition-all 2 range" "(quote ((0 1) (2 3) (4 5)))" "(take 3 (partition-all 2 (range)))"] ["take 3 partition-all 2 range" "(quote ((0 1) (2 3) (4 5)))" "(take 3 (partition-all 2 (range)))"]
["take 3 map-indexed vector range" "(quote ([0 0] [1 1] [2 2]))" "(take 3 (map-indexed vector (range)))"] ["take 3 map-indexed vector range" "(quote ([0 0] [1 1] [2 2]))" "(take 3 (map-indexed vector (range)))"]