Reputation: 21
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
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:
first
, andrest
.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:
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))
:
first
is 2
and rest
is [3, 4, 5]
, so it calls itself again as part of fn(first, foldWith(fn, terminalValue, rest))
:
first
is 3
and rest
is [4, 5]
, so it calls itself again as part of fn(first, foldWith(fn, terminalValue, rest))
:
first
is 4
and rest
is [5]
, so it calls itself again as part of fn(first, foldWith(fn, terminalValue, rest))
:
first
is 5
and rest
is []
, so it calls itself again as part of fn(first, foldWith(fn, terminalValue, rest))
:
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
.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.fn(first, <<previous result>>)
and that result is returnedfn(first, <<previous result>>)
and that result is returnedfn
.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