4

JavaScript newbie here. Today I learned about reduce and set out to implement my own array flatten function.

What I had was

var array = [[1, 2], [3, 4, 5], [6]];
var result = array.reduce(Array.prototype.concat, []); // Causes Uncaught TypeError: Array.prototype.concat called on null or undefined
var result = array.reduce(Array.prototype.concat.call, []); // Causes Uncaught TypeError: undefined is not a function

While answers in Merge/flatten an array of arrays in JavaScript? are elegant and idiomatic, I'd really appreciate an illustration on how my attempts failed.

Community
  • 1
  • 1

3 Answers3

4

your code is equivalent of

result = array.reduce(fn, []);
function fn(a, b, index, array) { // this is the arguments reduce sends to the callback
    return Array.prototype.concat.call(null, a, b, index, array);
}

can you spot the problem?

Jaromanda X
  • 53,868
  • 5
  • 73
  • 87
2

You have the right idea with Array.prototype.concat.call. With Array.prototype.concat, the calls are going to look like this:

var concat = Array.prototype.concat;

concat(concat(concat([],
                     [1, 2]),
              [3, 4, 5]),
       [6])

which doesn’t work because Array.prototype.concat concatenates its arguments to its this; calling it as concat() gives it a this of undefined. How about with call?

var call = Array.prototype.concat.call;

call(call(call([],
               [1, 2]),
          [3, 4, 5]),
     [6])

This runs into the same problem, but with Function.prototype.call (Array.prototype.concat is a function like any other, and inherits its call method from Function.prototype). call tries to call its this, but calling it as call() gives it a this of undefined.

You could pass Function.prototype.call.bind(Array.prototype.concat)… if reduce didn’t call its function with more arguments than just the accumulator and the current item. But it does, passing the index of the current item and context array as well, and ruining any chance of making this work by passing only Array.prototype.concat wrapped with some builtins.

Ry-
  • 218,210
  • 55
  • 464
  • 476
  • _"You could pass `Function.prototype.call.bind(Array.prototype.concat)`… if reduce didn’t call its function with more arguments than just the accumulator and the current item."_ Could `.apply()` and `.slice()` be used to use only first two parameters? – guest271314 Feb 17 '17 at 05:44
  • @guest271314: Not easily as far as I know. This quickly gets much longer than `(m, n) => m.concat(n)`. – Ry- Feb 17 '17 at 05:46
  • Yes, perhaps not easily; though, is it possible? Also, is it possible to call a function reference passed to bind while also passing a parameter to the caller of bound function [How to call function that is parameter to Function.prototype.bind with value passed as parameter to Function.prototype.bind](http://stackoverflow.com/questions/40541169/how-to-call-function-that-is-parameter-to-function-prototype-bind-with-value-pas)? – guest271314 Feb 17 '17 at 05:54
  • @guest271314 @Ryan is there a way in JavaScript to modify the arity of a function without wrapping it in higher order one? I had `var takeTwo = function (func) { return function(...args) { return func(...args.slice(0,2)); }; };` and then `array.reduce(takeTwo(Function.prototype.call.bind(Array.prototype.concat)), [])` worked. But defining `takeTwo` defeated the purpose. – Mengchen Yu Feb 18 '17 at 03:12
  • 1
    @MengchenYu: There might be a way to do something with another layer of `reduce`, but I’m not really that interested in finding it… there’s nothing special about builtins compared to user-defined functions here. – Ry- Feb 18 '17 at 03:16
0

This is so because Array.prototype.concat requires a function prototype such that it is an array or list of values to be concatenated to the main array.

However, the callback to Array.prototype.reduce requires an accumulator, currentValue, currentIndex and array as parameters.

The parameters (as well as what they're supposed to do) don't match and hence, you get unexpected results or errors.

Soubhik Mondal
  • 2,666
  • 1
  • 13
  • 19