It may help to first look at this simpler variant of the algorithm, where there is no such magic as a function that returns a function:
function binary_op(func, my_little_array) { // Two arguments
let wunth = my_little_array.pop();
let zeroth = my_little_array.pop();
my_little_array.push(func(zeroth, wunth));
return my_little_array;
};
let add = function(zeroth, wunth) { // No call to make_binary_op
return zeroth + wunth;
};
let mul = function(zeroth, wunth) {
return zeroth * wunth;
};
let my_little_stack = [];
my_little_stack.push(3);
my_little_stack.push(5);
my_little_stack.push(7);
binary_op(mul, my_little_stack); // we need to pass two arguments
binary_op(add, my_little_stack);
let answer = my_little_stack.pop();
console.log(answer);
I think you will be able to understand how this works: the new binary_op
function takes both a callback function (that performs a operation on two arguments) and a stack.
It then pops two values from the stack, passes them to the callback function, gets the result from it, and pushes that result (possibly a sum or a product) on the stack.
The stack has thus reduced in size by 1: two operands were replaced by the func
's result on them.
Assuming you follow how this works, now see how we could make it that instead of this:
binary_op(mul, my_little_stack);
... we could write this:
mulop(my_litte_stack);
mulop
would need to be a function that can combine what mul
does and what the above binary_op
does, in one go.
That is where the function make_binary_op
comes in: it creates (and returns) a function that is specifically tailored
to the operator you have in mind (and which you pass as argument). If you pass mul
to make_binary_op
, it will produce
a function that implements the above binary_op
function, specifically tailored to mul
: when that created function is invoked
it will call mul.
But note how that dynamically created function only needs one argument (the stack), because the other argument (func
)
is already known to it. It is present in the "closure" in which that function was returned.
Addendum
One critique on this pattern could be the following observation: while items are added to my_little_array
using dot-notation (my_little_array.push
), the operations mul/add have to be expressed like function calls where my_little_array
is passed as argument. Why could it not be made to work with the dot-notation also, so that you could write my_little_array.mul()
?
In the current state of the JS language you could do that with a class (constructor) that extends Array
, so that besides push
and pop
it can also support add
and mul
:
class PolishCalc extends Array {
static registerBinaryOp(func) {
this.prototype[func.name] = function () {
let wunth = this.pop();
let zeroth = this.pop();
this.push(func(zeroth, wunth));
return this;
}
}
}
// Extend the prototype with add and mul methods:
PolishCalc.registerBinaryOp(function add(zeroth, wunth) {
return zeroth + wunth;
});
PolishCalc.registerBinaryOp(function mul(zeroth, wunth) {
return zeroth * wunth;
});
let polishCalc = new PolishCalc;
polishCalc.push(3, 5, 7);
let answer = polishCalc.mul().add().pop(); // the method calls can be chained...
console.log(answer);