15.4.4.21 Array.prototype.reduce ( callbackfn [ , initialValue [ , thisArg ] ] )

# Edward Lee (16 years ago)

Right now [].reduce doesn't take an optional thisArg, so the callback is always called with |null| for |this| per 9.c.ii.

The Array prototype object take an optional thisArg for every, some, forEach, map, and filter; so it would be good to make reduce consistent with the rest.

The new parameter list for Array.prototype.reduce would then be..

15.4.4.21 Array.prototype.reduce ( callbackfn [ , initialValue [ , thisArg ] ] )

Insert after step 4: #. If thisArg was supplied, let T be thisArg; else let T be undefined.

For step 9.c.ii, replace: "callbackfn with null as the this value" with.. "callbackfn with T as the this value"

Similarly, this should be applied to reduceRight as well: 15.4.4.22 Array.prototype.reduceRight ( callbackfn [ , initialValue [ , thisArg ] ] )

The only remaining Array callback is sort, which could be updated as well: 15.4.4.11 Array.prototype.sort ( comparefn [, thisArg ] )

Ed

# David-Sarah Hopwood (16 years ago)

Edward Lee wrote:

Right now [].reduce doesn't take an optional thisArg, so the callback is always called with |null| for |this| per 9.c.ii.

The Array prototype object take an optional thisArg for every, some, forEach, map, and filter; so it would be good to make reduce consistent with the rest.

Why is it better to use 'this' than to simply have the callback function capture the variables it needs? The latter is just as expressive and IMHO results in clearer code, since:

  • the captured variables are named, and the names can be more meaningful than 'this';
  • there can be more than one such variable, without needing to set 'this' to an object or list.

The required variables are necessarily in scope when passing a FunctionExpression as the callback. The case where they are not in scope because the callback function is defined elsewhere is quite unusual; in that case, you can instead pass a lambda expression that calls the function defined elsewhere with these variables as explicit parameters. (That is a situation where using 'this' results in particularly unclear code, because the definition of what 'this' is set to may be far away from its use.)

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.

# Allen Wirfs-Brock (16 years ago)

Adding to David-Sarah's comments, here is how I would expect a call that needs to supply a thisArg to be expressed:

a.reduce(f.bind(thisArg),init)

It almost makes me wonder if we should drop the thisArg from the standard for the other "array extra" functions and leave it as an implementation extension: Pros: It's unnecessary, leaving it out of the standard may wean thisArg usage. Cons: Because of the legacy, all implementation are going to implement the thisArg any, so it is better to have a standard specification for it.

Of course, all the other "array extras" are specified with a thisArg so consistency argues that reduce and reduceRight should be specified that way too.

# Edward Lee (16 years ago)

On Sat, Mar 21, 2009 at 9:50 AM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:

Why is it better to use 'this' than to simply have the callback function capture the variables it needs?

It's nice to be able to consistently refer to the same 'this' from an prototype's function whether from inside a local lambda or another function on that prototype. Any generic function that takes 2 arguments and returns 1 can be used for reduce, but if that callback if a prototype's function, its 'this' will be wrong unless you provided extra code to bind the function to an object.

Yes, you can achieve this in other ways by just binding the callback to the object before passing it to reduce, so one minor benefit is that it's more compact:

[].reduce(fn, init, this) [].reduce(fn.bind(this), init)

But the main reason is just consistency with the rest of the functions that take a callback.

Ed

# Brendan Eich (16 years ago)

On Mar 21, 2009, at 9:01 AM, Allen Wirfs-Brock wrote:

Adding to David-Sarah's comments, here is how I would expect a call
that needs to supply a thisArg to be expressed:

a.reduce(f.bind(thisArg),init)

That's one way; the other way was to use a closure. Dave Herman
developed the reduce (and reduceRight) specs for ES4 and JS1.8 based
on my proposals, without thisArg in order to keep the signature short
and Pythonic. Here's Dave's write-up, I don't think he'll mind me
sharing it:

SYNOPSIS:

  • Haskell and ML both have essentially the same versions of
    "foldl" (i.e., reduce left-to-right) and "foldr" (i.e., reduce right- to-left), other than laziness and slight differences in currying.

  • Haskell also has "foldl1" and "foldr1", which don't take the "basis
    element" argument, and use the first element of the list instead.

  • SRFI 1 has "fold" and "fold-right", which are similar to ML and
    Haskell, except they can take any number of lists, and the function
    argument has to accept one argument for every list. (This is hard to
    give precise types for, though, and it doesn't fit into a single- dispatch OO paradigm.)

  • SRFI 1 also has what it calls "reduce" and "reduce-right", which are
    optimized special cases of fold and fold-right: they require the basis
    element to be a right identity of the function argument, so they don't
    have to call the function argument in the one-element case (in case
    that function is expensive). This is only applicable when the basis
    element is a right identity, though.

  • Python has "reduce" which takes a function, a list, and optionally a
    basis element. If the basis element is provided, it's essentially
    foldl. If the basis element is not provided, it's essentially foldl1.

MY SUGGESTION:

I think we should offer a reduce method similar to Python's except as
a method on arrays (to be consistent with JS 1.6's map). However,
because of the JS 1.6 precedent for the signature of map, it's gonna
be hard to add an optional argument, since it already accepts an
optional "thisObject". I'm guessing this is because we won't have
bound methods until ES4, so anything that accepts a callback should
also accept an optional "this" object to use in the call to the
callback.

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 do something like:

arr.map(function(x,i,me) { return obj.method(x,i.me) })

to ensure that the desired method gets called with the appropriate
binding for "this". Then in ES4 they'll be able to do even better with
bound methods.

This way we could then make the signatures of map and reduce signature
really simple:

map(callback)
reduce(callback[, init])

The benefits:

  • just like Python => Python community mindshare
  • full generality of fold (left)
  • but also make the simple case, where the first element is the basis
    element, simpler

I'd guess most people find the left-to-right version of reduce more
intuitive, since they usually iterate over arrays from left to right.
Plus that's what Python does.

I think it would also be important to provide a reduceRight as well,
since not every operation is associative, and sometimes people need to
go from right to left.

Details attached.

Dave

1.) Haskell [www.haskell.org/ghc/docs/latest/html/libraries/base/Data-List.html#3 ]

foldl :: (a -> b -> a) -> a -> [b] -> a

foldl f z [x1, x2, ..., xn] = f (f ... (f (f z x1) x2) ...) xn

foldr :: (a -> b -> b) -> b -> [a] -> b

foldr f z [x1, x2, ..., xn] = f x1 (f x2 ... (f xn z) ...)

foldl1 :: (a -> a -> a) -> [a] -> a

foldl1 f (x1:xs) = foldl f x1 xs

foldr1 :: (a -> a -> a) -> [a] -> a

foldr1 f (x1:xs) = foldr f x1 xs

2.) ML [www.standardml.org/Basis/list.html#SIG:LIST.foldl:VAL]

foldl : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b

foldl f init [x1, x2, ..., xn] = f(xn, ..., f(x2, f(x1, init)) ...)

foldr : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b

foldr f init [x1, x2, ..., xn] = f(x1, f(x2, ..., f(xn, init) ...))

3.) SRFI 1 [srfi.schemers.org/srfi-1/srfi-1.html#FoldUnfoldMap]

fold : ('a1 * ... * 'ak * 'b -> 'b) * 'b * 'a1 list * ... * 'ak list -

'b (fold f init (list x1 x2 ... xn)) = (f x1.n x2.n ... xk.n ... (f x1.2 x2.2 ... xk.2 (f x1.1 x2.1 ... xk. 1 init)) ...)

fold-right : ('a1 * ... * 'ak * 'b -> 'b) * 'b * 'a1 list * ... * 'ak

list -> 'b (fold-right f init (list x1 x2 ... xn)) = (f x1.1 x2.1 ... xk.1 (f x1.2 x2.2 ... xk.2 ... (f x1.n x2.n ...
xk.n init) ...))

reduce : ('a * 'a -> 'a) * 'a * 'a list -> 'a

PRECONDITION: (f x ridentity) = x (reduce f ridentity ()) = ridentity (reduce f ridentity (list x1 x2 ... xn)) = (fold f x1 (list x2 ... xn))

reduce-right : ('a * 'a -> 'a) * 'a * 'a list -> 'a

PRECONDITION: (f x ridentity) = x (reduce-right f ridentity ()) = ridentity (reduce-right f ridentity (list x1)) = x1 (reduce-right f ridentity (list x1 x2 ... xn)) = (f x1 (reduce-right f ridentity (list x2 ... xn)))

4.) Python [docs.python.org/lib/built-in-funcs.html]

reduce(f, ls[, init])

reduce(f, ls) => same as Haskell's foldl1

reduce(f, ls, init) => same as Haskell's foldl

----- END DAVE CITE -----

It almost makes me wonder if we should drop the thisArg from the
standard for the other "array extra" functions and leave it as an
implementation extension: Pros: It's unnecessary, leaving it out of the standard may wean
thisArg usage.

The spec is not mommy; it can't reliably wean anyone off of anything
already out there in use.

The anti-|this| animus on parade in David-Sarah's message is at war
with compatibility and convention. It should not sweep all that came
before aside, by sub-committee fiat. I admit this makes things
messier, but that's life in Compatibility Kingdom (AKA the Web).

Cons: Because of the legacy, all implementation are going to
implement the thisArg any, so it is better to have a standard
specification for it.

Right. This is plausible with something ancient and in decline
(foo.arguments, e.g.) but for new stuff it's bad for business. We
should specify what implementations must do to interoperate when using
recently added methods.

Of course, all the other "array extras" are specified with a thisArg
so consistency argues that reduce and reduceRight should be
specified that way too.

I had to remind myself of Dave's analysis/argument, cited in full
above, to see why we didn't do that.

By breaking symmetry we have made at least one browser-based
implementation precedent (Rhino also supports reduce and reduceRight)
with no thisArg for the "fold" methods. At this point I would rather
we stand on precedent than waver or haver further.

Ed, what do you think? Apologies for forgetting the details.

# Douglas Crockford (16 years ago)

Brendan Eich wrote:

On Mar 21, 2009, at 9:01 AM, Allen Wirfs-Brock wrote:

By breaking symmetry we have made at least one browser-based implementation precedent (Rhino also supports reduce and reduceRight) with no thisArg for the "fold" methods. At this point I would rather we stand on precedent than waver or haver further.

I agree with Brendan on this.

# David-Sarah Hopwood (16 years ago)

Edward Lee wrote:

On Sat, Mar 21, 2009 at 9:50 AM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:

Why is it better to use 'this' than to simply have the callback function capture the variables it needs?

It's nice to be able to consistently refer to the same 'this' from an prototype's function whether from inside a local lambda or another function on that prototype. Any generic function that takes 2 arguments and returns 1 can be used for reduce, but if that callback if a prototype's function, its 'this' will be wrong unless you provided extra code to bind the function to an object.

Yes, you can achieve this in other ways by just binding the callback to the object before passing it to reduce, so one minor benefit is that it's more compact:

[].reduce(fn, init, this) [].reduce(fn.bind(this), init)

Very minor. '.bind(this)' has the advantage of working in general for all such cases, not just for particular Array methods.

In the "thisless" style where objects are constructed as closures rather than using prototypes, of course, this problem never happens.

But the main reason is just consistency with the rest of the functions that take a callback.

I accept that consistency is a valid consideration; I just don't think it outweighs the considerations given in my previous post. I'm not strongly opposed to adding 'thisArg' to these functions, though, if the concensus is in favour. My argument is primarily that they're not needed and that it is better for programs to use variable capture, and either the thisless style or '.bind(this)', instead.

# Edward Lee (16 years ago)

On Sat, Mar 21, 2009 at 11:57 AM, Brendan Eich <brendan at mozilla.com> wrote:

Of course, all the other "array extras" are specified with a thisArg so consistency argues that reduce and reduceRight should be specified that way too. I had to remind myself of Dave's analysis/argument, cited in full above, to see why we didn't do that.

What exactly was Dave's argument for not including a thisArg? There's a brief mention of multiple-optional-args-messiness, and then it talks about a future where other "array extras" don't have a thisArg.

So this would mean reduce takes one callback argument and two optional arguments: thisObject and init. Alternatively, we could just eliminate that extra "thisObject" argument,    arr.map(function(x,i,me) { return obj.method(x,i.me) })

vs arr.map(method.bind(obj) vs arr.map(method, obj)

At this point I would rather we stand on precedent than waver or haver further.

Reasonable. (I'll just probably end up using bind for all the other array extras ;))

Ed

# Igor Bukanov (16 years ago)

2009/3/21 Edward Lee <edilee at mozilla.com>:

On Sat, Mar 21, 2009 at 11:57 AM, Brendan Eich <brendan at mozilla.com> wrote: ...

Alternatively, we could just eliminate that extra "thisObject" argument,    arr.map(function(x,i,me) { return obj.method(x,i.me) }) vs arr.map(method.bind(obj) vs arr.map(method, obj)

The last line is no exact alternative to the previous one. In full one would write:

arr.map(obj.method.bind(obj)) or arr.map(obj.method, obj)

IMO this double-usage of obj looks strange compared with the closure version even if the latter is too verbose.

Igor

# Brendan Eich (16 years ago)

On Mar 21, 2009, at 10:26 AM, Edward Lee wrote:

On Sat, Mar 21, 2009 at 11:57 AM, Brendan Eich <brendan at mozilla.com>
wrote:

Of course, all the other "array extras" are specified with a
thisArg so consistency argues that reduce and reduceRight should be specified
that way too. I had to remind myself of Dave's analysis/argument, cited in full
above, to see why we didn't do that. What exactly was Dave's argument for not including a thisArg? There's a brief mention of multiple-optional-args-messiness, and then it talks about a future where other "array extras" don't have a thisArg.

Dave wrote:

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...

Indeed the other Array extras take (callback [, thisArg]) where []
means optional, but reduce and reduceRight want an optional
initialValue "fold basis". Without named parameters there's a problem,
since the initialValue is probably more commonly supplied than thisArg
(we intuit, and I think this is true from the reduce uses I've seen,
but more data welcome).

We could surely give reduce the parameters (callback [, initialValue
[, thisArg]]) but as Dave noted this breaks one symmetry (a more
superficial kind than the loss of thisArg? nevertheless...) with the
other Array extras, by not putting the optional thisArg parameter
immediately after callback.

The other way 'round makes the initialValue parameter case have to
supply a dummy thisArg, according to the hypothesis that initialValue
is more commonly supplied in actual use cases.

Then there's the Pythonic argument: "just like Python => Python

community mindshare". Of course Python's reduce is a function, not a
method, but the mapping is straightforward.