3 typo
source | link

withSetup assumes that any variable initialization that is a compound expression expression is initialized using the first part of the expression, and is cleaned up using the remaining parts. One is free to write {var = (init1; init2); cleanup1; cleanup2} if desired.

withSetup assumes that any variable initialization that is a compound expression expression is initialized using the first part of the expression, and is cleaned up using the remaining parts. One is free to write {var = (init1; init2); cleanup1; cleanup2} if desired.

withSetup assumes that any variable initialization that is a compound expression is initialized using the first part of the expression, and is cleaned up using the remaining parts. One is free to write {var = (init1; init2); cleanup1; cleanup2} if desired.

2 added the update concerning muffled messages
source | link

Update

I subsequently discovered that CheckAll muffles all messages generated by the exit function. Furthermore, any messages from the main expression are also muffled in such circumstances. Here is a revised version of unwindProtect that performs some gymnastics to preserve the main messages and to inform the user when a clean-up expression fails:

ClearAll@unwindProtect
SetAttributes[unwindProtect, HoldAll]
unwindProtect[body_, cleanup_] :=
  CheckAll[body, HoldComplete] /.
    ( CheckAll[cleanup, HoldComplete] /. _[v_, _[c__]] :>
        Check[
          Message[unwindProtect::cleanupFailed
          , HoldForm @ cleanup
          , HoldForm @ {v}
          , HoldForm @ {c}
          ]
        , Null
        ]
    ; { _[_, _[r__]] :> r
      , _[r_, _[]] :> r
      }
    )

unwindProtect::cleanupFailed =
  "Cleanup expression failed: ``, results: ``, controls:  ``";

Update

I subsequently discovered that CheckAll muffles all messages generated by the exit function. Furthermore, any messages from the main expression are also muffled in such circumstances. Here is a revised version of unwindProtect that performs some gymnastics to preserve the main messages and to inform the user when a clean-up expression fails:

ClearAll@unwindProtect
SetAttributes[unwindProtect, HoldAll]
unwindProtect[body_, cleanup_] :=
  CheckAll[body, HoldComplete] /.
    ( CheckAll[cleanup, HoldComplete] /. _[v_, _[c__]] :>
        Check[
          Message[unwindProtect::cleanupFailed
          , HoldForm @ cleanup
          , HoldForm @ {v}
          , HoldForm @ {c}
          ]
        , Null
        ]
    ; { _[_, _[r__]] :> r
      , _[r_, _[]] :> r
      }
    )

unwindProtect::cleanupFailed =
  "Cleanup expression failed: ``, results: ``, controls:  ``";
1
source | link

There is an undocumented function, CheckAll, that can be used for this purpose. It dates back to at least version 7. All the usual caveats about undocumented functions apply -- it might not be supported in future releases, there may be gaps in its functionality, etc. Buyer beware.

The usage information looks like this:

usage screenshot

The usage text is slightly in error as the control arguments are wrapped in Hold rather than HoldComplete.

CheckAll can be used to detect all manner of non-local exits, such as:

CheckAll[Abort[], List]
(* {$Aborted, Hold[Abort[]]} *)

CheckAll[Throw[1], List]
(* {$Aborted, Hold[Throw[1]]} *)

CheckAll[Goto[a], List]
(* {$Aborted, Hold[Goto[a]]} *)

CheckAll[MemoryConstrained[Range@1000, 100], List]
(* {$Aborted, Hold[]} *)

CheckAll[TimeConstrained[Pause[1000], 1], List]
(* {$Aborted, Hold[]} *)

We can use this function to build unwindProtect, a control structure that assures that a clean-up expression is evaluated after any non-local exit of its body:

ClearAll @ unwindProtect
SetAttributes[unwindProtect, HoldAll]
unwindProtect[body_, cleanup_] :=
  CheckAll[body, HoldComplete] /.
    ( cleanup
    ; { _[_, _[r__]] :> r
      , _[r_, _[]] :> r
      }
    )

It starts by evaluating the body, guarded by CheckAll. Then it evaluates the clean-up expression. Finally, it returns either the pending non-local control actions or, if there are none, the return value of the body (which might be a held Sequence).

Here are some examples of its use:

hi[] := Print@"hi"
bye[] := Print@"bye"
fail[] := Print@"FAIL!"

unwindProtect[hi[]; Abort[], bye[]]
(* During evaluation of In[75]:= hi
   During evaluation of In[75]:= bye
   Out[75]= $Aborted *)

Catch @ unwindProtect[hi[]; Throw[1]; fail[], bye[]]
(* During evaluation of In[76]:= hi
   During evaluation of In[76]:= bye
   Out[76]= 1 *)

Module[{n = 0}
, Label[a]
; If[n < 2, unwindProtect[Print["hi ", ++n]; Goto[a], Print["bye ", n]]]
]
(* hi 1
   bye 1
   hi 2
   bye 2 *)

It even handles some tricky cases:

Catch@unwindProtect[hi[];Throw[Unevaluated[Abort[]]], bye[]]
(* During evaluation of In[82]:= hi
   During evaluation of In[82]:= bye
   Out[82]= $Aborted *)

Catch@unwindProtect[hi[];Throw[Unevaluated[Throw[$Failed]]], bye[]]
(* During evaluation of In[83]:= hi
   During evaluation of In[83]:= bye
   Out[83]= $Failed *)

unwindProtect can be used to build a still higher-level control structure (withSetup) that supports the declaration of Module-like variables, with sequential assignment and resource-cleanup expressions:

ClearAll[withSetup]
SetAttributes[withSetup, HoldAll]

withSetup[{}, body_] := body

withSetup[{var_ = val_; cleanup___, rest___}, body_] :=
  Module[{var = val}
  , unwindProtect[withSetup[{rest}, body], CompoundExpression[cleanup]]
  ]

withSetup[{var_ = val_, rest___}, body_] :=
  Module[{var = val}, withSetup[{rest}, body]]

w:withSetup[___] := (Message[withSetup::malformed, Short@HoldForm[w]]; Abort[])

withSetup::malformed = "``";

withSetup starts out resembling Module:

withSetup[{x = 1}, x + 1]
(* 2 *)

It differs from Module in that the variable assignments are performed sequentially:

withSetup[{x = 1, y = x + 1}, {x, y}]
(* {1, 2} *)

But the real value of withSetup is that it can be used to declare clean-up actions for each variable:

open[f_] := (Print["opened ", f]; file[f])
close[f_] := Print["closed ", f]

withSetup[{f = open["f1"]; close[f]}, f]
(* During evaluation of In[152]:= opened f1
   During evaluation of In[152]:= closed file[f1]
   Out[154]= file[f1] *)

... and those clean-up actions are performed even in the face of a non-local return:

Catch @ withSetup[{f = open["f1"]; close[f]}, Throw["early exit"]]
(* During evaluation of In[160]:= opened f1
   During evaluation of In[160]:= closed file[f1]
   Out[160]= early exit *)

Clean-up actions are performed in reverse order from their corresponding initializations:

withSetup[
  { file1 = open["f1"]; close[file1]
  , file2 = open["f2"]; close[file2]
  , files = {file1, file2}
  }
, doStuffWith[file1, file2, files]
]
(* During evaluation of In[155]:= opened f1
   During evaluation of In[155]:= opened f2
   During evaluation of In[155]:= closed file[f2]
   During evaluation of In[155]:= closed file[f1]
   doStuffWith[file[f1],file[f2],{file[f1],file[f2]}] *)

withSetup assumes that any variable initialization that is a compound expression expression is initialized using the first part of the expression, and is cleaned up using the remaining parts. One is free to write {var = (init1; init2); cleanup1; cleanup2} if desired.

Beware that any errors or other non-local exits from the clean-up actions can cause all kinds of strange behaviour. Clean-up actions should be foolproof, with no reasonable chance of failure. Wrap them in CheckAbort, AbortProtect or even unwindProtect if there is any doubt and circumstances demand it. unwindProtect does not do this automatically, although it could be changed to do so according to one's personal preference (I prefer to see the errors rather than muffle them).