3

There was an excellent post from trincot in Oct 2016 regarding construction of a piping function using the reduce method (link to posting), and I'm trying to understand the code in detail.

ES6 Version

function piper(...fs) {
    return (...args) => fs.reduce((args,f) => [f.apply(this,args)],args)[0];
}

Specifically:
1. Instead of [f.apply(this.args)], f(...args) also works, but f(args) doesn't work. Why doesn't f(args) work?
2. Regarding the second parameter in the internal (anonymous) "reduce" function, why is args even necessary? . . ., i.e., why doesn't the following code work?

function piper(...fs) {
    return (...args) => fs.reduce((args,f) => [f.apply(this,args)])[0];
}

B. Alternatively, why can't we just use [] as a parameter instead of args?

function piper(...fs) {
    return (...args) => fs.reduce((args,f) => [f.apply(this,args)],[])[0];
}

ES5 Version

function piper(/* functions */) {
    var fs = [].slice.apply(arguments);    
    return function (/* arguments */) { 
        return fs.reduce(function (args,f) {
            return [f.apply(this,args)];
        }.bind(this), [].slice.apply(arguments))[0];
    }.bind(this);
}

Questions: 1. In the internal (anonymous) reduce function, why do we need the second parameter [].slice.apply(arguments)? i.e., why doesn't [] work in in its place?
2. Alternatively, why can't we just leave out that parameter entirely?

Many thanks in advance for helping me understand these issues at a deeper level.

Crowdpleasr
  • 3,574
  • 4
  • 21
  • 37

1 Answers1

1

The args passed as a second parameter to the reduce function are required to specify the initial value of the accumulator (that is, the args variable), so that on the first iteration,

fs.reduce((args,f) =>

results in args being the initial arguments (as expected). Otherwise, without passing an initial value to .reduce, the initial value of args will be the first item in the fns array, which definitely isn't what you want:

function piper(...fs) {
    return (...args) => fs.reduce((args,f) => {
      console.log(args);
      return [f.apply(this,args)];
    })[0];
}

var result = piper(Math.min, Math.abs, Math.sqrt)(16, -9, 0)
// Output result:
console.log(result);

In addition, like this, the first function in the fns array will never be called at all.

Generally, you only leave out an initial value for .reduce when the type of the accumulator matches the type of the items in the array being iterated over and matches the desired type of the result, like with number manipulation:

// sum:
const result = [1, 2, 3].reduce((a, b) => a + b);
console.log(result);

Of course, if there's ever a chance of the array being iterated over being empty, you should also provide an initial value.

In (almost?) all other situations, providing an initial value is necessary for the .reduce to work.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 1
    Even there it would probably be appropriate to provide an initial value of `0` so an empty array won't throw when calling `reduce()` on it. – Patrick Roberts Feb 02 '19 at 06:31