Notes and examples of Transducers and Reducers
-
Used on collections
-
Eager evaluation only. (not lazy)
-
The operation(s) and the input are no longer tied together
(r/map zero?)
;=> #<reducers$map$fn__2527 clojure.core.reducers$map$fn__2527@7201f000>
(let [map-zero? (r/map zero?)
inputs [0 1 0 1 0 1]]
(reduce conj [] (map-zero? inputs)))
;=> [true false true false true false]
- Can be combined into a single function for better performance. (composable)
(def zero?-and-not-false
(comp
(r/filter true?)
(r/map zero?)))
;=> #'user/zero?-and-not-false
(reduce conj [] (zero?-and-not-false [0 1 0 1 0 1]))
[true true true]
-
Can be used in ClojureScript
-
Function C is the reducing function
-
Function B calls Function C
-
Function A creates Function B
-
(A C) -> B
(defn a [c]
(fn b
([] (c))
([coll] (c coll))
([coll input] (c coll input))))
;=> #'user/a
(transduce a + [1 2 3])
;=> 6
-
Doesn't care about the input type
-
Transducers seem to be faster than reducers
(dotimes [n 5] (time (r/reduce + 0 [1 2 3])))
"Elapsed time: 0.23142 msecs"
"Elapsed time: 0.047252 msecs"
"Elapsed time: 0.043944 msecs"
"Elapsed time: 0.062372 msecs"
"Elapsed time: 0.05938 msecs"
;=> nil
(dotimes [n 5] (time (transduce a + 0 [1 2 3])))
"Elapsed time: 0.1257 msecs"
"Elapsed time: 0.026548 msecs"
"Elapsed time: 0.018166 msecs"
"Elapsed time: 0.031276 msecs"
"Elapsed time: 0.024773 msecs"
;=> nil
- Collection fns of previous Clojure versions are now optionally Transducers
(let [*2 #(* % 2)
*4 #(* % 4)]
(def weird-composition
(comp
(filter even?)
(map *2)
(map *4))))
;=> #'user/weird-composition
(into [] weird-composition [1 2 3 4])
;=> [16 32]
- For lazy transformations you must use
sequence
(def star-wrap
(map #(str "*" % "*")))
;=> #'user/star-wrap
(into [] star-wrap [1 2 3])
;=> ["*1*" "*2*" "*3*"]
(sequence star-wrap [1 2 3])
;=> ("*1*" "*2*" "*3*")
(type (into [] star-wrap [1 2 3]))
;=> clojure.lang.PersistentVector
(type (sequence star-wrap [1 2 3]))
;=> clojure.lang.LazyTransformer