This post constitutes my steps to understand what are transducers.
You can correct, and modify the code here: Transducers
// Given a collection 'C'
const C = [1,2,4,5,6]
// Is it possible to apply mutiple transformations
// in one go, without create intermediates collections ?
// What tools do we have at our disposal to transform a collection.
// We can't use 'for' loop because it does not return any value
// That leads us to use the well known methods provided by the Array object,
// namely : map, filter, reduce.
// But we will soon see that those methods does not respond to our constraint, at least not whithout some manipulations
// Anyway, le's recap their use :
const incBy3 = x => x + 3;
const filterOdd = x => x % 2 == 0;
const sumReducer = (acc, current) => acc + current
// The map method
const incedByThree = C.map(incBy3);
// This filter method
const pairedOnly = C.filter(filterOdd);
// The reduce method
const summedValues = C.reduce((acc, current) => acc + current)
// All these methods accepts a collection as entries.
// but only map and filter return a collection.
// reduce return a unique value.
// If we want to chain those transormations, the reduce method
// must be the last one in the chain.
let chainedTransformation = C
.map(incBy3)
.filter(filterOdd)
.reduce(sumReducer)
// While we successfully transform our initial collection,
// at each stage of the transormation we iterate on a new collection.
// If there are many transformations and the collection is very big
// the performance are degraded, and the memmory consumption whould be very high.
// Before going further, let us precice some notions about the reduce method.
// We can pass an initial value to a reduce method.
// For a sum reducer this would be the value 0, for an array concataination this would be the empty array []
// The reduce method is passed what is called a "reducer"
// A reducer is function with the following signature : A, B => A
// The function takes two arguments of different type and return an element of the first type
// nothing less, nothing more
// In the following example, we pass the number 2 as the initial value
const doubleFactorial = [1,2,3,4,5].reduce((acc, value) => acc *= value, 2);
// Here we pass the empty array as the initial value
const flatArray = [1,2, [5, 6], 7].reduce((acc, value) => acc.concat(value), []);
// Let's continue our journey
// One question arise, for pure curiosity, is it possible
// to express map and filter using only the reduce method ?
//Let's try :
// helper
const concat = (a, b) => a.concat(b)
const mapWithReduce = function (func) {
return (acc, current) => acc.concat(func(current));
}
// mapWithReduce takes a function and return a reducer.
// Nice, we can then pass our newly reducer to the reduce method
// of our collection.
// Let us create, for example, multiply all the element
// of our collection by 5
const multipliedBy5 = C.reduce(mapWithReduce(x => x * 5), []);
// Let is create a reduce version of the filter method
const filterWithReduce = (predicate) => {
return (acc, current) => predicate(current) && acc.concat(current) || acc;
}
// Let's create and use an odd filter
const oddValues = C.reduce(filterWithReduce( x => x % 2 == 0), [])
// Our previous transformation can now be reexpressed like so:
const mulBy5 = x => x * 5;
const isOdd = x => x % 2 == 0;
const add = (x, y) => x + y
const mulBy5Reducer = mapWithReduce(mulBy5)
const oddReducer = filterWithReduce(isOdd)
chainedTransformation = C
.reduce(mulBy5Reducer, [])
.reduce(oddReducer, [])
.reduce(add, 0)
// Our main problem is still not resolved, bet we are on the way.
// In our transformation, there are three values than can vary :
// - the collection C
// - the reducer (mulBy5Reducer, oddReducer, add)
// - the initValue (0, []), let's call it 'step'
// - furthermore reducers are not composable, they take two paramaters in entry and return a single value
// We will see in the next post what composable functions.
// Let's play with these ideas and see where that leads...
// Let's us create the first function that comes in mind, let us call it 'transducer'
let transducer = (collection, reducer, step) => collection.reduce(reducer, step);
// We can rewrite this like so :
transducer = collection => reducer => step => collection.reduce(reducer, step);
// Here is anoter way to write it :
transducer = reducer => (collection, step) => collection.reduce(reducer, step);
// Let'us explain litterally what is our tranducer
// It's a function which takes a reducer as parameter and return a binary function, function which takes
// two parameters different types.
// I think you have guessed that this binary function is in fact a reducer !!!
// Indeed, it takes two parameters of different type (a type of array, a type of object) and return a type of array.
// That's exactly the definition of a transducer.
// We can proudly say :
// A transducer is a function that takes a reducer and return a reducer, nothing less, nothing more
So, ma comprehension till now is :
A transducer is a function that takes a reducer and return a reducer.
Don't miss the next post.
Top comments (0)