jolt/test/spec/lazy-seqs-spec.janet
Yogthos 4a1a9e3aec core: lazy realization is shared across walks (once-only effects); pmap family
Every walk over a lazy seq created FRESH wrapper tables around the shared
rest-thunks (ls-rest, ls-seq/ls-count, realize-for-iteration, the printers,
reduce — each had its own make-lazy-seq loop), so independent walks re-ran
the thunks: side effects duplicated, and a doall'd seq of futures was
re-spawned serially by the deref walk. Every walker now goes through
ls-rest-cached, which memoizes the rest wrapper on its node — thunks run
exactly once, as in Clojure. Costs ~10% on walk-heavy benches (the per-node
cache get/put — Clojure's LazySeq pays the same); net still -9% vs the
pre-linear-walks baseline. Three regression rows pin once-only effects and
value stability across walks.

On top of that: pmap/pcalls/pvalues (jolt-oeu) over the real-thread futures
— spawn-all-then-deref (the once-only fix is what makes the doall actually
mean that), snapshot semantics documented, multi-coll arity via the
canonical vector-zip. System/currentTimeMillis + nanoTime land as System
statics (the realtime clock — os/time is whole seconds, which quantized
every elapsed measurement to 1000ms). Seven pmap rows incl. a generous-
margin parallelism check (4 x 200ms sleeps under 700ms after warmup).
2026-06-10 19:14:49 -04:00

44 lines
2.6 KiB
Text

# Specification: lazy sequences.
(use ../support/harness)
(defspec "lazy / construction & laziness"
["lazy-seq value" "[1 2 3]" "(take 3 (lazy-seq (cons 1 (lazy-seq (cons 2 (lazy-seq (cons 3 nil)))))))"]
["not eagerly evaluated" "0" "(let [c (atom 0)] (lazy-seq (swap! c inc) nil) @c)"]
["realized on demand" "1" "(let [c (atom 0) s (lazy-seq (swap! c inc) [1])] (first s) @c)"]
["lazy-cat" "[0 1 2 3]" "(lazy-cat [0 1] [2 3])"]
["doall forces" "[2 3 4]" "(doall (map inc [1 2 3]))"]
["dorun returns nil" "nil" "(dorun (map inc [1 2 3]))"])
(defspec "lazy / infinite"
["take from repeat" "[7 7 7]" "(take 3 (repeat 7))"]
["take from iterate" "[1 2 4 8]" "(take 4 (iterate (fn [x] (* 2 x)) 1))"]
["take from cycle" "[1 2 1 2]" "(take 4 (cycle [1 2]))"]
["take from range" "[0 1 2]" "(take 3 (range))"]
["drop then take" "[5 6 7]" "(take 3 (drop 5 (range)))"]
["filter infinite" "[0 2 4]" "(take 3 (filter even? (range)))"]
["map infinite" "[0 1 4]" "(take 3 (map (fn [x] (* x x)) (range)))"]
["nth of infinite" "100" "(nth (range) 100)"])
(defspec "lazy / self-referential"
["self-ref ones" "[1 1 1 1 1]" "(do (def ones (lazy-seq (cons 1 ones))) (take 5 ones))"]
["self-ref nats" "[0 1 2 3 4]" "(do (def nats (lazy-cat [0] (map inc nats))) (take 5 nats))"]
["self-ref fib" "[0 1 1 2 3 5 8 13 21 34]"
"(do (def fib (lazy-cat [0 1] (map + (rest fib) fib))) (take 10 fib))"])
(defspec "lazy / realized?"
["unrealized" "false" "(realized? (lazy-seq (cons 1 nil)))"]
["realized after" "true" "(let [s (lazy-seq (cons 1 nil))] (first s) (realized? s))"]
["body runs once" "1" "(let [c (atom 0) s (lazy-seq (do (swap! c inc) [1 2 3]))] (seq s) (seq s) @c)"])
# Independent walks over the SAME lazy seq share realization: each node's rest
# wrapper is memoized (ls-rest-cached), so the shared rest-thunks run exactly
# once. Pre-fix, every walk after the first re-ran the thunks — duplicating
# side effects, and a doall'd seq of futures re-spawned them serially on the
# deref walk (which is how it surfaced, via pmap).
(defspec "lazy-seq / realization is shared across walks"
["effects run once across three walks" "3"
"(let [a (atom 0) s (map (fn [x] (swap! a inc) x) [1 2 3])] (doall s) (dorun s) (vec s) (deref a))"]
["values stable across walks" "true"
"(let [s (map inc [1 2 3])] (= (vec s) (vec s) [2 3 4]))"]
["filter effects once" "4"
"(let [a (atom 0) s (filter (fn [x] (swap! a inc) (odd? x)) [1 2 3 4])] (dorun s) (count s) (deref a))"])