core: Phase 5 Option A — remaining lazy transformers + nth falsy-element fix

Converts the rest of the lazy transformers to return a LazySeq even over a
concrete collection (dropping the eager "preserve representation" branch, which
returned a vector over vector input): drop, distinct, partition, partition-all,
map-indexed, keep, keep-indexed, take-nth, interpose. Each collapses to its
existing lazy branch run over (lazy-from coll); transducer arities unchanged.

Fixes a latent nth bug exposed by this: core-nth's lazy branch walked with
(ls-first cur) truthiness as the end-of-seq test, so a legitimate false/nil
element was mistaken for the end — (nth (map identity [false 1 2]) 0) threw
instead of returning false, and cond-> (whose macro nths over a now-lazy
(drop 2 clauses) containing the boolean clause tests) failed. nth now walks
with seq-done? and reads via core-first.

Gate: conformance 246x3 (+7 cases), lazy-infinite 18/18, fixpoint, self-host,
all specs+unit green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yogthos 2026-06-08 13:56:57 -04:00
parent 074eb2da41
commit fa36488dad
2 changed files with 116 additions and 158 deletions

View file

@ -1035,17 +1035,15 @@
(defn core-drop [n & rest] (defn core-drop [n & rest]
(if (= 0 (length rest)) (td-drop n) (if (= 0 (length rest)) (td-drop n)
(let [coll (in rest 0)] (let [coll (in rest 0)]
(if (lazy-seq? coll) # Option A: lazy drop — skip n (forcing only those), return the lazy tail.
(do (make-lazy-seq
(var cur coll) (fn []
(var cur (lazy-from coll))
(var i 0) (var i 0)
(while (and (< i n) (ls-first cur)) (while (and (< i n) (not (seq-done? cur)))
(set cur (ls-rest cur)) (set cur (core-rest cur))
(++ i)) (++ i))
(if (nil? (ls-first cur)) nil cur)) (coll->cells cur))))))
(let [c (realize-for-iteration coll)
dropped (array/slice c (min n (length c)))]
(if (jvec? coll) (make-vec dropped) dropped))))))
# ffirst/nfirst/fnext/nnext/last/butlast (seq tier) and second/peek/subvec/mapv/ # ffirst/nfirst/fnext/nnext/last/butlast (seq tier) and second/peek/subvec/mapv/
# update (kernel tier) now live in the Clojure clojure.core tiers under # update (kernel tier) now live in the Clojure clojure.core tiers under
@ -1207,13 +1205,16 @@
(pv-nth coll idx) (pv-nth coll idx)
(oob (pv-count coll))) (oob (pv-count coll)))
(if (lazy-seq? coll) (if (lazy-seq? coll)
# Walk with seq-done?, NOT (ls-first cur): a lazy element may legitimately be
# false or nil, which truthiness would mistake for end-of-seq.
(if (< idx 0) (oob 0)
(do (do
(var cur coll) (var cur coll)
(var i 0) (var i 0)
(while (and (< i idx) (ls-first cur)) (while (and (< i idx) (not (seq-done? cur)))
(set cur (ls-rest cur)) (set cur (core-rest cur))
(++ i)) (++ i))
(if (ls-first cur) (ls-first cur) (oob idx))) (if (seq-done? cur) (oob i) (core-first cur))))
(do (do
(var c (realize-for-iteration coll)) (var c (realize-for-iteration coll))
(if (and (>= idx 0) (< idx (length c))) (if (and (>= idx 0) (< idx (length c)))
@ -1251,29 +1252,20 @@
(tuple/slice (tuple ;arr)))))) (tuple/slice (tuple ;arr))))))
(defn core-distinct [coll] (defn core-distinct [coll]
(if (nil? coll) @[] # Option A: always lazy. seen-set is captured once and shared across the chain.
(if (lazy-seq? coll) (let [seen @{}]
(do
(var seen @{})
(defn dstep [c] (defn dstep [c]
(fn [] (fn []
(var cur c) (var found false) (var result nil) (var cur c) (var found false) (var result nil)
(while (and (not found) (not (seq-done? cur))) (while (and (not found) (not (seq-done? cur)))
(let [x (ls-first cur)] (let [x (core-first cur)]
(set cur (ls-rest cur)) (set cur (core-rest cur))
(when (nil? (seen x)) (when (nil? (seen x))
(put seen x true) (put seen x true)
(set found true) (set found true)
(set result x)))) (set result x))))
(if found @[result (dstep cur)] nil))) (if found @[result (dstep cur)] nil)))
(make-lazy-seq (dstep coll))) (make-lazy-seq (dstep (lazy-from coll)))))
(do
(var seen @{})
(var result @[])
(each x (realize-for-iteration coll)
(if (nil? (seen x))
(do (put seen x true) (array/push result x))))
(if (jvec? coll) (make-vec result) result)))))
# group-by / frequencies now live in the Clojure collection tier # group-by / frequencies now live in the Clojure collection tier
# (core/20-coll.clj). # (core/20-coll.clj).
@ -1285,30 +1277,21 @@
(let [has-step (> (length rest) 1) (let [has-step (> (length rest) 1)
step (if has-step (first rest) n) step (if has-step (first rest) n)
coll (if has-step (in rest 1) (first rest))] coll (if has-step (in rest 1) (first rest))]
(if (lazy-seq? coll) # Option A: always lazy.
(do
(defn pstep [c] (defn pstep [c]
(fn [] (fn []
(if (seq-done? c) nil (if (seq-done? c) nil
(do (do
(var part @[]) (var cur c) (var i 0) (var part @[]) (var cur c) (var i 0)
(while (and (< i n) (not (seq-done? cur))) (while (and (< i n) (not (seq-done? cur)))
(array/push part (ls-first cur)) (array/push part (core-first cur))
(set cur (ls-rest cur)) (set cur (core-rest cur))
(++ i)) (++ i))
(if (= i n) (if (= i n)
(let [next-cur (if (= step n) cur (core-drop (- step n) cur))] (let [next-cur (if (= step n) cur (lazy-from (core-drop (- step n) cur)))]
@[(tuple/slice (tuple ;part)) (pstep next-cur)]) @[(tuple/slice (tuple ;part)) (pstep next-cur)])
nil))))) nil)))))
(make-lazy-seq (pstep coll))) (make-lazy-seq (pstep (lazy-from coll)))))
(let [c (realize-for-iteration coll)]
(var result @[]) (var i 0)
(while (<= (+ i n) (length c))
(var part @[]) (var j 0)
(while (< j n) (array/push part (in c (+ i j))) (++ j))
(array/push result (tuple/slice (tuple ;part)))
(+= i step))
result))))
(defn core-partition-by [f coll] (defn core-partition-by [f coll]
(def f (as-fn f)) (def f (as-fn f))
@ -1327,65 +1310,45 @@
result) result)
(defn core-partition-all [n coll] (defn core-partition-all [n coll]
(if (lazy-seq? coll) # Option A: always lazy.
(do
(defn pstep [c] (defn pstep [c]
(fn [] (fn []
(if (seq-done? c) nil (if (seq-done? c) nil
(do (do
(var part @[]) (var cur c) (var i 0) (var part @[]) (var cur c) (var i 0)
(while (and (< i n) (not (seq-done? cur))) (while (and (< i n) (not (seq-done? cur)))
(array/push part (ls-first cur)) (array/push part (core-first cur))
(set cur (ls-rest cur)) (set cur (core-rest cur))
(++ i)) (++ i))
@[(tuple/slice (tuple ;part)) (pstep cur)])))) @[(tuple/slice (tuple ;part)) (pstep cur)]))))
(make-lazy-seq (pstep coll))) (make-lazy-seq (pstep (lazy-from coll))))
(let [c (realize-for-iteration coll)]
(var result @[]) (var i 0)
(while (< i (length c))
(var part @[]) (var j 0)
(while (and (< j n) (< (+ i j) (length c)))
(array/push part (in c (+ i j))) (++ j))
(array/push result (tuple/slice (tuple ;part)))
(+= i n))
result)))
(defn core-keep-indexed [f coll] (defn core-keep-indexed [f coll]
(def f (as-fn f)) (def f (as-fn f))
(if (lazy-seq? coll) # Option A: always lazy.
(do
(defn kstep [c i] (defn kstep [c i]
(fn [] (fn []
(var cur c) (var idx i) (var found false) (var result nil) (var cur c) (var idx i) (var found false) (var result nil)
(while (and (not found) (not (seq-done? cur))) (while (and (not found) (not (seq-done? cur)))
(let [v (f idx (ls-first cur))] (let [v (f idx (core-first cur))]
(++ idx) (++ idx)
(set cur (ls-rest cur)) (set cur (core-rest cur))
(when (not (nil? v)) (when (not (nil? v))
(set found true) (set found true)
(set result v)))) (set result v))))
(if found @[result (kstep cur idx)] nil))) (if found @[result (kstep cur idx)] nil)))
(make-lazy-seq (kstep coll 0))) (make-lazy-seq (kstep (lazy-from coll) 0)))
(let [c (realize-for-iteration coll) result @[]]
(var i 0)
(each x c (let [v (f i x)] (when (not (nil? v)) (array/push result v))) (++ i))
(tuple/slice (tuple ;result)))))
(defn core-map-indexed [f & rest] (defn core-map-indexed [f & rest]
(if (= 0 (length rest)) (td-map-indexed f) (if (= 0 (length rest)) (td-map-indexed f)
(let [coll (in rest 0)] (let [coll (in rest 0)]
(if (lazy-seq? coll) # Option A: always lazy.
(do
(defn mstep [c i] (defn mstep [c i]
(fn [] (fn []
(if (seq-done? c) nil (if (seq-done? c) nil
@[(f i (ls-first c)) (mstep (ls-rest c) (+ i 1))]))) @[(f i (core-first c)) (mstep (core-rest c) (+ i 1))])))
(make-lazy-seq (mstep coll 0))) (make-lazy-seq (mstep (lazy-from coll) 0)))))
(let [c (realize-for-iteration coll) result @[]]
(var i 0)
(each x c (array/push result (f i x)) (++ i))
(tuple/slice (tuple ;result)))))))
(defn core-cycle [coll] (defn core-cycle [coll]
(let [c (realize-for-iteration coll)] (let [c (realize-for-iteration coll)]
@ -2321,18 +2284,14 @@
(defn core-take-nth [n & rest] (defn core-take-nth [n & rest]
(if (= 0 (length rest)) (td-take-nth n) (if (= 0 (length rest)) (td-take-nth n)
(let [coll (in rest 0)] (let [coll (in rest 0)]
(if (lazy-seq? coll) # Option A: always lazy.
(do
(defn tstep [c] (defn tstep [c]
(fn [] (fn []
(if (seq-done? c) nil (if (seq-done? c) nil
(let [drop-n (core-drop n c)] (let [drop-n (lazy-from (core-drop n c))]
(if (seq-done? drop-n) @[(ls-first c) nil] (if (seq-done? drop-n) @[(core-first c) nil]
@[(ls-first c) (tstep drop-n)]))))) @[(core-first c) (tstep drop-n)])))))
(make-lazy-seq (tstep coll))) (make-lazy-seq (tstep (lazy-from coll))))))
(let [c (realize-for-iteration coll) r @[]]
(var i 0) (while (< i (length c)) (array/push r (in c i)) (+= i n))
(tuple/slice (tuple ;r)))))))
# filterv now lives in the Clojure collection tier (core/20-coll.clj). # filterv now lives in the Clojure collection tier (core/20-coll.clj).
@ -2347,19 +2306,14 @@
(defn core-interpose [sep & rest] (defn core-interpose [sep & rest]
(if (= 0 (length rest)) (td-interpose sep) (if (= 0 (length rest)) (td-interpose sep)
(let [coll (in rest 0)] (let [coll (in rest 0)]
(if (lazy-seq? coll) # Option A: always lazy.
(do
(defn istep [c need-sep] (defn istep [c need-sep]
(fn [] (fn []
(if (seq-done? c) nil (if (seq-done? c) nil
(if need-sep (if need-sep
@[sep (istep c false)] @[sep (istep c false)]
@[(ls-first c) (istep (ls-rest c) true)])))) @[(core-first c) (istep (core-rest c) true)]))))
(make-lazy-seq (istep coll false))) (make-lazy-seq (istep (lazy-from coll) false)))))
(let [items (realize-for-iteration coll) r @[]]
(var first? true)
(each x items (if first? (set first? false) (array/push r sep)) (array/push r x))
(tuple ;r))))))
(defn core-keep (defn core-keep
"(keep f coll) — (f x) for each x, dropping nils. (keep f) is a transducer." "(keep f coll) — (f x) for each x, dropping nils. (keep f) is a transducer."
@ -2368,23 +2322,18 @@
(if (= 0 (length rest)) (if (= 0 (length rest))
(td-keep f) (td-keep f)
(let [coll (in rest 0)] (let [coll (in rest 0)]
(if (lazy-seq? coll) # Option A: always lazy.
(do
(defn kstep [c] (defn kstep [c]
(fn [] (fn []
(var cur c) (var found false) (var result nil) (var cur c) (var found false) (var result nil)
(while (and (not found) (not (seq-done? cur))) (while (and (not found) (not (seq-done? cur)))
(let [v (f (ls-first cur))] (let [v (f (core-first cur))]
(set cur (ls-rest cur)) (set cur (core-rest cur))
(when (not (nil? v)) (when (not (nil? v))
(set found true) (set found true)
(set result v)))) (set result v))))
(if found @[result (kstep cur)] nil))) (if found @[result (kstep cur)] nil)))
(make-lazy-seq (kstep coll))) (make-lazy-seq (kstep (lazy-from coll))))))
(let [r @[]]
(each x (realize-for-iteration coll)
(let [v (f x)] (when (not (nil? v)) (array/push r v))))
(tuple ;r))))))
(defn core-empty [coll] (defn core-empty [coll]

View file

@ -76,6 +76,15 @@
["juxt fns in vec" "[1 3]" "((juxt first last) [1 2 3])"] ["juxt fns in vec" "[1 3]" "((juxt first last) [1 2 3])"]
["last of lazy take" "5" "(last (take 5 (iterate inc 1)))"] ["last of lazy take" "5" "(last (take 5 (iterate inc 1)))"]
["next empty lazy" "nil" "(next (take 1 [1]))"] ["next empty lazy" "nil" "(next (take 1 [1]))"]
# drop/distinct/partition/map-indexed/take-nth/interpose/keep are lazy too
["drop vec is seq" "true" "(seq? (drop 1 [1 2 3]))"]
["distinct vec is seq" "true" "(seq? (distinct [1 1 2]))"]
["map-indexed is seq" "true" "(seq? (map-indexed vector [1 2]))"]
["partition vec lazy" "(quote ((1 2) (3 4)))" "(partition 2 [1 2 3 4 5])"]
# nth over a lazy seq must not treat a false/nil element as end-of-seq
["nth lazy false elem" "false" "(nth (map identity [false 1 2]) 0)"]
["nth lazy past false" "2" "(nth (drop 1 (list false 1 2)) 1)"]
["cond-> false clause" "2" "(cond-> 1 true inc false inc)"]
### ---- HIGH: destructuring ---- ### ---- HIGH: destructuring ----
["destr nested seq" "[1 2 3]" "(let [[a [b c]] [1 [2 3]]] [a b c])"] ["destr nested seq" "[1 2 3]" "(let [[a [b c]] [1 [2 3]]] [a b c])"]