Step 4: evaluator eager assumptions — lazy rest + lazy mapcat

Fix & rest destructuring (evaluator.janet): when the original
value is a lazy-seq, derive & rest by walking ls-rest vi steps
instead of slicing the eagerly-realized array from d-realize.

Rewrite core-mapcat as a lazy state machine (core.janet): step
through input colls one element at a time, call f on each tuple,
then yield from f's result collection. No apply-forcing —
all input colls are walked lazily via lazy-from + seq-done?.

Add mapcat to core-renames (compiler.janet) and remove from
Clojure overlay (10-seq.clj) — compile mode now emits direct
calls to the lazy core-mapcat, fixing the pre-existing
protocol-on-record compile error.

Tests: re-enabled mapcat infinite harness case, added 2 & rest
laziness cases. 21/21 lazy-infinite, 229x3 conformance, 32/32 specs.
This commit is contained in:
Yogthos 2026-06-08 10:46:42 -04:00
parent ff8ffb8cd6
commit 42da5cef9a
5 changed files with 60 additions and 26 deletions

View file

@ -22,8 +22,3 @@
(if (next s)
(recur (conj ret (first s)) (next s))
(seq ret))))
(defn mapcat
([f] (comp (map f) cat))
([f & colls]
(apply concat (apply map f colls))))

View file

@ -97,6 +97,7 @@
"take-while" "core-take-while"
"drop-while" "core-drop-while"
"nth" "core-nth"
"mapcat" "core-mapcat"
"list" "core-list"
"name" "core-name"
"subs" "core-subs"

View file

@ -1134,17 +1134,44 @@
(each x (realize-for-iteration (f (a 1)))
(set acc (rf acc x)))
acc))))
# collection arity: map f over colls, then concatenate. A non-seqable
# result counts as a single element (this leniency is what jolt's `for`
# expansion relies on for :let on the last binding, whose body yields a
# scalar rather than a seq).
(let [mapped (realize-for-iteration (core-apply core-map f colls))
seqs (map (fn [item]
(if (or (tuple? item) (array? item) (pvec? item)
(lazy-seq? item) (set? item))
item (tuple item)))
mapped)]
(core-apply core-concat seqs))))
# collection arity: direct lazy implementation. Pull one element
# from each input coll, apply f, then yield elements from f's result.
# No apply-forcing — walk input colls lazily element-by-element.
(do
(var n (length colls))
(var init-cs @[])
(var i 0)
(while (< i n)
(array/push init-cs (lazy-from (in colls i)))
(++ i))
(defn step [cs res]
(fn []
(var cursors cs) (var cur-res res) (var hit nil) (var ok false)
(while (not ok)
(if (nil? cur-res)
(do
(var args @[]) (var next-cs @[]) (var exhausted false) (var j 0)
(while (and (< j n) (not exhausted))
(let [c (in cursors j)]
(if (seq-done? c) (set exhausted true)
(do
(array/push args (ls-first c))
(array/push next-cs (ls-rest c)))))
(++ j))
(if exhausted (break))
(let [r (apply f args)]
(set cursors next-cs)
(set cur-res (if (or (nil? r) (tuple? r) (array? r)
(lazy-seq? r) (pvec? r) (set? r) (plist? r))
(lazy-from r)
(lazy-from (tuple r))))))
(if (seq-done? cur-res)
(set cur-res nil)
(let [val (ls-first cur-res) rest (ls-rest cur-res)]
(set hit @[val (step cursors rest)])
(set ok true)))))
(if ok hit nil)))
(make-lazy-seq (step init-cs nil)))))
(defn core-reverse [coll]
(if (nil? coll) @[]

View file

@ -549,14 +549,24 @@
(while (< di n)
(let [elem (in pat di)]
(cond
# & rest
(and (struct? elem) (= :symbol (elem :jolt/type)) (= "&" (elem :name)))
(do
# rest binds a seq (jolt list = array), per Clojure semantics
(destructure-bind ctx bindings (in pat (+ di 1))
(if (and seqable? (< vi (length rv)))
(array/slice (if (tuple? rv) (array/slice rv) rv) vi)
@[]))
# & rest
(and (struct? elem) (= :symbol (elem :jolt/type)) (= "&" (elem :name)))
(do
# rest binds a seq (jolt list = array), per Clojure semantics.
# For lazy-seqs, preserve laziness: walk vi steps via ls-rest
# instead of slicing the eagerly-realized array.
(destructure-bind ctx bindings (in pat (+ di 1))
(if (lazy-seq? val)
(do
(var c val) (var i 0)
(while (< i vi)
(let [nxt (ls-rest c)]
(if (nil? nxt) (break)
(do (set c nxt) (++ i)))))
c)
(if (and seqable? (< vi (length rv)))
(array/slice (if (tuple? rv) (array/slice rv) rv) vi)
@[])))
(set di (+ di 2)))
# :as whole
(= elem :as)

View file

@ -52,8 +52,9 @@
["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 distinct cycle" "(quote (1 2 3))" "(take 3 (distinct (cycle [1 2 1 3 1])))"]
# mapcat on infinite inputs hangs (apply forces lazy result, needs Step 4).
# ["take 6 mapcat dup range" "(quote (0 0 1 1 2 2))" "(take 6 (mapcat (fn [x] [x x]) (range)))"]
["take 6 mapcat dup range" "(quote (0 0 1 1 2 2))" "(take 6 (mapcat (fn [x] [x x]) (range)))"]
["first rest lazy" "1" "(let [[a & r] (range)] (first r))"]
["take 3 rest lazy" "(quote (1 2 3))" "(let [[a & r] (range)] (take 3 r))"]
["take 3 take-nth 2 range" "(quote (0 2 4))" "(take 3 (take-nth 2 (range)))"]
["take 3 interpose :x range" "(quote (0 :x 1))" "(take 3 (interpose :x (range)))"]
["take 3 map vector range iterate" "(quote ([0 100] [1 101] [2 102]))" "(take 3 (map vector (range) (iterate inc 100)))"]