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;