For better or worse, Mathematica provides a wealth of constructs that allow you to do non-local transfers of control, including Return, Catch/Throw, Abort and Goto. However,
Great question, but I don't agree that the semantics of Return
are murky; They are documented in the link you provide. In short, Return
exits the innermost construct (namely, a control structure or function definition) in which it is invoked.
The only case in which your CleanUp
function above fails to cleanup from a Return
is when you directly pass a single or CompoundExpression
(e.g. (one;two;three)
directly as input to it.
Return exits the function f
:
In[28]:= f[] := Return["ret"]
In[29]:= CleanUp[f[], Print["cleaned"]]
During evaluation of In[29]:= cleaned
Out[29]= "ret"
Return
exits x
:
In[31]:= x = Return["foo"]
In[32]:= CleanUp[x, Print["cleaned"]]
During evaluation of In[32]:= cleaned
Out[32]= "foo"
Return
exits the Do
loop:
In[33]:= g[] := (x = 0; Do[x++; Return["blah"], {10}]; x)
In[34]:= CleanUp[g[], Print["cleaned"]]
During evaluation of In[34]:= cleaned
Out[34]= 1
Returns from the body of CleanUp
at the point where body
is evaluated (since CleanUp
is HoldAll
):
In[35]:= CleanUp[Return["ret"], Print["cleaned"]];
Out[35]= "ret"
In[36]:= CleanUp[(Print["before"]; Return["ret"]; Print["after"]),
Print["cleaned"]]
During evaluation of In[36]:= before
Out[36]= "ret"
As I noted above, the latter two examples are the only problematic cases I can contrive (although I could be wrong) but they can be handled by adding a definition to CleanUp
:
In[44]:= CleanUp[CompoundExpression[before___, Return[ret_], ___], form_] :=
(before; form; ret)
In[45]:= CleanUp[Return["ret"], Print["cleaned"]]
During evaluation of In[46]:= cleaned
Out[45]= "ret"
In[46]:= CleanUp[(Print["before"]; Return["ret"]; Print["after"]),
Print["cleaned"]]
During evaluation of In[46]:= before
During evaluation of In[46]:= cleaned
Out[46]= "ret"
As you said, not going to win any beauty contests, but hopefully this helps solve your problem!
Response to your update
I would argue that using Return
inside If
is unnecessary, and even an abuse of Return
, given that If
already returns either the second or third argument based on the state of the condition in the first argument. While I realize your example is probably contrived, If[3==3, Return["Foo"]]
is functionally identical to If[3==3, "foo"]
If you have a more complicated If
statement, you're better off using Throw
and Catch
to break out of the evaluation and "return" something to the point you want it to be returned to.
That said, I realize you might not always have control over the code you have to clean up after, so you could always wrap the expression in CleanUp
in a no-op control structure, such as:
ret1 = Do[ret2 = expr, {1}]
... by abusing Do
to force a Return
not contained within a control structure in expr
to return out of the Do
loop. The only tricky part (I think, not having tried this) is having to deal with two different return values above: ret1
will contain the value of an uncontained Return
, but ret2
would have the value of any other evaluation of expr
. There's probably a cleaner way to handle that, but I can't see it right now.
HTH!