How to efficiently determine the depth of closures

227 views Asked by At

In terms of closure, if only account for functions that return another function as shown in the following examples. How do we programmatically determine the depth of this closure in runtime?

Let's say this function F has the depth of 2:

function F(a){ // 1
    return function G(b){ // 2
        return 'something';
    }
}

This function 'H' has the depth of 3.

function H(a){ // 1
   return function(b){ //2
      return function(c){} //3
   }
}

So far I have made some simple script to determine the depth of closure by iteratively checks the return type of a function until it returns other type than the function. But my script only works in the case where each nested function requires no argument. See the following code:

function F(){
    return function(){
        return function(){
            return 1;
        }
   }
}

function depth(f){
    var n = 1;
    var ret = f();
    while (typeof(ret)=='function'){
        ret = ret(); 
        n++;
    }
    return n; 
}

The simple function depth above correctly determines the depth of F which is 3. However, in some real-world cases such as closures which take function as argument and invoke it, my function depth cannot deal with.

function F2(a,f){
    return function(g){
        return f(a) + g(a); // This will break my depth function
   }
}

*My question : Is there any way to determine how deep my closure is more efficiently? Especially in some cases mentioned above (closures which take function as argument and invoke it). Generic way which can dynamically cope with closures which may or may not take some functions as arguments will be most appreciated.

P.S. Let me narrow down the scopes as follows.

  • Only pure nested functions are taken into account.
  • Each layer of nested function always returns the same type.
  • The depth of the function is constant.
  • Neither one of the nested functions is recursive.
3

There are 3 answers

5
Roland Pihlakas On BEST ANSWER

Here is another approach. This works at runtime by observing how the code behaves.
My function Reflector is the special sauce in this case.
See https://jsfiddle.net/rffv34oz/8/ for running the example.

The function Reflector does four things:
1. It "instruments" a function with a hook (fh) by returning the hook instead of the original function. The effect is that the hook is called before that instrumented function is called.
2. When the instrumented function is called, it forwards the call - it calls the original function. And remains ready for capturing the function result.
3. When the function returns, it inspects the result. If the result is also a function, it stores new maximum depth.
4. Also, when the returned result was a function, the hook also instruments the returned function, so that step (1) is again applied for the new returned function.

//Your function You want to USE and REFLECT on too.
function F(){ 
    //return 3;
    return function(){
        //return 2;
        return function(){
            return 1;
        }
   }
}

//Your function You want to USE and REFLECT on too.
function H(a){ // 1
    return function(b){ //2
        return function(c){ //3
            return a + b + c;   
        }
   }    
}

//this function is the machinery that knows how to USE the functions You want to measure. 
//But this function only USES them, but does no reflection.
function BlackboxF(f) { 

    var r1 = f();
    if (typeof r1 == "function")
    {
        var r2 = r1();
        if (typeof r2 == "function")
        {
            var r3 = r2();  

            return r3;
        }
        else 
        {
            return r2;   
        }
    }
    else 
    {
         return r1;   
    }

}

//this function is the machinery that knows how to USE the functions You want to measure. 
//But this function only USES them, but does no reflection.
function BlackboxH(f) { 

    var r1 = f(1);
    var r2 = r1(2);
    var r3 = r2(4);
    return r3;

}

var maxdepth = 1;

//This is the special agent for reflecting code as it runs
function Reflector(f, depth) { 

    if (!depth)
        depth = 1;

    //1. It "instruments" a function with a hook (`fh`) by returning the hook 
    //instead of the original function. The effect is that the hook is called 
    //before that instrumented function is called.
    var fh = function()     
    {
        //2. When the instrumented function is called, it forwards the call - it calls 
        //the original function. And remains ready for capturing the function result.
        var r = f.apply(this, arguments);    

        //3. When the function returns, it inspects the result. 
        //If the result is also a function, it stores new maximum depth.
        if (typeof r == "function")
        {
            maxdepth = Math.max(maxdepth, depth + 1);

            //4. Also, when the returned result was a function, the hook also 
            //instruments the returned function, so that step (1) is again applied 
            //for the new returned function.
            return Reflector(r, depth + 1);        
        }
        else 
        {
            return r;
        }
    };

    return fh;
}

if (false) //The simple case with no arguments
{
    var F2 = Reflector(F);
    var RF = BlackboxF(F2);

    document.getElementById("result").textContent = "Result: " + RF;
    document.getElementById("depth").textContent = "Depth: " + maxdepth;
}
else //A more complicated case with arguments
{
    var H2 = Reflector(H);
    var RH = BlackboxH(H2);

    document.getElementById("result").textContent = "Result: " + RH;
    document.getElementById("depth").textContent = "Depth: " + maxdepth;
}
2
Roland Pihlakas On

You maybe are able to change all the functions so that they are capable of running without arguments too, for Your reflection purpose?
Since during the depth calculation the function calls should not do any other useful computation or have any side effects anyway. They should just return a function or a non-function value.
Since You say: "Each layer of nested function always returns the same type. The depth of the function is constant." the decision to return a dummy response of corresponding type upon encountering empty argument list should be simple one.

6
Etheryte On

This is more of a comment than an answer, but it's better to format here.

This is not a solvable problem unless you give a set of all required parameters.
Consider calculating the "depth" of the following:

function F(a) {
    if (a) {
        return function G(b) {
            return 0;
        }
    } else {
        return 1;
    }
}