DEV Community

artydev
artydev

Posted on • Edited on

I am trying to understand what are transducers - part 1

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 

Enter fullscreen mode Exit fullscreen mode

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)