One of the nice things that you’ll come across in Clojure is transducer. In this post I’ll go over what transducers are, how you can use them, how you can make one, and what transducible contexts are.
What are transducers?
In simple terms, transducers are composable transformation pipelines. A transducer does not care about where the input comes from or where the output will go; it simply cares about the transformation of the data that that flows through the pipeline. Let’s look at an example:
1 | (let [xf (comp |
Here xf (external function) is our transducer which will increment every number and then will keep only the even numbers. Calling sequence functions like map, filter, etc. with single arity returns a transducer which you can then compose. The transducer doesn’t know where it will be used - will it be used with a collection or with a channel? So, a transducer captures the essence of your transformation. sequence is responsible for providing the input to the transducer. This is the context in which the transducer will run.
Here’s how the same thing can be done using threading macro:
1 | (->> (range 10) |
The difference here is that the 2-arity version of map and filter will create intermediate collections while the 1-artity versions won’t. Transducers are much more efficient than threading together sequence functions.

Inside a transducer
Let’s look at the 1-arity version of map and see what makes a transducer.
1 | (defn map |
When you call 1-arity version of map you get back a transducer which, as shown above, is a function. Functions like map, filter, etc. take a collection and return a collection. Transducers, on the otherhand, take one reducing function and return another. The function returned is expected to have three arities:
So, the general form of a transducer is this:
1 | (fn [rf] |
Using transducers
You can use a transducer in a context. There’s four contexts which come out of the box — into, transduce, sequence, and educe.
into
The simplest way to use a transducer is to pass it to into. This will add your transformed elements to an already-existing collection after applying the transducer. In this example, we’re simply adding a range into a vector.
1 | (let [xf (comp (map inc) |
Internally, into calls transduce.
transduce
transduce is similar to the standard reduce function but it also takes an additional xform as an argument.
1 | (let [xf (comp (map inc) |
sequence
sequence lets you create a lazy sequence after applying a transducer. In contrast, into and transduce are eager.
1 | (let [xf (comp (map inc) |
eduction
eduction lets you capture applying a transducer to a collection. The value returned is an iterable application of the transducer on the collection items which can then be passed to, say, reduce.
1 | (let [xf (comp (map inc) |
Inside a transducible context
As mentioned before, transducers run in transducible contexts. The ones that come as a part of clojure.core would suffice most real-world needs and you’ll rarely see yourself writing new ones. Let’s look at transduce.
1 | (defn transduce |
transduce is just like reduce. The 3-arity version expects an initial value to be supplied by calling the 0-arity version of the supplied function. The 4-arity version is slightly more involved. IReduceInit is an interface implemented by collections to let them provide an initial value. It lets a collection reduce itself. If not, the call goes to coll-reduce which is a faster way to reduce a collection than using first/next recursion.
Stateful transducers
It’s possible for transducers to maintain reduction state.
1 | (defn multiply-xf |
Here’s a transducer which will multiply all the incoming numbers. We maintain state by using a Volatile. Whenever we get a new input we multiply it with the product and update the state of Volatile using vswap!. Let’s see this in action:
1 | (into [] (multiply-xf) [1 2 3]) |
Early Termination
The way the above transducer is written, it’ll process all the inputs even if one of the inputs is zero. We know that once we encounter a zero, we can safely end the reduction process and return a zero. reduced lets you return a reduced value and end the reduction. Let’s make a minor change to the above transducer and add in early termination.
1 | (defn multiply-xf |
In the 2-arity function, we check if the new-product is zero. If it is, we know we have a reduced value. We end the reduction by returning the result we have so far. Let’s see this in action:
1 | (into [] (multiply-xf) [1 2 3]) |
Conclusion
Transducers can be a very useful tool in your Clojure toolkit that let you process large collections, channels, etc. effectively by letting you make composable transformation pipelines that process one element at a time. They require a little getting used-to but once you’re past the learning curve, performance galore!