Search code examples
javascriptreducefunction-composition

JavaScript bind sets the second argument equal to the first one when it should be undefined?


I recently tried to understand function composition in javascript. I understood what currying is and how it works, but I saw this code and I can't really understand it. It's function composition using .reduce(...). This is the code:

const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));

const add5 = x => x + 5;
const add10 = x => x + 10;
const multiply = (x, y) => x * y;

const multiplyAndAdd5 = compose(
  add10,
  add5,
  multiply
);


console.log(multiplyAndAdd5(5, 2));

What I don't understand is the reduce function, I tried to break it down into this :

const compose = (...fns) => fns.reduce(
  (f, g) => (...args) => {
    console.log("f: ");
    console.log(f);
    console.log("g: ");
    console.log(g);
    for(let i = 0 ; i < 3 ; i++){
      console.log('');
    }
    return f(g(...args))
  },
);

In the console this shows up:

f: 
(...args) => {
    console.log("f: ");
    console.log(f);
    console.log("g: ");
    console.log(g);
    // console.log("args: ");
    // console.log(...args);
    // console.log(f(g(...args…
g: 
(x, y) => x * y

f: 
x => x + 10
g: 
x => x + 5
15 
25

What I don't understand is what the accumulator actually is in that function and in the first part of the reduce function, f is the function itself so, what is f(g(...args)) in that situation ?

Does anyone know how function composition work in javascript using .reduce() ?


Solution

  • reduce can be thought of as putting an operator between each pair of items in a sequence. E.g. reducing [1, 2, 3] using * would produce 1*2*3 (by performing ((1)*2)*3); in the same way, reducing with function composition would reduce [f, g, h] into f∘g∘h (by performing ((f∘g)∘h), also written as (...args) => f(g(h(...args))).

    When you don't give an initial value to reduce, it takes the first element of the array; so accumulator starts off as f. Operating on the element g, it returns a new function (...args) => f(g(...args)) (also known as f∘g). In your example, the initial accumulator is add10, the element is add5, and the result is a function (...args) => add10(add5(...args)); let's call it add10AfterAdd5.

    In the next iteration, the accumulator is f∘g, and the element is h. The result is a new function (...args) => (f∘g)(h(...args)), which is equivalent to (...args) => f(g(h(...args))) (also known as f∘g∘h). In your example, the accumulator is add10AfterAdd5, and the element is multiply; the result is (...args) => add10AfterAdd5(multiply(...args)), or (...args) => add10(add5(multiply(...args))), or add10AfterAdd5AfterMultiply.