# Transducers
A **transducer** is a reusable transformation pipeline independent of
the source or sink. Transducers let you write the "map + filter + take"
part once and apply it to any collection, reducer, or channel.
Transducers are stateful functions: `(rfn) → rfn'`. They compose, they
terminate early, and they don't allocate intermediate collections.
## Quick Start
# A transducer is built by composing 1-arg forms def xf [comp [map {|x| ($x * 2)}] [filter is-even] [take 10]] # Apply it wherever a reducing context is expected [into @{} xf $xs] # conj into a list [into ^{} xf $xs] # add into a set [transduce xf + 0 $xs] # fold via a plain fn [transduce xf [completing +] 0 $xs] # explicit Reducer wrap
## The Two Forms
`map`, `filter`, `take`, `drop`, and `mapcat` are **arity-overloaded**:
- **1-arg** returns a transducer: `[map f]`, `[filter pred?]`, `[take n]`
- **2-arg** returns a lazy seq: `[map $xs f]`, `[filter $xs pred?]`
The lazy-seq form is covered in [Lazy Sequences](lazy-sequences).
Transducers are the 1-arg form composed with `comp`.
## Composing Transducers
`comp` composes transducers left-to-right (data flows through them in
declaration order):
def xf [comp [filter { ($it % 2) == 0 }] [map { $it + 1 }] [take 3]] [into @{} xf @{ 1 2 3 4 5 6 7 8 }] # => @{ 3 5 7 }
Note that `comp` runs in source order — `filter` before `map` before
`take` — unlike mathematical function composition.
## Applying a Transducer
### `into` — conj into a collection
`[into target xf source]` walks `source` through `xf`, conjoining each
result element into `target`. Target can be a list literal, a set
literal, a map literal, or any existing collection:
[into @{} xf $xs] # list
[into ^{} xf $xs] # set (duplicates collapse)
### `transduce` — fold with a custom reducer
[transduce xf + 0 $xs] # sum of transformed elements [transduce xf str "" $xs] # concatenate
The reducer can be a plain 2-arg callable (auto-wrapped via
`completing`) or a `%Reducer{:init :step :done}` tagged map.
### `%Reducer` — full reducing protocol
A reducing function is a `%Reducer{:init :step :done}` tagged map. The
`:init` and `:done` keys may be `nil`. `completing` wraps a plain 2-arg
function as a `%Reducer`:
def sum-reducer %Reducer{ :init {|| 0} :step {|acc x| ($acc + $x)} :done {|result| [println "sum was $result"] } } [transduce xf $sum-reducer 0 $xs]
## Early Termination
`(take n)` short-circuits any reducing context — not just lazy seqs.
Under the hood this uses `%Reduced`:
# A transducer chain with take inside def xf [comp [map { $it * 2 }] [take 3]] # Even when transducing into a sum, the chain stops after 3 elements [transduce xf + 0 @{ 1 2 3 4 5 6 7 8 9 10 }] # => 12 (only 1,2,3 were processed: 2 + 4 + 6)
### `reduced` / `reduced?` / `unreduced`
Build your own early-termination step:
def first-even {| set step {|acc x| if (is-even x) { [reduced x] # wrap to terminate } { $acc } | } %Reducer{ :init {|| nil} :step $step :done nil } | } [transduce [filter identity] $first-even nil @{ 1 3 5 6 7 }] # => 6 [reduced? [reduced 42]] # => true [unreduced [reduced 42]] # => 42 [unreduced 42] # => 42 (passthrough)
## Lazy Seq vs Transducer
| Aspect | Lazy seq | Transducer |
|---|---|---|
| Source | Always a collection | Any reducing context |
| Sink | Always a list (via `collect`) | List, set, map, sum, custom |
| Reuse | Per-call | Reusable across collections |
| Early termination | `take n` in the chain | `take n` **or** `%Reduced` in any step |
| Best for | One-shot, list-out, readable | Reusable, multi-sink, performance-critical |
## When To Reach For Transducers
- The same transformation pipeline is applied to multiple collections
or sinks.
- The sink is something other than a vector (set, map, sum, custom
reducer).
- You want explicit early termination across the whole chain.
- You're feeding a channel or async task that consumes via a reducing
function.
For one-shot collection-to-collection transforms where readability
matters most, prefer `|>` pipelines (see [Lazy Sequences](lazy-sequences)).
## Examples
### Sum of squares of first 10 evens
def xf [comp [filter { ($it % 2) == 0 }] [map { $it * $it }] [take 10]] [transduce xf + 0 [range 0 1000]] # => 0 + 4 + 16 + ... + 36 (= sum of squares of 0,2,4,...,18)
### Index a list into a map
def index-by-name [comp [map {|x| %{ :key $x~name :val $x }}] (mapcat {|kv| @{ $kv }})]) # unfold into key/val pairs [into ^{} index-by-name $users] # set of users by name
### Compose with multiple sources
def normalize [comp [map trim] [filter not-empty?] [map lower]] [into @{} normalize $raw-lines] # one source [into @{} normalize $other-lines] # different source, same pipeline
## Quick Reference
| Form | Purpose |
|---|---|
| `[map f]` | 1-arg transducer |
| `[filter pred?]` | 1-arg transducer |
| `[take n]` / `[drop n]` | 1-arg transducer |
| `[mapcat f]` | 1-arg transducer |
| `[comp xf1 xf2 ...]` | Compose transducers |
| `[into target xf source]` | Conj into a collection |
| `[transduce xf rfn init source]` | Fold via reducer |
| `[completing f]` | Wrap a plain fn as `%Reducer` |
| `[reduced x]` / `[reduced? x]` / `[unreduced x]` | Early-termination protocol |