Search code examples
javascriptecmascript-5

Why does Array.prototype.reduce not have a thisObject parameter?


Javascript Array methods such as forEach have a thisArg parameter, which is used as the context for invoking the callback:

array.forEach(callback[, thisArg])

as do every, some, filter and map. However, reduce and reduceRight have no such parameter. Is there some particular reason for this, or some reason it is not necessary?

For instance, consider the following implementation of functional composition using reduceRight:

function compose () {
    var fns = [].slice.call(arguments,0);
    return function result() {
        return fns.reduceRight(
            function (prev,cur){
                return [cur.apply(this,prev)];
            },
            arguments
        )[0];
    };
}

I would like to make this "this-aware", so the functions being composed are called in the context in which the function returned by compose is invoked. Currently they appear to be invoked in the context of the global object. I could do the old var self=this; at the top of function result, and use that as the first argument to the cur.apply call, where I currently have this, but that would be unnecessary if reduce took a thisArg argument.

Am I missing something here, and is there something about reduce that makes this unnecessary or unuseful?

UPDATE

@kangax Yes, that occurred to me. Far be it from me to criticize the design of the API, but the signature for reduce seems a bit strange to me. The second optional argument functions differently than normal optional arguments, which typically just have a default value; instead its presence or absence changes the behavior, essentially overloading the function based on signature (argument count). When the second parameter is absent, the first element of the array becomes the starting value and the first call to the callback is against the second value. It seems to me that this behavior could be easily emulated by simply calling

array.slice(1).reduce(fn,array[0])

instead of building in special rules for the case where the second argument is omitted, which in turn, if your presumption is correct, also made it essentially impossible to figure out where to specify the thisArg argument. Then again, I am sure such issues were already debated while the spec was being hashed out, and there may be good reasons for such an approach.


Solution

  • It becomes messy with two optional arguments, since reduce(Right) already covers two functionalities (see Wikipedia), which are distinguished in pure languages (e.g. named foldl and foldl1 in Haskell). To cite Brendan Eich:

    So this would mean reduce takes one callback argument and two optional arguments: thisObject and init. Which one should come first? The more common one is probably init, but then you separate the callback arg from the "thisObject" arg, which is maybe okay. Multiple optional arguments are kinda messy this way...

    Alternatively, we could just eliminate that extra "thisObject" argument, since people can always [use binding].

    I don't think it's a big issue, since these functional higher-order-functions are mostly used with lamdba-function-expressions anyway (like in your example). Of course there's a little inconsistency, but we can live with that. Image the alternative:

     array.reduce(callback[, initialValue[, thisArg]])
    

    Can't be really used, we cannot really determine "if an initialValue was provided" since that means arguments.length < 2 - we could pass undefined literally as well. So that means

     array.reduce(callback[, thisArg[, initialValue]])
    

    which is ugly since we always needed to pass null or something to thisArg if we only wanted an initial value.

    You noticed that already in your comment to Kangax ("The second optional argument functions differently than normal optional arguments, […] its presence or absence changes the behavior"), but I can't support your statement

    this behavior could be easily emulated by simply calling array.slice(1).reduce(fn,array[0])

    as that would a) not work with a complex (chained) expression instead of the array variable and b) is cumbersome.