I would like to ask why is 0 part of the argument?

36 Views Asked by At

I get the entire logic of the reduce method, and rest parameter, however I really don't get the importance of 0. Thank you very much in advance!

const sum = (...args) => {
   return args.reduce((a,b) => a + b, 0);
}
1

There are 1 best solutions below

1
Jörg W Mittag On

ECMAScript is a dynamically typed / untyped / uni-typed programming language (depending on whom you ask). However, it still makes sense to look at the types of Array.prototype.reduce here.

I am going to use TypeScript syntax, in fact, I am going to use the definition from the TypeScript library, but the actual syntax doesn't really matter:

reduce<U>(
    callbackfn: (
        previousValue: U,
        currentValue: T,
        currentIndex: number,
        array: readonly T[]
    ) => U,
    initialValue: U
): U;

The fact that the original array and the current index are passed into the callback function is an ECMAScript oddity that is not really necessary to understand how reduce works, so let's get rid of those:

reduce<U>(
    callbackfn: (
        previousValue: U,
        currentValue: T,
    ) => U,
    initialValue: U
): U;

Note also that we have two different type parameters here:

  • T is the element type of the Array<T>.
  • U is the result type of reduce.

So, reduce is a function which takes three arguments:

  • The invisible this, which is the Array<T> it iterates over.
  • A function which combines a U and a T and produces a new U.
  • An initial value of type U.

Conceptually, what reduce does is that it "reduces" a collection of values down to a single value. It is important to understand that this single result value can have an arbitrary type. It does not have to be the same type as the element type of the Array.

reduce does this using the reduction function that is passed to it.

So, conceptually what reduce returns is

callbackfn(
    callbackfn(
        callbackfn(
            callbackfn(
                callbackfn(
                    callbackfn(
                        callbackfn(
                            callbackfn(
                                initialValue,
                                this[0]
                            ),
                            this[1]
                        ),
                        this[2]
                    ),
                    this[3]
                ),
                this[4]
            ),
            this[5]
        ),
        this[6]
    ),
    this[7]
)
// and so on …

Or, if we write the callback function as a binary operator ω:

initialValue ω this[0] ω this[1] ω this[2] ω this[3] ω …

Note that the callback function must be left-associative, i.e. equivalent to

(((((initialValue ω this[0]) ω this[1]) ω this[2]) ω this[3]) ω …)

Many collections libraries, and ECMAScript is no exception, also provide a version of reduce that does not have an initial value. In many libraries, this version has a different name (for example, in Scala, the version with initial value is called foldLeft, the version without is called reduceLeft, in Haskell, the version with initial value is called foldl, the version without is called foldl1). In ECMAScript, this is instead handled via "overloading", i.e. you can simply leave out the initialValue argument.

If the initial value is left out, we instead start off with the first element of the collection, i.e.

callbackfn(
    callbackfn(
        callbackfn(
            callbackfn(
                callbackfn(
                    callbackfn(
                        callbackfn(
                            this[0],
                            this[1]
                        ),
                        this[2]
                    ),
                    this[3]
                ),
                this[4]
            ),
            this[5]
        ),
        this[6]
    ),
    this[7]
)
// and so on …

or written as an operator

this[0] ω this[1] ω this[2] ω this[3] ω …

This has an important consequence, though! Our operator / callback function now needs to take two Ts and return a T, there is no longer a way to have a result type that is different from the element type.

And it has another important consequence: reduce no longer works with an empty collection.

So, these would be the two reasons why you would need to supply an initial value:

  • You want to transform the type.
  • You want the reduce to work with an empty collection.

In this particular case, the first reason does not apply: the function is clearly meant to reduce an Array<number> to number. But the second reason is relevant: you don't want to force clients of the function to have to check whether or not the collection is empty. A client should be able to call

sum(...someArray)

and not worry whether someArray is empty or not.

The ability to change the type to an arbitrary type is important for the universality of reduce. It turns out that reduce is a powerful function: it can do everything you could do by iterating over a collection. In other words: you could remove every method of Array.prototype except reduce, and also remove for … of and for … in from ECMAScript, and you could still do everything you could possibly do by iterating over a collection. (Well, you'd need a way to add something to an array, I guess, so let's keep push as well.)

Here is just an example of implementing Array.prototype.map using Array.prototype.reduce:

Array.prototype.map = function map(callbackFn) {
    return this.reduce(
        (previousValue, currentValue) =>
            previousValue.push(callbackFn(currentValue)),
        []
    );
}

Here you see that the fact that we can change the type is important, because we start with an Array<T>, but we don't want a T as a result, we want an Array<U>. This also shows that the statement "reduce reduces the collection down to a single value" does not mean that the result has to be a simple value. The result value can be as complex as you like … in this case, another array.