transduce
description
Returns an output
value after reducing an input collection
using the provided transformer
and reducer
.
The reducer function should have the following type signature:
(Any accumulator, Any value, Any key, Collection collection) => Any output
, where:
accumulator
is the result of the previousreduce
iterationsvalue
is the value of the current item in the iterationkey
is the key of the current item in the iterationcollection
is the input collection being iterated on
The transformer function should accept a reducer and return another reducer function, and thus have the following type signature
(Any accumulator, Any value, Any key, Collection collection) =>
(Any accumulator, Any value, Any key, Collection collection) => Any output
Calling transduce(transformer, reducer, seed, collection)
is equivalent to calling reduce(transformer(reducer), seed, collection)
.
transduce
is basically equivalent to reduce
with a separation of concerns between the transforming operation and the reducing one (hence the name trans-duce), favoring clarity and code reuse. Although it is very similar to reduce, it can be hard to grasp how it works and its usefulness. If you're not familiar with this concept, I highly suggest you read the excellent article What's a Transducer? by Reginald Raganwald Braithwaite.
example
basic example
Suppose we have an array of characters
, each having a name
and a side
property. We want to get a new array containing only the names of the good guys. If we look closely at what we want to achieve, we can decompose it into 2 parts:
Transforming each item (filtering it and accessing a property)
Combining these items in a new array
Of course, we could also use map
and filter
to perform this operation, like so:
But that would also mean the characters
array would be iterated over twice, which seems a pity.
Using transduce
ensures the iteration is only done once, no matter how many transformations are done on each item.
concatNames
is our reducer, simply adding a name to our array accumulator and returning it.retrieveGoodGuysName
is our transformer: it accepts a reducer and returns another one, but decorated with the actual transformation we want to performseed
is simply an empty array
During the transduce operation, concatNames
is decorated by retrieveGoodGuysName
and then our characters array is iterated on, and each item goes through the transforming operation before the reducer gets called. In the end, we get ['Luke', 'Han']
. Hooray!
optimizing the previous example with utility transformers
Let's get back to our previous example, and especially this part where I said
Suppose we have an array of
characters
, each having aname
and aside
property. We want to get a new array containing only the names of the good guys. If we look closely at what we want to achieve, we can decompose it into 2 parts:
Transforming each item (filtering it and accessing a property)
Combining these items in a new array
Well, if you look at it even more closely, we can decompose (again!) the transforming part into 2 subparts:
Filtering (removing bad guys in our case)
Mapping (mapping an input value to an output)
Any transformation on a collection is actually a more specific version of reduce, and if you think of it, any transformation can be expressed as a combination of one or several mapping/transforming operations.
So conductor also features two utility transformer functions, which can be very useful in the context of transducers:
transformers/filter
transformers/map
You can write any data transformation as a combination of those two functions. Let's get back at our example.
Let's re-define our retrieveGoodGuysName
as a combination of filter
and map
:
The filtering operation, isGood
, will be done using transformers/filter
to check if the item's side property is equal to light. The mapping
operation, getName
, will be done using transformers/map
and simply consists of retrieving the name property.
Notice how these 2 utility transformer functions are not imported directly from conductor
but rather from conductor/transformers
. Also, since transformers/filter and transformers/map
are higher order functions, they compose in a counterintuitive fashion.
Our full code is now:
Last updated