seq fns are lazy by default, like Clojure (LazySeq, not eager-headed)

map/filter/remove/take/drop/concat/take-while/drop-while/mapcat/partition
built an eager-headed cseq: the first element (and the fn application) ran
at construction, so a side-effecting (map f coll) fired f immediately and
(class (map …)) was PersistentList instead of LazySeq. This diverged from
Clojure, which wraps the whole body in lazy-seq. It went unnoticed because
the conformance gate certifies values, not realization — eager and lazy
heads produce identical values — and unit.edn even baked PersistentList in
as expected. test.check's for-all-takes-multiple-expressions (which counts
side effects in a for-all body) exposed it.

Wrap each native producer's result in a lazy-seq node so the body, incl.
the first element, defers until forced — the forced cseq still has eager
heads, so reduce/count/dorun/etc. force on walk and there's no per-element
cost. dedupe's (seq coll) is moved inside its lazy-seq. A jolt LazySeq is
now recognized by coll?/empty, the analyzer's form predicates (a macro can
build its expansion with map), value-host-tags + instance? (LazySeq/ISeq/
Sequential), and reports clojure.lang.LazySeq.

Kept the native Scheme implementations rather than porting Clojure's: a
straight lazy-seq+cons port is 3x slower and Clojure's chunked fast path is
288x slower because jolt's chunk machinery is unoptimized (filed jolt-j9dz);
the wrapped natives are Clojure-lazy at native speed.

+12 corpus rows (laziness at construction, LazySeq type, both JVM-certified).
make test + shakesmoke green, selfhost holds, 0 new divergences.
This commit is contained in:
Yogthos 2026-06-28 00:16:47 -04:00
parent 92368b49f1
commit b879430618
10 changed files with 80 additions and 40 deletions

View file

@ -766,6 +766,15 @@
{:suite "lazy / construction & laziness" :label "not eagerly evaluated" :expected "0" :actual "(let [c (atom 0)] (lazy-seq (swap! c inc) nil) @c)"}
{:suite "lazy / construction & laziness" :label "realized on demand" :expected "1" :actual "(let [c (atom 0) s (lazy-seq (swap! c inc) [1])] (first s) @c)"}
{:suite "lazy / construction & laziness" :label "lazy-cat" :expected "[0 1 2 3]" :actual "(lazy-cat [0 1] [2 3])"}
{:suite "lazy / seq fns are lazy (no side effect at construction)" :label "map" :expected "0" :actual "(let [a (atom 0)] (map (fn [x] (swap! a inc) x) (range 5)) @a)"}
{:suite "lazy / seq fns are lazy (no side effect at construction)" :label "filter" :expected "0" :actual "(let [a (atom 0)] (filter (fn [x] (swap! a inc) true) (range 5)) @a)"}
{:suite "lazy / seq fns are lazy (no side effect at construction)" :label "remove" :expected "0" :actual "(let [a (atom 0)] (remove (fn [x] (swap! a inc) false) (range 5)) @a)"}
{:suite "lazy / seq fns are lazy (no side effect at construction)" :label "take over a lazy source" :expected "0" :actual "(let [a (atom 0)] (take 3 (map (fn [x] (swap! a inc) x) (range 100))) @a)"}
{:suite "lazy / seq fns are lazy (no side effect at construction)" :label "drop over a lazy source" :expected "0" :actual "(let [a (atom 0)] (drop 2 (map (fn [x] (swap! a inc) x) (range 100))) @a)"}
{:suite "lazy / seq fns are lazy (no side effect at construction)" :label "concat over lazy sources" :expected "0" :actual "(let [a (atom 0)] (concat (map (fn [x] (swap! a inc) x) [1 2]) (map (fn [x] (swap! a inc) x) [3 4])) @a)"}
{:suite "lazy / seq fns are lazy (no side effect at construction)" :label "take-while" :expected "0" :actual "(let [a (atom 0)] (take-while (fn [x] (swap! a inc) true) (range 5)) @a)"}
{:suite "lazy / seq fns are lazy (no side effect at construction)" :label "partition over a lazy source" :expected "0" :actual "(let [a (atom 0)] (partition 2 (map (fn [x] (swap! a inc) x) (range 6))) @a)"}
{:suite "lazy / result type is LazySeq" :label "map/filter/take/concat/mapcat are LazySeq" :expected "[true true true true true]" :actual "(mapv #(instance? clojure.lang.LazySeq %) [(map inc [1]) (filter odd? [1]) (take 1 [1]) (concat [1]) (mapcat list [1])])"}
{:suite "lazy / construction & laziness" :label "doall forces" :expected "[2 3 4]" :actual "(doall (map inc [1 2 3]))"}
{:suite "lazy / construction & laziness" :label "dorun returns nil" :expected "nil" :actual "(dorun (map inc [1 2 3]))"}
{:suite "lazy / infinite" :label "take from repeat" :expected "[7 7 7]" :actual "(take 3 (repeat 7))"}

View file

@ -475,10 +475,10 @@
{:suite "type" :expr "(type '(1 2))" :expected "clojure.lang.PersistentList"}
{:suite "type" :expr "(type '())" :expected "clojure.lang.PersistentList$EmptyList"}
{:suite "type" :expr "(type (first {:a 1}))" :expected "clojure.lang.PersistentVector"}
{:suite "type" :expr "(type (map inc [1 2]))" :expected "clojure.lang.PersistentList"}
{:suite "type" :expr "(type (filter odd? [1 2 3]))" :expected "clojure.lang.PersistentList"}
{:suite "type" :expr "(type (map inc [1 2]))" :expected "clojure.lang.LazySeq"}
{:suite "type" :expr "(type (filter odd? [1 2 3]))" :expected "clojure.lang.LazySeq"}
{:suite "type" :expr "(type (lazy-seq (cons 1 nil)))" :expected "clojure.lang.LazySeq"}
{:suite "type" :expr "(type (take 2 (iterate inc 0)))" :expected "clojure.lang.PersistentList"}
{:suite "type" :expr "(type (take 2 (iterate inc 0)))" :expected "clojure.lang.LazySeq"}
{:suite "type" :expr "(type inc)" :expected "clojure.core$inc"}
{:suite "type" :expr "(type (sorted-map :a 1))" :expected ":map"}
{:suite "type" :expr "(type (sorted-set 1))" :expected ":jolt/sorted-set"}