Step 5: partition-by, dedupe → lazy overlay + trampoline/rand-int overlay

partition-by (10-seq.clj): ported canonical lazy implementation
from CLJS — lazy-seq + cons + take-while, matching Clojure/CLJS
exactly. Fixes the third and final remaining leak from Step 3.

dedupe (20-coll.clj): replaced eager vec-based impl with lazy
step function using lazy-seq + cons. Infinite input no longer hangs.

trampoline (20-coll.clj): pure HOF ported from CLJS — recur until
non-function result. Removed core-trampoline + binding from Janet.

rand-int (20-coll.clj): thin overlay over Janet math/random +
math/floor. Removed core-rand-int + binding from Janet.

Removed core-partition-by + binding from core.janet (now in overlay).

Tests: added dedupe infinite-input case to lazy-infinite harness.
22/22 pass. Conformance 229x3. Specs 32/32.
This commit is contained in:
Yogthos 2026-06-08 11:02:03 -04:00
parent 42da5cef9a
commit 64b1c60939
4 changed files with 37 additions and 31 deletions

View file

@ -22,3 +22,15 @@
(if (next s)
(recur (conj ret (first s)) (next s))
(seq ret))))
;; Lazy partition-by: groups consecutive elements by (f x), matching Clojure/CLJS.
(defn partition-by [f coll]
(let [step (fn step [s]
(lazy-seq
(let [s (seq s)]
(when s
(let [fst (first s)
fv (f fst)
run (cons fst (take-while (fn [x] (= fv (f x))) (rest s)))]
(cons run (step (lazy-seq (drop (count run) s)))))))))]
(step coll)))

View file

@ -208,16 +208,32 @@
;; No ratio type on Jolt, so rationalize is identity.
(defn rationalize [x] x)
;; trampoline: repeatedly calls f with args until a non-function result.
(defn trampoline
([f] (trampoline f (f)))
([f & args]
(let [ret (apply f args)]
(if (fn? ret)
(recur ret)
ret))))
;; rand-int: random integer in [0, n). Uses Janet math/random.
(defn rand-int [n] (math/floor (* (math/random) n)))
;; Eager dedupe of consecutive equal elements (Jolt has no transducer arity yet).
(defn dedupe [coll]
(let [c (vec coll)]
(if (empty? c)
[]
(loop [prev (first c) xs (rest c) out [(first c)]]
(if (seq xs)
(let [x (first xs)]
(recur x (rest xs) (if (= x prev) out (conj out x))))
out)))))
(let [step (fn step [s prev]
(lazy-seq
(let [s (seq s)]
(when s
(let [x (first s)]
(if (= x prev)
(step (rest s) prev)
(cons x (step (rest s) x))))))))]
(let [s (seq coll)]
(if s
(cons (first s) (step (rest s) (first s)))
()))))
;; Internal helper for {:keys [...]} destructuring over a seq of k/v pairs:
;; builds a map from consecutive pairs, dropping a trailing unpaired element.

View file

@ -244,7 +244,6 @@
(defn core-min [& args] (each x args (need-num x "min")) (apply min args))
(defn core-rand [] (math/random))
(defn core-rand-int [n] (math/floor (* (math/random) n)))
# ============================================================
# Comparison
@ -1318,21 +1317,6 @@
(+= i step))
result))))
(defn core-partition-by [f coll]
(def f (as-fn f))
(var result @[])
(var part @[])
(var last-k nil)
(each x (realize-for-iteration coll)
(let [k (f x)]
(if (and last-k (deep= k last-k))
(array/push part x)
(do
(if (> (length part) 0) (array/push result (tuple/slice (tuple ;part))))
(set part @[x])
(set last-k k)))))
(if (> (length part) 0) (array/push result (tuple/slice (tuple ;part))))
result)
(defn core-partition-all [n coll]
(if (lazy-seq? coll)
@ -1419,10 +1403,6 @@
# subvec lives in the Clojure kernel tier — core/00-kernel.clj.
(defn core-trampoline [f & args]
(var result (apply f args))
(while (function? result) (set result (result)))
result)
(def core-format (fn [fmt & args] (string/format fmt ;args)))
@ -2818,7 +2798,6 @@
"max" core-max
"min" core-min
"rand" core-rand
"rand-int" core-rand-int
"=" core-=
"not=" core-not=
"<" core-<
@ -2837,7 +2816,6 @@
"map-indexed" core-map-indexed
"cycle" core-cycle
"pop" core-pop
"trampoline" core-trampoline
"format" core-format
"first" core-first
"rest" core-rest
@ -2958,7 +2936,6 @@
"sort-by" core-sort-by
"distinct" core-distinct
"partition" core-partition
"partition-by" core-partition-by
"range" core-range
"repeat" core-repeat
"iterate" core-iterate

View file

@ -55,6 +55,7 @@
["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))"]
["dedupe inf" "(quote (1 2 1 2 1))" "(take 5 (dedupe (cycle [1 1 2 2])))"]
["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)))"]