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, these kinds of non-local transfers of control often conflict with writing robust programs that need to ensure that clean-up code (like closing streams) gets run. Many languages provide ways of ensuring that clean-up code gets run in a wide variety of circumstances; Java has its finally
blocks, C++ has destructors, Common Lisp has UNWIND-PROTECT
, and so on.
In Mathematica, I don't know how to accomplish the same thing. I have a partial solution that looks like this:
Attributes[CleanUp] = {HoldAll};
CleanUp[body_, form_] :=
Module[{return, aborted = False},
Catch[
CheckAbort[
return = body,
aborted = True];
form;
If[aborted,
Abort[],
return],
_, (form; Throw[##]) &]];
This certainly isn't going to win any beauty contests, but it also only handles Abort
and Throw
. In particular, it fails in the presence of Return
; I figure if you're using Goto
to do this kind of non-local control in Mathematica you deserve what you get.
I don't see a good way around this. There's no CheckReturn
for instance, and when you get right down to it, Return
has pretty murky semantics. Is there a trick I'm missing?
EDIT: The problem with Return
, and the vagueness in its definition, has to do with its interaction with conditionals (which somehow aren't "control structures" in Mathematica). An example, using my CleanUp
form:
CleanUp[
If[2 == 2,
If[3 == 3,
Return["foo"]]];
Print["bar"],
Print["cleanup"]]
This will return "foo" without printing "cleanup". Likewise,
CleanUp[
baz /.
{bar :> Return["wongle"],
baz :> Return["bongle"]},
Print["cleanup"]]
will return "bongle" without printing cleanup. I don't see a way around this without tedious, error-prone and maybe impossible code-walking or somehow locally redefining Return
using Block
, which is heinously hacky and doesn't actually seem to work (though experimenting with it is a great way to totally wedge a kernel!)
Pillsy's later version of CleanUp is a good one. At the risk of being pedantic, I must point out a troublesome use case:
The problem is due to the fact that one cannot explicitly specify a tag pattern for Catch that will match an untagged Throw.
The following version of CleanUp addresses that problem:
Alas, this code is even less likely to be competitive in a beauty contest. Furthermore, it wouldn't surprise me if someone jumped in with yet another non-local control flow that that this code will not handle. Even in the unlikely event that it handles all possible cases now, problematic cases could be introduced in Mathematica X (where X > 7.01).
I fear that there cannot be a definitive answer to this problem until Wolfram introduces a new control structure expressly for this purpose. UnwindProtect would be a fine name for such a facility.
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 aReturn
is when you directly pass a single orCompoundExpression
(e.g.(one;two;three)
directly as input to it.Return exits the function
f
:Return
exitsx
:Return
exits theDo
loop:Returns from the body of
CleanUp
at the point wherebody
is evaluated (sinceCleanUp
isHoldAll
):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
: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
insideIf
is unnecessary, and even an abuse ofReturn
, given thatIf
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 toIf[3==3, "foo"]
If you have a more complicated
If
statement, you're better off usingThrow
andCatch
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:... by abusing
Do
to force aReturn
not contained within a control structure inexpr
to return out of theDo
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 uncontainedReturn
, butret2
would have the value of any other evaluation ofexpr
. There's probably a cleaner way to handle that, but I can't see it right now.HTH!
Michael Pilat provided the key trick for "catching" returns, but I ended up using it in a slightly different way, using the fact that
Return
forces the return value of a named function as well as control structures likeDo
. I made the expression that is being cleaned up after into the down-value of a local symbol, like so: