Puzzle: JS Function that returns itself until there are no arguments

后端 未结 5 2060
北荒
北荒 2020-12-06 14:29

I\'m trying to solve a puzzle, and am at my wit\'s end trying to figure it out.

I\'m supposed to make a function that works like this:

add(1);                


        
5条回答
  •  醉梦人生
    2020-12-06 14:45

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

提交回复
热议问题