Reputation:
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.
Upvotes: 17
Views: 4841
Reputation: 11706
There is no need for a thisObject argument, bacause you can add a kind of thisObject in the accumulator and use the initial value to pass the thisObject.
Example:
thisObj = { some data } // kind of this arg to be used in the reducer
const { view, used, totals } = sheetConfig.reduce(reducer, {
view: [], // initial
used: [], // another initial
totals: [], // and one more initial
thisObj // and ...
});
Upvotes: 1
Reputation: 2676
You can always use this method :
array.reduce(callback.bind(context), initialValue);
... in order to attach a specific context to the callback function.
Upvotes: 6
Reputation: 665090
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.
Upvotes: 4
Reputation: 12926
On the Es-discuss mailing list you find in answer http://article.gmane.org/gmane.comp.lang.javascript.ecmascript4.general/4770
The other methods with callbacks take a 'thisArg' not because it is needed or even useful, but for compatibility, because they already do in existing implementations that provide these functions.
Upvotes: 4