core: fix jolt-r81 at root — move lazy-seq/lazy-cat to the early syntax tier
Root cause: lazy-seq/lazy-cat were defined in 30-macros, which loads AFTER the seq/coll tiers (10-seq, 20-coll) that use them. In compile mode a tier's forms are compiled as the tier loads, so (lazy-seq …) in those tiers was compiled when lazy-seq was not yet a registered macro — i.e. as a CALL to the macro-as-function, which at runtime returns its own expansion `(make-lazy-seq (fn* [] …))` as data. That leaked form then flowed into ops like `odd?` (partition-by) → type errors, or silently produced wrong structure. Interpret/self-host masked it (expand at call time); the eager fallbacks and the earlier letfn versions masked it by falling back to the interpreter. Fix: define lazy-seq/lazy-cat in 00-syntax (loaded first), exactly as when-let already is for the same reason. They use only seed fns (make-lazy-seq/coll->cells/ concat) + map. With the macro registered early, the seq/coll tiers compile (lazy-seq …) correctly. With the root fixed, interleave/reductions/tree-seq drop their letfn workarounds and use the canonical recursive Clojure forms (top-level / fn-self-name recursion inside lazy-seq), verified leak-free in compile mode with strict probes. Regression guards added: partition-by with odd? (the strict pred that exposed the leak; the prior case used identity which masked it), reductions over an infinite range, tree-seq summed through a strict filter — all ×3 modes. Gate: conformance 249x3, lazy-infinite 40/40, fixpoint, self-host, specs+unit green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f8bf384b93
commit
297d92fbb8
4 changed files with 46 additions and 42 deletions
|
|
@ -330,3 +330,16 @@
|
||||||
(let [form (bindings 0) tst (bindings 1)]
|
(let [form (bindings 0) tst (bindings 1)]
|
||||||
`(let [temp# ~tst]
|
`(let [temp# ~tst]
|
||||||
(if temp# (let [~form temp#] ~@body) nil))))
|
(if temp# (let [~form temp#] ~@body) nil))))
|
||||||
|
|
||||||
|
;; lazy-seq / lazy-cat live here (not 30-macros) because the seq/coll tiers use
|
||||||
|
;; them and compile-as-they-load: the macro must be registered before those tiers
|
||||||
|
;; or (lazy-seq …) compiles to a call of the macro-as-function and leaks its
|
||||||
|
;; expansion at runtime (jolt-r81). They use only seed fns (make-lazy-seq/
|
||||||
|
;; coll->cells/concat) + map, all available from the start.
|
||||||
|
;; lazy-seq defers its body: make-lazy-seq holds a thunk that realizes the body
|
||||||
|
;; to cells when forced. lazy-cat wraps each coll in a lazy-seq and concats.
|
||||||
|
(defmacro lazy-seq [& body]
|
||||||
|
`(make-lazy-seq (fn* [] (coll->cells (do ~@body)))))
|
||||||
|
|
||||||
|
(defmacro lazy-cat [& colls]
|
||||||
|
`(concat ~@(map (fn [c] `(lazy-seq ~c)) colls)))
|
||||||
|
|
|
||||||
|
|
@ -151,9 +151,7 @@
|
||||||
(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)))
|
||||||
|
|
||||||
;; Lazy: the running accumulators, one at a time (matches Clojure). Recursion is
|
;; Lazy: the running accumulators, one at a time (matches Clojure).
|
||||||
;; 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]
|
||||||
(lazy-seq
|
(lazy-seq
|
||||||
|
|
@ -162,22 +160,19 @@
|
||||||
(reductions f (first s) (rest s))
|
(reductions f (first s) (rest s))
|
||||||
(list (f))))))
|
(list (f))))))
|
||||||
([f init coll]
|
([f init coll]
|
||||||
(letfn [(step [acc s]
|
(cons init
|
||||||
(cons acc
|
(lazy-seq
|
||||||
(lazy-seq
|
(when-let [s (seq coll)]
|
||||||
(let [s (seq s)]
|
(reductions f (f init (first s)) (rest s)))))))
|
||||||
(when s
|
|
||||||
(step (f acc (first s)) (rest s)))))))]
|
|
||||||
(step init coll))))
|
|
||||||
|
|
||||||
;; Lazy pre-order DFS (matches Clojure). letfn-bound walk (not (fn walk …)) so it
|
;; Lazy pre-order DFS (matches Clojure): node, then its children's walks spliced
|
||||||
;; compiles cleanly in the overlay under :compile? — see jolt-r81.
|
;; via the (now lazy) mapcat.
|
||||||
(defn tree-seq [branch? children root]
|
(defn tree-seq [branch? children root]
|
||||||
(letfn [(walk [node]
|
(let [walk (fn walk [node]
|
||||||
(lazy-seq
|
(lazy-seq
|
||||||
(cons node
|
(cons node
|
||||||
(when (branch? node)
|
(when (branch? node)
|
||||||
(mapcat walk (children node))))))]
|
(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.
|
||||||
|
|
@ -190,28 +185,22 @@
|
||||||
(tree-seq (complement string?) (comp seq :content) root))
|
(tree-seq (complement string?) (comp seq :content) root))
|
||||||
|
|
||||||
;; Lazy interleave: round-robin one element from each coll until any exhausts.
|
;; Lazy interleave: round-robin one element from each coll until any exhausts.
|
||||||
;; letfn-bound recursion (not top-level self-call) so the lazy-seq body compiles
|
|
||||||
;; cleanly in the overlay — see jolt-r81.
|
|
||||||
(defn interleave
|
(defn interleave
|
||||||
([] ())
|
([] ())
|
||||||
([c1] (lazy-seq c1))
|
([c1] (lazy-seq c1))
|
||||||
([c1 c2]
|
([c1 c2]
|
||||||
(letfn [(step [s1 s2]
|
(lazy-seq
|
||||||
(lazy-seq
|
(let [s1 (seq c1) s2 (seq c2)]
|
||||||
(let [s1 (seq s1) s2 (seq s2)]
|
(when (and s1 s2)
|
||||||
(when (and s1 s2)
|
(cons (first s1)
|
||||||
(cons (first s1)
|
(cons (first s2)
|
||||||
(cons (first s2)
|
(interleave (rest s1) (rest s2))))))))
|
||||||
(step (rest s1) (rest s2))))))))]
|
|
||||||
(step c1 c2)))
|
|
||||||
([c1 c2 & cs]
|
([c1 c2 & cs]
|
||||||
(letfn [(step [ss]
|
(lazy-seq
|
||||||
(lazy-seq
|
(let [ss (map seq (list* c1 c2 cs))]
|
||||||
(let [ss (map seq ss)]
|
(when (every? identity ss)
|
||||||
(when (every? identity ss)
|
(concat (map first ss)
|
||||||
(concat (map first ss)
|
(apply interleave (map rest 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)
|
||||||
|
|
|
||||||
|
|
@ -219,11 +219,8 @@
|
||||||
~@(map (fn [g] (impl (first g) (rest g))) (group-by-head body)))))
|
~@(map (fn [g] (impl (first g) (rest g))) (group-by-head body)))))
|
||||||
|
|
||||||
;; --- laziness --------------------------------------------------------------
|
;; --- laziness --------------------------------------------------------------
|
||||||
;; lazy-seq defers its body: make-lazy-seq holds a thunk that, when forced,
|
;; lazy-seq / lazy-cat moved to the 00-syntax tier: the seq/coll tiers (10-seq,
|
||||||
;; realizes the body to cells. lazy-cat wraps each coll in a lazy-seq and concats
|
;; 20-coll) use lazy-seq, and in compile mode a tier's forms are compiled as it
|
||||||
;; (concat is itself lazy, so no outer wrapping needed).
|
;; loads — so the macro must be registered BEFORE those tiers, else (lazy-seq …)
|
||||||
(defmacro lazy-seq [& body]
|
;; compiles as a call to the macro-as-function and leaks its expansion at runtime
|
||||||
`(make-lazy-seq (fn* [] (coll->cells (do ~@body)))))
|
;; (jolt-r81). They only need seed fns (make-lazy-seq/coll->cells/concat).
|
||||||
|
|
||||||
(defmacro lazy-cat [& colls]
|
|
||||||
`(concat ~@(map (fn [c] `(lazy-seq ~c)) colls)))
|
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,11 @@
|
||||||
["reductions" "(quote (1 3 6 10))" "(reductions + [1 2 3 4])"]
|
["reductions" "(quote (1 3 6 10))" "(reductions + [1 2 3 4])"]
|
||||||
["reductions init" "(quote (0 1 3 6))" "(reductions + 0 [1 2 3])"]
|
["reductions init" "(quote (0 1 3 6))" "(reductions + 0 [1 2 3])"]
|
||||||
["dedupe" "(quote (1 2 3 1))" "(dedupe [1 1 2 3 3 1])"]
|
["dedupe" "(quote (1 2 3 1))" "(dedupe [1 1 2 3 3 1])"]
|
||||||
|
# partition-by with a strict pred (odd?) — guards jolt-r81: a lazy overlay fn
|
||||||
|
# whose lazy-seq leaked its expansion in compile mode passed a non-int to odd?.
|
||||||
|
["partition-by odd?" "(quote ((1 1) (2) (3 3)))" "(partition-by odd? [1 1 2 3 3])"]
|
||||||
|
["reductions inf" "(quote (0 1 3 6))" "(take 4 (reductions + (range)))"]
|
||||||
|
["tree-seq strict" "10" "(reduce + 0 (filter (complement coll?) (tree-seq coll? seq [1 [2 [3 4]]])))"]
|
||||||
["keep-indexed" "(quote (:b :d))" "(keep-indexed (fn [i x] (if (odd? i) x)) [:a :b :c :d])"]
|
["keep-indexed" "(quote (:b :d))" "(keep-indexed (fn [i x] (if (odd? i) x)) [:a :b :c :d])"]
|
||||||
["map-indexed" "(quote ([0 :a] [1 :b]))" "(map-indexed (fn [i x] [i x]) [:a :b])"]
|
["map-indexed" "(quote ([0 :a] [1 :b]))" "(map-indexed (fn [i x] [i x]) [:a :b])"]
|
||||||
["trampoline" ":done" "(do (defn a [n] (if (zero? n) :done (fn [] (a (dec n))))) (trampoline a 5))"]
|
["trampoline" ":done" "(do (defn a [n] (if (zero? n) :done (fn [] (a (dec n))))) (trampoline a 5))"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue