13

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
  • 9,781
  • 1
  • 43
  • 70
  • `CheckAll` seems to offer a fairly bulletproof solution to this problem. See [this answer](http://mathematica.stackexchange.com/a/48493/142) over on Mathematica StackExchange. – WReach May 29 '14 at 03:37

3 Answers3

3

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!

Michael Pilat
  • 6,480
  • 27
  • 30
  • This is a worthy effort, and it works for the simplest case, but unfortunately fails in the presence of conditionals. See the edited question for more details. – Pillsy Aug 02 '10 at 13:31
  • I agree that using `Return` inside a conditional isn't great Mathematica style, but it's an extremely common idiom in most imperative languages, and people often wish to translate algorithms from C, Fortran or pseudocode as directly as possible. – Pillsy Aug 06 '10 at 13:40
3

Pillsy's later version of CleanUp is a good one. At the risk of being pedantic, I must point out a troublesome use case:

Catch[CleanUp[Throw[23], Print["cleanup"]]]

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:

SetAttributes[CleanUp, HoldAll]
CleanUp[expr_, cleanup_] :=
  Module[{exprFn, result, abort = False, rethrow = True, seq},
    exprFn[] := expr;
    result = CheckAbort[
      Catch[
        Catch[result = exprFn[]; rethrow = False; result],
        _,
        seq[##]&
      ],
      abort = True
    ];
    cleanup;
    If[abort, Abort[]];
    If[rethrow, Throw[result /. seq -> Sequence]];
    result
  ]

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.

Community
  • 1
  • 1
WReach
  • 18,098
  • 3
  • 49
  • 93
  • 2
    The "CheckAbort[Catch[Catch[" sequence will show up in my nightmares from now on ... – Dr. belisarius Sep 10 '10 at 04:51
  • +1. Nice for the most part, but what about this: `In[21]:= CleanUp[a = 0; Throw[Unevaluated[Abort[]]], a = 1] Out[21]= $Aborted`. I would prefer the output to be `Hold[Throw[Abort[]]]` with a message on uncaught exception on the top level. Likewise, this `CleanUp[Throw[Unevaluated[Throw[$Failed]]], a = 1]` will also break your code. The only way I see to fix this is to dynamically redefine `Throw` during the code execution via Gayley - Villegas trick and giving `Throw` temporarily a `HoldAllComplete` attribute, but this is not exactly elegant or robust. This is a defect of tagless `Catch` ... – Leonid Shifrin Sep 24 '11 at 14:03
  • ...since for exceptions with tags this can be avoided: `Catch[Throw[Unevaluated[Abort[]], tag], _, HoldComplete]`. I actually think that tagless exceptions are a language defect altogether - for a marginally more convenient form of `Throw` we get more than one door to subtle bugs wide open. – Leonid Shifrin Sep 24 '11 at 14:12
  • @Leonid Indeed. All the more reason why I think that something like `UnwindProtect` needs to be native functionality provided by Mathematica. It probably needs to be built directly into the kernel evaluation process to handle all the fiddly cases. – WReach Sep 24 '11 at 15:44
  • @WReach Agree. I also share your view that, whatever we can come up with on the top level, won't be robust. In particular, because some of the kernel mma code by-passes the main evaluator, so that even if I redefine `Throw` dynamically (which is easy to do), there is no guarantee that there wouldn't be such uses of `Throw` in the internal code. In light of all this, I am not even sure that it is a good idea to include protection in my importing functions. If there is no reliable mechanism, may be it is better to not include any. What do you think? – Leonid Shifrin Sep 24 '11 at 15:52
  • 1
    @Leonid I am inclined to agree. In situations where the protected code is known (e.g. written as a unit), then `CheckAbort`, `AbortProtect` or `WithLocalSettings` are adequate. When trying to write new control structures that invoke caller-supplied code, there seems to be no bulletproof solution. Having said all that, Mathematica plays "fast and loose" a lot: reference-counting garbage collection, missing streaming operations, not-so-temporary temporary symbols, surprising array copies, evaluation leaks, scope leaks... unwinding leaks are just one more to add to the list :) – WReach Sep 24 '11 at 20:32
2

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 like Do. I made the expression that is being cleaned up after into the down-value of a local symbol, like so:

Attributes[CleanUp] = {HoldAll};
CleanUp[expr_, form_] :=
  Module[{body, value, aborted = False},

   body[] := expr;

   Catch[
    CheckAbort[
     value = body[],
     aborted = True];
    form;
    If[aborted,
     Abort[],
     value],
    _, (form; Throw[##]) &]];
Community
  • 1
  • 1
Pillsy
  • 9,781
  • 1
  • 43
  • 70