Transducers

Clojure 1.7 introduced a new abstraction called transducers for "composable algorithmic transformations", commonly used to apply a series of transformations over collections. The idea of transducers follows from the reducing function, which accepts arguments of the form (result, input) and returns result. A reducing function is what we typically use with reduce. A transducer accepts a reducing function, wraps/composes over its functionality to provide something extra, and returns another reducing function.

The functions in clojure.core that deal with collections have acquired an arity-1 variant, which returns a transducer, namely map, cat, mapcat, filter, remove, take, take-while, take-nth, drop, drop-while, replace, partition-by, partition-all, keep, keep-indexed, dedupe and random-sample.

Consider the following few examples, all of which do the same thing:

user=> (reduce ((filter odd?) +) [1 2 3 4 5])
9
user=> (transduce (filter odd?) + [1 2 3 4 5])
9
user=> (defn filter-odd? [xf]
         (fn
           ([] (xf))
           ([result] (xf result))
           ([result input] (if (odd? input)
                               (xf result input)
                               result))))
#'user/filter-odd?
user=> (reduce (filter-odd? +) [1 2 3 4 5])
9

Here, (filter odd?) returns a transducer—in the first example the transducer wraps over the reducer function + to return another combined reducing function. While we use the ordinary reduce function in the first example, in the second example we use the transduce function that accepts a transducer as an argument. In the third example, we write a transducer filter-odd?, which emulates what (filter odd?) does. Let's see how the performance varies between traditional and transducer versions:

;; traditional way
user=> (time (dotimes [_ 10000] (reduce + (filter odd? (range 10000)))))
"Elapsed time: 2746.782033 msecs"
nil
;; using transducer
(def fodd? (filter odd?))
user=> (time (dotimes [_ 10000] (transduce fodd? + (range 10000))))
"Elapsed time: 1998.566463 msecs"
nil

Performance characteristics

The key point behind transducers is how orthogonal each transformation is allowed to be, yet highly composable also. At the same time, transformations can happen in lockstep for the entire sequence instead of each operation producing lazy chunked sequences. This often causes significant performance benefits with transducers. Lazy sequences are still going to be useful when the final result is too large to realize at once—for other use cases transducers should fit the need aptly with improved performance. Since the core functions have been overhauled to work with transducers, it makes sense to model transformations more often than not in terms of transducers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.137.197