Transducers are recipes what to do with a sequence of data without knowledge what the underlying sequence is (how to do it). It can be any seq, async channel or maybe observable.
They are composable and polymorphic.
The benefit is, you don't have to implement all standard combinators every time new data source is added. Again and again. As resulting effect you as user are able to reuse those recipes on different data sources.
Prior version 1.7 of Clojure you had three ways how to write dataflow queries:
- nested calls
(reduce + (filter odd? (map #(+ 2 %) (range 0 10))))
- functional composition
(def xform (comp (partial filter odd?) (partial map #(+ 2 %)))) (reduce + (xform (range 0 10)))
- threading macro
(defn xform [xs] (->> xs (map #(+ 2 %)) (filter odd?))) (reduce + (xform (range 0 10)))
With transducers you will write it like:
(def xform (comp (map #(+ 2 %)) (filter odd?))) (transduce xform + (range 0 10))
They all do the same. The difference is that you never call Transducers directly, you pass them to another function. Transducers know what to do, the function that gets transducer knows how. The order of combinators is like you write it with threading macro (natural order). Now you can reuse
xform with channel:
(chan 1 xform)