From c7378f4be0d3d36bbc142fce73c9a85b8131d599 Mon Sep 17 00:00:00 2001 From: Yogthos Date: Mon, 8 Jun 2026 18:00:05 -0400 Subject: [PATCH] =?UTF-8?q?core:=20jolt-b56=20=E2=80=94=20lazy=20sequence?= =?UTF-8?q?=20(the=20laziness-coupled=20straggler)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (sequence xform coll) was eager (transduce into a tuple), so it hung on infinite input. Now it's a lazy buffered transducer pump: pulls one source element at a time, pushes it through the transducer's reducing fn into a buffer, and emits buffered outputs lazily — so (take 3 (sequence (map inc) (range))) works. Honors `reduced` (early stop) and runs the completion arity to flush stateful transducers. Normalizes the source via core-seq (walking a raw pvec/set would misfire — seq-done? uses length, which counts a pvec table's keys). The other jolt-b56 items stay native by design: sequential?/seqable? are representation-coupled and representation-mode-sensitive (jolt-1vx); realized? reads the tagged :realized flag; cat/eduction/transduce/halt-when/unreduced/ ensure-reduced are the transducer/Reduced kernel the issue says to keep native. Note: partition-all has no transducer (1-arg) arity, so (sequence (partition-all 2) …) errors — a pre-existing gap, unrelated to this change. Gate: conformance 249x3, lazy-infinite 44/44, fixpoint, self-host, specs+unit green. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/jolt/core.janet | 40 +++++++++++++++++++++-- test/integration/lazy-infinite-test.janet | 2 ++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/jolt/core.janet b/src/jolt/core.janet index d7d25cf..a7b1f0e 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -878,11 +878,47 @@ (into-conj to (realize-for-iteration (in rest 0))))) (defn core-sequence - "(sequence coll) or (sequence xform coll) — eager here (returns a seq/tuple)." + "(sequence coll) -> a seq of coll. (sequence xform coll) -> a LAZY seq of coll + transformed by xform: elements are pulled and pushed through the transducer one + at a time, with outputs buffered and emitted lazily — so it works over infinite + input (matching Clojure). Honors `reduced` (early stop) and runs the completion + arity to flush stateful transducers (e.g. partition-all)." [a & rest] (if (= 0 (length rest)) (core-seq a) - (tuple ;(core-transduce a (fn [& x] (case (length x) 0 @[] 1 (x 0) (do (array/push (x 0) (x 1)) (x 0)))) @[] (in rest 0))))) + (let [xform a + coll (in rest 0) + buf @[] + state @{:stopped false :completed false} + rf (fn [& args] + (case (length args) + 0 buf + 1 (in args 0) + (do (array/push (in args 0) (in args 1)) (in args 0)))) + xf (xform rf)] + # Pull/complete until buf holds an output or the source is fully drained. + (defn ensure-buf [src] + (var s src) + (while (and (= 0 (length buf)) (not (state :stopped)) (not (seq-done? s))) + (let [r (xf buf (core-first s))] + (set s (core-rest s)) + (when (core-reduced? r) (put state :stopped true)))) + (when (and (= 0 (length buf)) (not (state :completed)) + (or (state :stopped) (seq-done? s))) + (put state :completed true) + (xf buf)) # completion arity — flushes any buffered state + s) + (defn gen [src] + (fn [] + (let [s (ensure-buf src)] + (if (= 0 (length buf)) nil + (let [val (in buf 0)] + (array/remove buf 0 1) + @[val (gen s)]))))) + # core-seq normalizes to a tuple / lazy-seq / nil — all walkable by + # core-first/rest/seq-done?. (Walking a raw pvec/set would misfire: + # seq-done? uses length, which counts a pvec table's KEYS, not elements.) + (make-lazy-seq (gen (core-seq coll)))))) (defn coll->cells [c] diff --git a/test/integration/lazy-infinite-test.janet b/test/integration/lazy-infinite-test.janet index a3fabfc..a29a5dd 100644 --- a/test/integration/lazy-infinite-test.janet +++ b/test/integration/lazy-infinite-test.janet @@ -50,6 +50,8 @@ ["take 4 interleave range iterate" "(quote (0 10 1 11))" "(take 4 (interleave (range) (iterate inc 10)))"] ["take 4 reductions + range" "(quote (0 1 3 6))" "(take 4 (reductions + (range)))"] ["take 3 tree-seq infinite" "(quote (0 0 0))" "(take 3 (tree-seq (fn [_] true) (fn [n] [n]) 0))"] + ["sequence xform lazy inf" "(quote (1 2 3))" "(take 3 (sequence (map inc) (range)))"] + ["sequence comp xform inf" "(quote (2 4 6))" "(take 3 (sequence (comp (filter odd?) (map inc)) (range)))"] ["every? short-circuits on inf" "false" "(every? pos? (range))"] ["not-every? short-circuits on inf" "true" "(not-every? pos? (range))"] ["take 3 partition 2 range" "(quote ((0 1) (2 3) (4 5)))" "(take 3 (partition 2 (range)))"]