Merge pull request #76 from jolt-lang/seed-shrink-r5-into-transduce

core: transduce/eduction/->Eduction to the overlay; into stays seed (perf wall)
This commit is contained in:
Dmitri Sotnikov 2026-06-11 18:00:59 +00:00 committed by GitHub
commit 5cac9efd4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 39 additions and 26 deletions

View file

@ -967,3 +967,26 @@
(defn unchecked-char [x] (bit-and (int x) 0xffff))
(defn unchecked-float [x] (double x))
(defn unchecked-double [x] (double x))
;; --- transduce / into / eduction (seed-shrink round 5) ---------------------
;; Canonical transduce: build the stacked rf once, reduce (which honors
;; `reduced` and steps lazy seqs incrementally), then run the completion arity.
(defn transduce
([xform f coll] (transduce xform f (f) coll))
([xform f init coll]
(let [xf (xform f)]
(xf (reduce xf init coll)))))
;; into stays in the seed: it's perf-wall hot (the into-vec bench pays ~11%
;; through the overlay call layers — same lesson as even?/odd? in round 4).
;; eduction is EAGER on jolt (documented divergence, as before): the composed
;; xforms applied to coll, realized into a vector.
(defn eduction [& args]
(let [coll (last args)
xforms (butlast args)]
(if xforms
(into [] (apply comp xforms) coll)
(into [] coll))))
(defn ->Eduction [xform coll] (into [] xform coll))

View file

@ -1423,21 +1423,7 @@
# :content) returned nil: janet keyword-apply is not jolt invoke). This
# private composer remains ONLY for the transducer machinery below, where the
# stages are always real fns.
(def- td-comp
(fn [& fs]
(case (length fs)
0 identity
1 (fs 0)
2 (let [f (fs 0) g (fs 1)] (fn [& args] (f (apply g args))))
(let [f (last fs)
gs (array/slice fs 0 (dec (length fs)))]
(fn [& args]
(var result (apply (last gs) args))
(var i (- (length gs) 2))
(while (>= i 0)
(set result ((gs i) result))
(-- i))
(f result))))))
# (td-comp is gone: eduction — its last caller — lives in the overlay now.)
# partial now lives in the Clojure collection tier (canonical arities).
@ -2416,14 +2402,7 @@
(defn core-update-proxy [proxy mappings] proxy)
# == lives in the Clojure collection tier (core/20-coll.clj); memfn is an
# overlay macro (core/30-macros.clj) over the .method call sugar.
(defn core-eduction [& args]
# (eduction xform* coll): apply the composed transducers eagerly to coll
(let [n (length args)
coll (in args (- n 1))
xforms (array/slice args 0 (- n 1))
xform (if (= 0 (length xforms)) (fn [rf] rf) (apply td-comp xforms))]
(core-into (make-vec @[]) xform coll)))
(defn core->Eduction [xform coll] (core-into (make-vec @[]) xform coll))
# eduction / ->Eduction live in the Clojure collection tier (core/20-coll.clj).
(defn core-proxy-super [& args] (error "proxy-super: JVM proxies are not supported in Jolt"))
(defn core-construct-proxy [c & args] (error "construct-proxy: not supported in Jolt"))
(defn core-init-proxy [proxy mappings] proxy)
@ -2508,6 +2487,8 @@
@{:jolt/type :jolt/transient :kind :map :tbl t})
# mutable-build arrays (vectors/lists) — copy into a transient vector
(array? coll) @{:jolt/type :jolt/transient :kind :vector :arr (array/slice coll)}
# tuples (reader vectors / map entries) are vectors too
(tuple? coll) @{:jolt/type :jolt/transient :kind :vector :arr (array ;coll)}
(error (string "Don't know how to create a transient from " (type coll)))))
# A transient is invalidated by persistent!; using it afterwards is a bug.
@ -2705,7 +2686,6 @@
"parse-double" core-parse-double
"current-time-ms" core-current-time-ms
"mapcat" core-mapcat
"transduce" core-transduce
"sequence" core-sequence
"keyword" core-keyword
"symbol" core-symbol
@ -2817,8 +2797,6 @@
"proxy-call-with-super" core-proxy-call-with-super
"proxy-mappings" core-proxy-mappings
"update-proxy" core-update-proxy
"eduction" core-eduction
"->Eduction" core->Eduction
"proxy-super" core-proxy-super
"construct-proxy" core-construct-proxy
"init-proxy" core-init-proxy

View file

@ -63,3 +63,15 @@
["reduce empty calls f" "0" "(reduce + [])"]
["reduce with-init" "16" "(reduce + 10 [1 2 3])"]
["reduce reduced immediate" ":x" "(reduce (fn [a x] (reduced :x)) :init [1 2 3])"])
# into/transduce/eduction are overlay fns now (seed-shrink round 5): into
# takes a transient fast path for vectors and preserves metadata; eduction
# stays eager (documented divergence).
(defspec "transducers / into & eduction (overlay)"
["into list prepends" "(quote (4 3 1 2))" "(into (quote (1 2)) [3 4])"]
["into sorted-map" "{1 :a, 2 :b}" "(into (sorted-map) [[2 :b] [1 :a]])"]
["into from map entry" "[:a 1]" "(into [] (first {:a 1}))"]
["into xform on map" "{:a 2}" "(into {} (map (fn [e] [(key e) (inc (val e))])) {:a 1})"]
["eduction multiple xforms" "[4]" "(into [] (eduction (filter odd?) (map inc) [2 3 4]))"]
["->Eduction" "[2 3]" "(->Eduction (map inc) [1 2])"]
["transduce no init uses (f)" "5" "(transduce (map inc) + [1 2])"])