简体   繁体   中英

How Javascript `reduce` performed on function array achieves function composition?

I came across this pattern in redux compose function. I still don't understand how in the example below the functions are evaluated starting from the last and not from the first:

function f2(a) {
  return a + a;
}
function f3(a) {
  return a + a + a;
}
function f4(a) {
  return a + a + a + a;
}
function f5(a) {
  return a + a + a + a + a;
}

function compose(...funcs) {
  return funcs.reduce(function x(a, b) {
    return function y(...args) {
      const temp = a(b(...args));
      return temp;
    };
  });
}

const composedFunction = compose(f2, f3, f4, f5);
const result = composedFunction(2);

In the first reduce iteration the accumulator is f2 so we'll get f2(f3(2))=12 . In the next iteration we'll call f4(12)=48 . In the last iteration we'll call f5(48)=240 . So the evaluation order is f5(f4(f2(f3(2)))) . But using console.log I see that the evaluation order is f2(f3(f4(f5(2)))) which is also 240 by coincidence.

As far as I understand the function y is called for all array elements so why only the last function gets 2 as the parameter?

Let's step through the code with a very simple example:

 compose(f2, f3, f4)

As no initial value was passed to reduce, it will start with the first (f2) and the second (f3) value of the array and call the callback with that, x gets called with a being f2 and b being f3 . Now x does'nt do anything, it just returns function y that can access a and b through a closure.

Reduce will now continue to the third element, the first argument being the result of the previous callback (the closured y ), and the second argument being f4 . Now x gets called again, and another closure is created over y , y gets the finally returned from the whole function.

If we try to visualize thus closured function it'll be:

 y { // closure of y
  a -> y { // a references another closure of y
    a -> f3,
    b -> f2
  },
  b -> f4
}

Now you call that closured y and pass 2 into it, that will call b ( f4 ) and pass the result to the call to a (closured y).

 a         (  b(...args))   
 y { ... } (  f4(2) )

Now that closured y will do the same:

 a (  b ( ...args))
 f2( f3( f4( 2 ) ) )

Hint: It is sometimes really difficult to keep track of closured values, therefore the console provides you with great utilities to keep track of them: Open your code in the consoles "debugger" tab, click on the line numbers where the function calls are to attach breakpoints, then run the code again, the execution will yield whenever a breakpoint is reached and you can see the values of all variables (including closured ones).

The reduce is not calling the functions f2, f3, f3, f5, but it is creating a function from those. This is the value of the accumulator in each iteration. Note that the value is a function and not a result from execution of the function.

1:a=f2;b=f3;return value(NOT TEMP but function y)=f2(f3(...args))

2:a(prev return value)=f2(f3(...args));b=f4;return value=f2(f3(f4(...args)))

and so on....

The compose function can be re-written as:

function compose(...funcs) {
  return funcs.reduce(function (a, b) {
    return function (arg) {
      const temp = a(b(arg));
      return temp;
    };
  });
}

After the first iteration, the returned function which is passed in as the next accumulator is:

function (arg) {       // R1
  return f2(f3(arg));

}

After the second iteration, the returned function which is passed in as the next accumulator is:

function (arg) {       // R2
  return R1(f4(arg));

}

And finally, the returned function assigned to composedFunction is:

function (arg) {       // composedFunction
  return R2(f5(arg));

}

So running composedFunction(2) and going back up the chain:

f5(2) returns 10

R2(10) returns R1(f4(10))
which is       R1(40)

R1(40) returns f2(f3(40))
which is   f2(120)
which is   240

Hopefully that's sufficient.

It can be written as a single call as:

function composedFunction(arg) {
  return f2(f3(f4(f5(arg))));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM