Transduction
Fusion only works if our data operations are all the same—all maps, all filters, or all reduces.
The trouble with combining any of these operations is that they have incompatible shapes:
Mapper takes an item and returns another item
Filter takes an item and returns a boolean
Reducer takes 2 items and returns an accumulator
If we want to compose these operations together, this is known as transduction.
High-level, transduction works by reshaping the functions into compatible shapes that can be composed together. Specifically, it reshapes them all into reducers because transduction is composition of reducers.
Here's how a functional programming library would perform transduction:
Note how we don't pass the reducer sumUp
. That's because a transducer is a special type of reducer that only becomes a regular reducer when you pass it a reducer as an argument. It's a higher-order reducer.
To do this, you use the utility function transduce
and pass in the following:
Transducer
Combinator/reducer
Initial value
Data structure
Alternatively, we can use into
, which looks at the data type of the initial value and uses a default combinator.
Numbers use an addition combinator
Strings use a string concatenation combinator
Arrays use a push combinator
Note: If the default combinators don't fit your use case, you want to use transduce
.
Deriving Transduction
Topics:
Modeling mappers and filters as reducers
Extracting reduce
How do you model a mapper and a filter as a reducer?
Let's look at the manual implementation using the array methods:
Instead of performing the reductions, let's now refactor mapWithReduce
and filterWithReduce
into functions that return the reducer itself.
Combiner and currying
In both reducers returned by mapReducer
and filterReducer
, there is a generic action happening: given list
and v
, these values are combined to return a new list
.
We are going to create a generic combiner
to and apply them to our specialized ones.
We can take this a step further and make mapReducer
and filterReducer
into higher-order reducers—i.e. transducers—by
Moving the combiner to a parameter, and
Currying the inputs.
That way when we pass the mapper or the predicate into either mapReducer
or filterReducer
(respectively), we get back a higher-order reducer that accepts a reducer in order to become a reducer.
Single reduce
Composing transducers
If you notice, mapTransducer
and filterTransducer
now have the same shape: they both accept a reducer as an input and return a reducer as output. That means you can compose them!
Effectively, what's happening is
listCombine
gets passed as input tofilterReducer(isEven)
, generating a filter reducerThat filter reducer gets passed as an input to
mapReducer(multiply2)
, generating a map + filter reducerIn other words, the filter reducer becomes the
combiner
argument inmapReducer
So,
combiner(list, mapper(v))
takes a mapped value and then filters it
Simplifying reducer inputs
Finally, now we have 2 reducers left. But how do we simplify it to just 1 reducer? Simple: just pass sumUp
directly as the reducer. Why bother using the intermediary reducer listCombiner
at all?
Last updated