Here's a somewhat streamlined version of @RobG's great answer:
function add(n) {
function calc(x) { return n+=x, calc; }
calc.valueOf = function() { return n; };
return calc;
}
The minor difference is that here calc
just updates n
and then returns itself, rather than returning itself via another call to add
, which puts another frame on the stack.
Making self-replication explicit
calc
is thus a pure self-replicating function, returning itself. We can encapsulate the notion of "self replication" with the function
function self_replicate(fn) {
return function x() {
fn.apply(this, arguments);
return x;
};
}
Then add
could be written in a possibly more self-documenting way as
function add(n) {
function update(x) { n += x; }
var calc = self_replicate(update);
calc.valueOf = function() { return n; };
return calc;
}
Parallel to Array#reduce
Note that there is a certain parallelity between this approach to repeatedly calling a function and Array#reduce
. Both are reducing a list of things to a single value. In the case of Array#reduce
the list is an array; in our case the list is parameters on repeated calls. Array#reduce
defines a standard signature for reducer functions, namely
function(prev, cur)
where prev
is the "accumulator" (value so far), cur
is the new value being fed in, and the return value becomes the new value the accumulator. It seems useful to rewrite our implementation to make use of a function with that kind of signature:
function add(n) {
function reducer(prev, cur) { return prev + cur; }
function update(x) { n = reducer(n, x); }
var calc = self_replicate(update);
calc.valueOf = function() { return n; };
return calc;
}
Now we can create a more general way to create self-replication-based reducers based on a reducer function:
function make_repeatedly_callable_function(reducer) {
return function(n) {
function update(x) { n = reducer(n, x); }
var calc = self_replicate(update);
calc.valueOf = function() { return n; };
return calc;
};
}
Now we can create add
as
var add = make_repeatedly_callable_function(function(prev, cur) { return prev + cur; });
add(1)(2);
Actually, Array#reduce
calls the reducer function with third and fourth arguments, namely the index into the array and the array itself. The latter has no meaning here, but it's conceivable we might want something like the third argument to know what "iteration" we're on, which is easy enough to do by just keeping track using a variable i
:
function reduce_by_calling_repeatedly(reducer) {
var i = 0;
return function(n) {
function update(x) { n = reducer( n, x, i++); }
var calc = self_replicate(update);
calc.valueOf = function() { return n; };
return calc;
};
}
Alternative approach: keeping track of values
There are certain advantages to keeping track of the intermediate parameters the function is being called with (using an array), and then doing the reduce at the end instead of as we go along. For instance, then we could do Array#reduceRight
type things:
function reduce_right_by_calling_repeatedly(reducer, initialValue) {
var array_proto = Array.prototype,
push = array_proto.push,
reduceRight = array_proto.reduceRight;
return function(n) {
var stack=[],
calc = self_replicate(push.bind(stack));
calc.valueOf = reduceRight.bind(stack, reducer, initialValue);
return calc(n);
};
}
Non-primitive objects
Let's try using this approach to build ("extend") objects:
function extend_reducer(prev, cur) {
for (i in cur) {
prev[i] = cur[i];
}
return prev;
}
var extend = reduce_by_calling_repeatedly(extend_reducer);
extend({a: 1})({b: 2})
Unfortunately, this won't work because Object#toValue
is invoked only when JS needs a primitive object. So in this case we need to call toValue
explicitly:
extend({a: 1})({b: 2}).toValue()