DEV Community

artydev
artydev

Posted on • Edited on

1

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.

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more