~ / track B / clojure advanced
Transducers
AdvancedA transducer is a transformation decoupled from its input and output.
The same filter or map logic that you'd normally apply to a vector can be
fused into a single pass over a stream, a channel, or any sink — without
allocating intermediate collections. Transducers are how Clojure makes
"map then filter then take" fast at scale.
The shape of the problem
The familiar ->> pipeline is clear, but each step builds a new lazy seq:
For 1,000 items it doesn't matter. For 1,000,000, or for streaming through a channel, each intermediate sequence costs memory and indirection.
Minimal example: build a transducer
When you call map, filter, etc. without a collection, they return a
transducer — a recipe — instead of doing the work:
comp here reads top-to-bottom: increment, then filter, then take 5. (It's
the opposite of comp on plain functions because transducers compose into a
nested transformation chain.)
Now apply the same xf to different "contexts":
One recipe, three sinks. No intermediate collections were allocated.
Why this matters
- No intermediate seqs.
(map f (filter pred xs))builds a seq for the filter, then another for the map. A transducer fuses them into one pass. - Sink-agnostic. The same
xfruns over collections, lazy seqs, channels, reducible sources, even Java streams via interop. You write the pipeline once and reuse it. - Early termination is real.
take,take-while, etc. signal "done" and the rest of the pipeline stops — important for both performance and infinite sources.
Stateful transducers
Some transducers carry state across items. dedupe only emits values
different from the previous one, partition-all buffers groups, etc. They
still work fine in a pipeline, but the state is per-application — it's safe
to reuse the recipe.
Writing one by hand
A transducer is a function xf that takes a "reducing function" and returns
another reducing function. You usually don't write them by hand, but seeing
the shape helps demystify the API:
The three arities are init / completion / step — almost all transducers follow that shape.
Check yourself
? quiz
What is the practical advantage of `(into [] (comp (map inc) (filter even?)) xs)` over `(->> xs (map inc) (filter even?) vec)`?
Exercise
Build a transducer xf that, over (range 100), keeps only multiples of 3,
squares them, and stops after the first 4. Apply it with into [] and with
transduce + 0 to see both shapes.