Returns a reducible/iterable application of the transducers to the items in coll. Transducers are applied in order as if combined with comp. Note that these applications will be performed every time reduce/iterator is called.
;; `eduction` calls the stack of transformers on each element, each time. ;; `->>` calls the transformer named 'left' on a collection, then the transformer ;; named 'right' on the result, etc. (def cnt (atom nil)) ;; inc with debugging output (defn inc-with-print [fn-id coll-idx x] (printf ";; fn-id: %s; coll-idx: %s; cnt: %s; x: %s\n" fn-id coll-idx @cnt x) (swap! cnt inc) (inc x)) (reset! cnt 0) (eduction (map-indexed (fn [coll-idx x] (inc-with-print " left" coll-idx x))) (map-indexed (fn [coll-idx x] (inc-with-print "right" coll-idx x))) (range 3)) ;; fn-id: left; coll-idx: 0; cnt: 0; x: 0 ;; fn-id: right; coll-idx: 0; cnt: 1; x: 1 ;; fn-id: left; coll-idx: 1; cnt: 2; x: 1 ;; fn-id: right; coll-idx: 1; cnt: 3; x: 2 ;; fn-id: left; coll-idx: 2; cnt: 4; x: 2 ;; fn-id: right; coll-idx: 2; cnt: 5; x: 3 ;;=> (2 3 4) (reset! cnt 0) (->> (range 3) (map-indexed (fn [coll-idx x] (inc-with-print " left" coll-idx x))) (map-indexed (fn [coll-idx x] (inc-with-print "right" coll-idx x)))) ;; fn-id: left; coll-idx: 0; cnt: 0; x: 0 ;; fn-id: left; coll-idx: 1; cnt: 1; x: 1 ;; fn-id: left; coll-idx: 2; cnt: 2; x: 2 ;; fn-id: right; coll-idx: 0; cnt: 3; x: 1 ;; fn-id: right; coll-idx: 1; cnt: 4; x: 2 ;; fn-id: right; coll-idx: 2; cnt: 5; x: 3 ;;=> (2 3 4)
;; eduction: just run an xform over a collection (eduction (map inc) [1 2 3]) ; => (2 3 4) (eduction (filter even?) (range 5)) ; => (0 2 4) ;; several transducers can be given, without using 'comp' (eduction (filter even?) (map inc) (range 5)) ; => (1 3 5)
;; This will run out of memory eventually, ;; because the entire seq is realized, ;; because the head of the lazy seq is retained. (let [s (range 100000000)] (do (apply print s) (first s))) ;; This iterates through the lazy seq without realizing the seq. (let [s (eduction identity (range 100000000))] (do (apply print s) (first s)))
;; Result of eduction is of the `clojure.core.Eduction` type which acts as a ;; lazy collection that re-executes all the steps again and again. This could be ;; useful when you don't want to store the collection separately. ;; ;; Eductions can be efficiently used with `reduce` and `transduce`. (defn inc-with-print [x] (println ";;" x) (inc x)) (def ed (eduction (map inc-with-print) (map inc-with-print) (range 3))) (defn identity-with-print [x] (println "identity:" x) x) (map identity-with-print ed) ;; 0 ;; 1 ;; 1 ;; 2 ;; 2 ;; 3 ;; identity: 2 ;; identity: 3 ;; identity: 4 ;; => (2 3 4) (defn sum-with-print [x y] (println "sum:" x "+" y) (+ x y)) (reduce sum-with-print ed) ;; 0 ;; 1 ;; 1 ;; 2 ;; sum: 2 + 3 ;; 2 ;; 3 ;; sum: 5 + 4 ;; => 9 ;; The above line does not work in Clojure 1.10.1 ;; Execution error (ClassCastException) at user/eval193 (REPL:1). ;; clojure.core.Eduction cannot be cast to clojure.lang.IReduce (transduce (map identity-with-print) + ed) ;; 0 ;; 1 ;; identity: 2 ;; 1 ;; 2 ;; identity: 3 ;; 2 ;; 3 ;; identity: 4 ;; => 9
;; Converting from seqs to transducers is trivial with eduction. (->> (range 100) ; <- coll source (map inc) (remove odd?) (reduce + 0)) ; <- reduction target ;; Refactor by placing an eduction in the middle. (->> (range 100) (eduction (map inc) (remove odd?)) (reduce + 0)) ; => 2550 ;; Because Eduction type implements clojure.lang.IReduceInit, ;; and its reduce implementation calls transduce, ;; it is equivalent to this. (transduce (comp (map inc) (remove odd?)) + 0 (range 100)) ; => 2550 ;; So long as the target is a reduce, or is implemented by reduce, ;; transduce will be called. It is a good idea to use clojure.repl/source ;; to check. (source mapv) ;; Examples functions implemented by reduce include: ;; - into ;; - mapv ;; - filterv ;; - frequencies ;; - group-by ;; - run! ;; Since Eduction calls transduce on the given coll, ;; for maximum performance, the coll passed to eduction should ;; ideally implement clojure.lang.IReduceInit. ;; Having both a clojure.lang.IReduceInit coll and a reduce target ;; cause the reduce to be executed on the internal fast path. ;; It is a good idea to verify with the instance? function. (instance? clojure.lang.IReduceInit (range 100)) ; => true ;; Example clojure.lang.IReduceInit types include: ;; - range ;; - vector ;; - iterate ;; - eduction
;; Use eduction within mapcat transducers ;; to avoid intermediate allocations. (->> [1 2 2 2 3 4 4 4 4 5 5] (eduction (partition-by identity) (mapcat (fn [nums] ;; This makes a intermediate vector! (into [] (map #(* (count nums) %)) nums)))) (vec)) ;; => [1 6 6 6 3 16 16 16 16 10 10] ;; Refactor using eduction inside mapcat instead! (->> [1 2 2 2 3 4 4 4 4 5 5] (eduction (partition-by identity) (mapcat (fn [nums] ;; Allocates a cheaper intermediate eduction ;; in place of a collection (eduction (map #(* (count nums) %)) nums)))) (vec)) ;; => [1 6 6 6 3 16 16 16 16 10 10] ;; This works because cat (and by extension mapcat) is implemented with reduce. ;; See the source (source cat) ;; In general, pass IReduceInit types into cat transducers for maximum performance. ;; Use eduction in particular to create a non-coll-allocating IReduceInit.
reduce with a transformation of f (xf). If init is not supplied, (f) will be called to produce it....
f should be a function of 2 arguments. If val is not supplied, returns the result of applying f to...
Returns a lazy seq of the intermediate values of the reduction (as per reduce) of coll by f, start...
Coerces coll to a (possibly empty) sequence, if it is not already one. Will not force a lazy seq. ...
eduction
is particularly useful as an adapter for collection-processing functions that don’t have a transducers arity. For example, you might want to transform a collection before passing it to frequencies
. eduction
makes that possible and efficient:
(->> coll (eduction (map #(quot % 5)) (filter odd?)) frequencies)
So eduction
turns out to be quite powerful in that it brings the benefits of transducers to all collection-processing functions, even those that predate transducers: first
, last
, group-by
, run!
, str/join
, …
Interesting background about sequence
versus eduction
: https://groups.google.com/d/msg/clojure/9I6MtgOTD0w/NiG5PimBCP8J
eduction
does not cache the result while sequence
does. eduction
is intended to be used when you are eventually going to reduce
over the sequence.
https://groups.google.com/d/msg/clojure/9I6MtgOTD0w/NiG5PimBCP8J