rakib .h
rakib .h

Reputation: 21

ES6 function parameter

I recently picked up a book called Javascript Allonge for learning the language. And I'm struggling at certain chapters and topics.

const foldWith = (fn, terminalValue, [first, ...rest]) =>
first === undefined
? terminalValue
: fn(first, foldWith(fn, terminalValue, rest));

Now, I understand ES6 function declaration, calling or invoking, but I'm confused by the fact foldWith gets called with more "inner parameters". In the next code:

foldWith((number, rest) => number * number + rest, 0, [1, 2, 3, 4, 5])

I can't seem to figure out how foldWith((number, rest) number and rest here gets decided what part of parameter they are? I mean foldWith function created with 4 parameters and called with 4 but how number gets linked to first and rest to rest?

Upvotes: 2

Views: 95

Answers (1)

T.J. Crowder
T.J. Crowder

Reputation: 1074999

foldWith is a recursive function (it calls itself sometimes) using parameter destructuring. The destructuring is here:

const foldWith = (fn, terminalValue, [first, ...rest]) =>
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^

That takes the third argument it receives, which is an array in the example call, and:

  • Extracts the first element from the array into the parameter first, and
  • Extracts all the other elements from the array into a new array it puts in the parameter rest.

This is called iterable destructuring (sometimes people call it "array destructuring" but it's not limited to arrays).

For the first call, the array is [1, 2, 3, 4, 5], so first gets 1 and rest gets a new array with [2, 3, 4, 5].

So that's that part. Next, the recursion. Let's look at the function again (with some added formatting):

const foldWith = (fn, terminalValue, [first, ...rest]) =>
    first === undefined
        ? terminalValue
        : fn(first, foldWith(fn, terminalValue, rest));
//                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^−−−−−− recursive call

foldWith calls itself. Initially, it's called with [1, 2, 3, 4, 5] as the third argument, so:

  • In the top call, first is 1 (not undefined) and rest is [2, 3, 4, 5]. Since first is not undefined, foldWith calls itself with [2, 3, 4, 5] as part of fn(first, foldWith(fn, terminalValue, rest)):
    • During that call, first is 2 and rest is [3, 4, 5], so it calls itself again as part of fn(first, foldWith(fn, terminalValue, rest)):
      • During that call, first is 3 and rest is [4, 5], so it calls itself again as part of fn(first, foldWith(fn, terminalValue, rest)):
        • During that call, first is 4 and rest is [5], so it calls itself again as part of fn(first, foldWith(fn, terminalValue, rest)):
          • During that call, first is 5 and rest is [], so it calls itself again as part of fn(first, foldWith(fn, terminalValue, rest)):
            • During that call, first is undefined (because if you try to get the first element from an empty array, you get undefined) and rest is []. So foldWith doesn't call itself. Instead, it returns the value terminalValue.
          • The rest of fn(first, foldWith(fn, terminalValue, rest)) can now be done, with terminalValue where the foldWith call was. So it's effectively fn(first, terminalValue); it returns that result.
        • That result is now used in fn(first, <<previous result>>) and that result is returned
      • Same again, that result is now used in fn(first, <<previous result>>) and that result is returned
    • Same again
  • The call returns the final result from fn.

Let's try to visualize that:

let indent = 0;
function log(...msgs) {
    console.log(`${"  ".repeat(indent)}${msgs.join(" ")}`);
}
const foldWith = (fn, terminalValue, [first, ...rest]) => {
    log(`foldWith(fn, ${terminalValue}, ${first === undefined ? "[]" : JSON.stringify([first, ...rest])})`);
    // log(first === undefined ? `* Returning ${terminalValue}` : `Recursing`);
    ++indent;
    const result = first === undefined
        ? terminalValue
        : fn(first, foldWith(fn, terminalValue, rest));
    --indent;
    log(`foldWith returning ${result}`);
    return result;
};

const fn = (number, rest) => {
    const result = number * number + rest;
    log(`fn(${number}, ${rest}) is ${result}`);
    return result;
}
const final = foldWith(fn, 0, [1, 2, 3, 4, 5])
log(`Final result: ${final}`);
.as-console-wrapper {
    max-height: 100% !important;
}

Upvotes: 3

Related Questions