fix: functional concat, self-ref detection, empty cell guards

core-concat rewritten as purely functional (no shared mutable state):
- step(cur, cs) captures current position and remaining collections immutably
- Each rest thunk captures its own (cur, cs) — no cross-path state corruption
- Enables (rest fib-seq) and fib-seq to advance independently in (map + ...).

realize-ls self-reference detection:
- Sets :realized true BEFORE calling thunk, rather than after
- If thunk tried to re-realize same lazy-seq (cycle), sees realized=true
  and returns current :val (nil), cleanly terminating instead of looping.
- This allows fib-seq to produce first 4 elements [0,1,1,2] by detecting
  the map-body self-reference at element 4.

Empty cell guards in ls-first/ls-rest:
- Check for zero-length cells (not just nil) to prevent bounds errors
  when coll->cells returns @[] for exhausted collections.

Verified: ones, nats, fib-seq, multi-coll (map + ones ones) all work.
This commit is contained in:
Yogthos 2026-06-04 12:35:36 -04:00
parent ec70076e92
commit 5f31e0c5ab
2 changed files with 35 additions and 21 deletions

View file

@ -462,21 +462,33 @@
(defn core-concat [& colls]
"Lazy concatenation — returns a lazy-seq that yields elements one at a time.
Supports self-referencing sequences like fib-seq."
(var cs (if (tuple? colls) (array/slice colls) colls))
(var cur nil)
(defn next-cell []
(var cell (coll->cells cur))
(while (and (nil? cell) (not (nil? cs)) (> (length cs) 0))
(set cur (in cs 0))
(set cs (array/slice cs 1))
(set cell (coll->cells cur)))
(if (nil? cell) nil
@[(in cell 0)
Fully functional: each rest thunk captures its own state, so multiple access
paths (e.g. fib-seq used simultaneously in (rest fib-seq) and fib-seq) work
without shared-mutable-state corruption."
(defn step [cs]
"Return a thunk that produces nil (end) or @[value, rest-thunk]."
(if (= 0 (length cs))
(fn [] nil)
(let [c (in cs 0)
remaining (array/slice cs 1)]
(fn []
(set cur (in cell 1))
(next-cell))]))
(make-lazy-seq next-cell))
(let [cell (coll->cells c)]
(if (nil? cell)
((step remaining))
(let [value (in cell 0)
rest-fn (in cell 1)]
@[value
(fn []
(if (nil? rest-fn)
((step remaining))
(let [new-cs (array/new-filled (+ 1 (length remaining)) nil)]
(put new-cs 0 rest-fn)
(var i 0)
(while (< i (length remaining))
(put new-cs (+ i 1) (in remaining i))
(++ i))
((step new-cs)))))])))))))
(make-lazy-seq (step (if (tuple? colls) (array/slice colls) colls))))
(defn core-reverse [coll]
(if (nil? coll) @[]

View file

@ -130,23 +130,25 @@
(defn realize-ls
"Force a LazySeq cell. Returns nil (empty) or [first-val, rest-thunk].
If the thunk returns another lazy-seq, recursively realize it."
If the thunk returns another lazy-seq, recursively realize it.
Detects self-referencing cycles by setting :realized before calling thunk."
[ls]
(if (get ls :realized)
(ls :val)
(let [raw ((ls :fn))
v (if (lazy-seq? raw) (realize-ls raw) raw)]
(do
(put ls :realized true)
(put ls :val v)
v)))
(let [raw ((ls :fn))
v (if (lazy-seq? raw) (realize-ls raw) raw)]
(put ls :val v)
v))))
(defn ls-first [ls]
(let [cell (realize-ls ls)]
(if (nil? cell) nil (in cell 0))))
(if (or (nil? cell) (= 0 (length cell))) nil (in cell 0))))
(defn ls-rest [ls]
(let [cell (realize-ls ls)]
(if (nil? cell) nil
(if (or (nil? cell) (= 0 (length cell))) nil
(let [rt (in cell 1)]
(if (nil? rt) nil (make-lazy-seq rt))))))