Performing a non-local return in R
In most languages return
is a statement, but in R it is a function (in fact R does not really have statements, it only has expressions). This function-like behavior of return is useful for figuring out the order in which operations are performed, e.g., the value returned by return(1)+return(2)
tells us that binary operators are evaluated left to right.
R also supports lazy evaluation, operands are only evaluated when their value is required. The question of when a value might be required is a very complicated rabbit hole. In R’s case arguments to function calls are lazy and in the following code:
ret_a_b=function(a, b) { if (runif(1, -1, 1) < 0) a else b } helpless=function() { ret_a_b(return(3), return(4)) return(99) } |
a call to helpless
results in either 3 or 4 being returned.
This ability to perform non-local returns is just what is needed to implement exception handling recovery, i.e., jumping out of some nested function to a call potentially much higher up in the call tree, without passing back up through the intervening function called, when something goes wrong.
Having to pass the return-blob to every function called would be a pain, using a global variable would make life much simpler and less error prone. Simply assigning to a global variable will not work because the value being assigned is evaluated (it does not have to be, but R chooses to not to be lazy here). My first attempt to get what I needed into a global variable involved delayedAssign, but that did not work out. The second attempt made use of the environment created by a nested function definition, as follows:
# Create an environment containing a return that can be evaluated later. set_up=function(the_ret) { ret_holder=function() { the_ret } return(ret_holder) } # For simplicity this is not nested in some complicated way do_stuff=function() { # if (something_gone_wrong) get_out_of_jail() return("done") } get_out_of_jail=0 # Out friendly global variable control_func=function(a) { # Set up what will get called get_out_of_jail <<- set_up(return(a)) # do some work do_stuff() return(0) } control_func(11) |
and has the desired effect 🙂
ISTM this is what exceptions and tryCatch are for, is there a reason you don’t use them? You can even make custom exceptions (signals) and catch your own signals while propagating unexpected ones. Hadley Wickham has a good writeup here:
http://adv-r.had.co.nz/Exceptions-Debugging.html#condition-handling
@Kent Johnson
try
/catch
is probably the most sensible construct to use for exception handling. The usage described above allows for dynamic selection of return point and if used will probably result in extremely hard to understand code. I would file this under there-be-dragons or Sunday afternoon doodling.Have you seen callCC ?
@Sapsi
callCC
is another function in base that I was not aware of.A brief search for examples shows rather localized usage, e.g., returning early from an apply-like call (the sort of thing
try
/catch
does not really handle well). The scoping of the function name complicates a more global usage; pointers to how it might be done simply welcome (but then this is whattry
/catch
is for).