tags:

views:

139

answers:

4

In Javascript, I sometimes want to return a value from a scope that isn't the current function. It might be a block of code within the function, or it might be an enclosing function as in the following example, which uses a local function to recursively search for something. As soon as it finds a solution, the search is done and the outer function should just exit. Unfortunately, I can't think of a simpler way to do this than by hacking try/catch for the purpose:

function solve(searchSpace) {
    var search = function (stuff) {
        var solution = isItSolved(stuff);
        if (solution) {
            throw solution;
        } else {
            search(narrowThisWay(stuff));
            search(narrowThatWay(stuff));
        };
    };
    try {
        return search(searchSpace);
    } catch (solution) {
        return solution;
    };
};

I realize one could assign the solution to a local variable and then check it before making another recursive call, but my question is specifically about transfer of control. Is there a better way than the above? Perhaps involving label/break?

Edit: since the answers to date are variations of "ew that's bad you're not supposed to do that", let me add some necessary context. I'm hacking on an open-source compiler that targets Javascript. No one is going to write this code by hand, so please don't tell me "this is a bad programming technique". What I want is a better code generation technique. The question is whether anyone has any clever hack for exploiting Javascript to get more flexible control transfer.

The reason assigning the result to a local variable and checking it is ruled out is because that requires understanding the code in a way that is hard for a compiler to do.

+1  A: 

It seems I stand corrected on the intent of the question. If statements are are a useful and readable way to structure code and make it flow however you want to. There's a reason goto was taken out of so many languages, because you don't need it. And it seems like, based on your example code, you're using a try-catch block as a form of goto. If you don't want certain things to run then use if statements or equivalents:

function solve(searchSpace) {
    function search = function (stuff) {
        //|| will only return the first value if that value is truthy, subsequent values will be ignored
        return isItSolved(stuff) || (search(narrowThisWay(stuff)) || search(narrowThatWay(stuff)));
    };
    return search(searchSpace);
};

I know of no way to break out of function calls like you want. You can break out of loops using labels, but it doesn't seem that's much help to your situation. Other than that, I don't think JavaScript has any such ability beyond your use of exceptions

Bob
Downvote? I'd like to know why (can't learn without seeing the error of your ways)
Bob
"I realize one could assign the solution to a local variable and then check it before making another recursive call, but *my question is specifically about transfer of control*." Emphasis added.
gruseom
Further to @gruseom's comment, your code specifically breaks the "transfer of control" aspect the OP is going for, whereby the second call to `search` does not need to execute if the `solution` is found in the first.
Roatin Marth
@Roatin: Yes. Thank you for actually reading and understanding my question.
gruseom
@Bob: You're right that what I really want is goto. You're wrong that I "don't need it". I assure you I do need it. One thing you learn about programming if you stick at it long enough with an open mind is that none of these rules (like goto-considered-harmful) have 100% applicability.
gruseom
@gruseom If I hadn't been programming long enough with an open mind I never would've stopped using goto. And I realize sometimes rules can be broken. But here, I can't see it. I could understand using goto in nested loops(and indeed, JavaScript offers such a way to break loops), but your example can be restructured to flow the way you want. Perhaps give another example?
Bob
@Bob No, it can't, because I'm working on a compiler and a compiler can't apply that restructuring to arbitrary code. That's the whole point here. Perhaps you missed my edit to the OP.
gruseom
@gruseom See my edit. Sorry I can't be more help
Bob
@Bob the reason you probably got downvoted was because you said goto statements are not needed. In certain contexts (compiler implementation, code generation, and very extreme cases of error handling) a goto can actually lead to a cleaner, more readable implementation than with their typical control structures counterparts.
luis.espinal
A: 

It looks like you're doing a fairly straightforward recursive search in your example. Why not just use "return"?

function solve(searchSpace) {
    var search = function (stuff) {
        var solution = isItSolved(stuff);
        if (solution) {
            return solution;
        } else {
            solution = search(narrowThisWay(stuff));
            if (solution) {
              return solution;
            }
            return search(narrowThatWay(stuff));
        };
    };
    return search(searchSpace);
};

I suppose it could be that there are other constraints you haven't mentioned, but it's in general possible to turn any control flow into a set of nested (or recursive) functions, with appropriate return values.

Mark Bessey
The trouble is that that second "if (solution)" adds complexity to the inner function. The lack of a construct for saying "I'm done here, airlift me out immediately" forces you to think about how to encode it indirectly using other constructs. The try/catch may be ugly but it expresses the desired behavior in a way that (a) is explicit, and (b) scales nicely as the code becomes more complicated. Ultimately, all that I'm asking for is a more general "return" - something that can get you out of more than just the immediate function scope without having to painstakingly hand-code your path.
gruseom
@Mark One more thing... I'm asking you guys to take a bit of a leap of faith by understanding that it's not really this toy SOLVE function that I care about; obviously it's possible to fix the control transfer problem *in this case* without doing anything particularly complicated. That isn't true, however, of the general case. Sometimes it's a pain in the neck (e.g. if you have several levels of nesting). I've distilled my question about control transfer into a simple enough example to post it here, but it remains a question about control transfer, not straightforward recursive search.
gruseom
I don't think what you are looking for is actually available in JavaScript. And the only reason I'd think to avoid the try/catch option is because of the expense of throwing and catching exceptions (assuming catching exceptions is relatively expensive in javascript as it is the case on other programming languages.)
luis.espinal
To add to my prev. comment, it seems you are looking for a setjmp/longjmp or goto equivalent (which actually provides a clean and elegant way for what you are looking for.) That's my main gripe with languages that get rid or restrict gotos - they make it near impossible to efficiently implement a solution for problems that actually needs them.
luis.espinal
@luis Agreed on all counts. I'm still hoping some clever person will figure out a way to hack the language spec to do it.
gruseom
+1  A: 
function solve(stuff) {
    return isItSolved(stuff) || solve(narrowThisWay(stuff)) ||     solve(narrowThatWay(stuff));
}

Bob's way is good... exept that he uses twice the function statement (and that he uses ; after a function delaration without an assignment)... and that as we can do it that way, function solve actually is function search.

PS : This code will epically fail if the isItSolved, narrowThisWay or narrowThatWay functions can return a value evaluated to false as a positive result. In this cas, you would have to use ? : statement in order to check if all responses are !== undefined.

PS2: And of ourse, if these function can send an error, you have to catch it...

xavierm02
A: 

The cleanest way would be to use a continuation, but you don't have that efficiently in JS (a few JS engines support continuations, but for the rest there's only CPS, which cries out for tail calls). In C, you could use setjmp/longjmp. In Common Lisp, you could use conditions (which include the functionality of exceptions plus much more). In JS, exceptions are the only non-local control flow option you have available.

You can programmatically transform a program into another that uses CPS.

function solve(searchSpace, isItSolved, isBase, narrowThisWay, narrowThatWay) {
    function search(stuff, k) {
        solution = isItSolved(stuff);
        if (solution) {
            return solution;
        } else if (isBase(stuff)) {
            return k();
        } else {
            return search(narrowThisWay(stuff), function() {
                    return search(narrowThatWay(stuff), k);
                });
        };
    };
    return search(searchSpace, function(val) {return val});
};


var arr=[1, 2,9,72,0,34,5,33,24,62,89,90,30,54,590,23,59,62,73];

solve(arr, function(a) {return (a.length==1 && a[0] == 5) ? a[0] : false;},
      function (a) {return a.length < 2; },
      function (a) {return a.slice(0, a.length / 2);}, 
      function (a) {return a.slice(a.length / 2);}
    );
outis
It's interesting you mention Common Lisp, because it's a CL construct that underlies my question. Not the condition system, though: I just want BLOCK and RETURN-FROM. It's JS's inability to jump out of a function from within a local function when it hits some termination condition that prompts my question. (What I really want is to add BLOCK and RETURN-FROM to Parenscript.) Based on the answers here, though, I'm guessing there's no good way to do it. If JS's BREAK didn't suck (that is, if it were more general) you could simply put a label around the desired scope and break out of it.
gruseom