The functions returned by n
and plus
, etc., are closures over the paramter value n
, etc., were called with. For instance, with n
:
var n = function(digit) {
return function(op) {
return op ? op(digit) : digit;
}
};
// ...
var one = n(1);
var two = n(2);
So in the case of two
, the function n
returned (assigned to two
) closes over the value of digit
for the call (2
), so it has access to it later.
Code calling two
can do one of two things:
- Call it with no arguments:
two()
- Call it with a function:
two(plus(one()))
If two
is called with no arguments, op
will be undefined
, so this:
return op ? op(digit) : digit;
becomes effectively
return digit;
So that means, two()
returns 2
.
But if you pass a function into two
, it calls that function with 2
as an argument. That's where things like plus
come into it:
function plus(r) { return function(l) { return l + r; }; }
plus
returns a function that closes over r
. That function, when you call it with a value (l
), returns the result of l + r
.
So let's look at that in action: two(plus(one()))
. Evaluation is inside out, so:
one()
is called with no arguments, so it returns 1
; now we effectively have:
two(plus(1))
plus()
is called with 1
and returns function(l) { return l + r; }
which is effectively function(l) { return l + 1; }
(because r
is 1
); so now we effectively have:
two(function(l) { return l + 1; })
two
is called with the function from Step 2 as op
. Since it was given a function, two
calls op(2)
and returns its result.
op
(the function from Step 2) returns 2 + 1
- The result is
3
.
Here's a version that makes it easier to see what's going on:
const n = function(digit) {
console.log(`n: creating "digitFn" for ${digit}`);
const digitFn = function(op) {
if (op) {
console.log(`digitFn: evaling op(${digit})`);
const result = op(digit);
console.log(`digitFn: returning ${result}`);
return result;
}
console.log(`digitFn: no "op", returning ${digit}`);
return digit;
};
return digitFn;
};
const plus = function(r) {
console.log(`plus: creating "opFn" for + ${r}`);
const opFn = function(l) {
console.log(`opFn: evaling ${l} + ${r}`);
const result = l + r;
console.log(`opFn: returning ${result}`);
return result;
};
return opFn;
};
console.log("main: Creating `one` and `two`");
const one = n(1);
const two = n(2);
console.log("main: Doing `two(plus(one()))`");
console.log(two(plus(one())));
.as-console-wrapper {
max-height: 100% !important;
}
This overall thing is called partial application: Taking a function that takes N arguments, "baking in" one or more of those arguments to produce a function that takes < N arguments (because some are already provided).
Side note: Because you'll see this in the wild, I'll just note that you'll probably see these written as concise-form arrow functions, which are written like this: (params) => expression
(if there's only one parameter, the ()
are optional: param => expression
). What makes those concise-form arrow functions is the fact that the first thing after the arrow (=>
) is not a {
(more here). When called, a concise arrow function evaluates the expression and returns the result (implicitly, there's no return
keyword in a concise arrow function). Those can seem complicated and intimidating at first, until you get used to reading them. Here's n
:
// Does exactly what your original `n` does
const n = digit => op => op ? op(digit) : digit;
That breaks down like this:
digit => ____
is n
, which returns the result of the expression ____
op => ____
is the function n
returns
op ? op(digit) : digit
is the expression the function created by n
evaluates
Here's the full list:
const n = digit => op => op ? op(digit) : digit;
const zero = n(0);
const one = n(1);
const two = n(2);
const three = n(3);
const four = n(4);
const five = n(5);
const six = n(6);
const seven = n(7);
const eight = n(8);
const nine = n(9);
// Do exactly the same thing your original functions do
const plus = r => l => l + r;
const minus = r => l => l - r;
const times = r => l => l * r;
const dividedBy = r => l => l / r;