diff --git a/jolt-core/clojure/core/20-coll.clj b/jolt-core/clojure/core/20-coll.clj index 2ce0089..60aae74 100644 --- a/jolt-core/clojure/core/20-coll.clj +++ b/jolt-core/clojure/core/20-coll.clj @@ -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)) diff --git a/src/jolt/core.janet b/src/jolt/core.janet index a85ef58..6487cbd 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -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 diff --git a/test/spec/transducers-spec.janet b/test/spec/transducers-spec.janet index 2b38908..be21ca1 100644 --- a/test/spec/transducers-spec.janet +++ b/test/spec/transducers-spec.janet @@ -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])"])