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);