1 2 3 4 5 6 7
On lines 3 and 4, I am calling clj->js to transform a clojurescript
I had heard of transducers in the clojure world without taking the trouble to see what all the fuss was about but I had no idea that they were available in clojurescript. I’m now going to give a brief introduction as to what transducers are but there is lots of good material out there that probably do a better job and Rich Hickey’s strangeloop introduction to them is a great start.
I always address a new concept by first of all determining what problem does the new concept solve and with tranducers the problem is one of decoupling. You are probably familiar with
filter which returns all items in a collection that are true in terms of a predicate function:
It should be noted that
filter could be constructed using
1 2 3 4 5 6 7
The problem with the above is that we cannot replace
conj on line 4 with another builder function like
+. This problem holds true for all the pre-transducer sequence functions like
filter etc. Transducers set out to abstract away operations like
conj so that the creation of the resultant datastructure is decoupled from the
+ are reducing functions in that they take a result and an input and return a new result. We could refactor our
filter-odd function to a more generic
filtering function that allows us to supply different predicates and reducing funtions by using higher order functions:
1 2 3 4 5 6 7 8 9 10
The above is not as scary as it looks and you can see on lines 9 and 10 that we are able to supply different reducing functions (
+). This is the problem that transducers set out to solve, the reducing function is now abstracted away so that the creation of the datastructure is decoupled from the sequence function (
map etc.) logic.
As of clojure 1.7.0 most of the core sequence functions (
filter etc.) are gaining a new 1 argument arity that will return a transducer that, for example this call will return a transducer from
One of the new ways (but not the only way) to apply transducers is with the transduce function. The transduce function takes the following form:
The above states that
transduce will reduce a collection
coll with the inital value
init, applying a transformation
xform to each value and applying the reducing function
We can now apply this to our previous example
1 2 3 4 5
I hope it is obvious that
(range 0 10) is
 is the
xform is the transducer function and
conj are the reducing functions.
This is the current code that we want to refactor:
So the first question is what would the reducing function be when dealing with native arrays? The answer is the native array
push push method which adds a new item to an array. My first ill thought out attempt at the above looked something like this:
This is completely wrong because I had not grasped what is required of the reducing funcion. A reducing function takes a result and an input and returns a new result e.g.
push function does not satisfy what is required as the
push function actually returns the length of the array which is not what is expected. What was needed was someway of turning the
push function into a function that behaved in a way that the transducer expected. The push function would need to return the result:
But as it turns out, this also does not work because a reducing function to transduce has to have 0, 1 and 2 arities and our reducing function only has 1.
As it turns out, both clojure and clojurescript provide a function called completing that takes a function and returns a function that is suitable for transducing by wrapping the reducing funtion and adding an extra arity that simply calls the identity function behind the scenes. Below is the
completing function from the
1 2 3 4 5 6 7
My final code ended up looking like this:
completing that makes it suitable for transducing.
If I have got anyting wrong in this explanation then please leave a comment below.