Preventing avalanche of runtime errors in Mathematica

As others have pointed out, there are three ways to deal with errors in a consistent manner:

  1. correctly typing parameters and setting up conditions under which your functions will run,
  2. dealing correctly and consistently with errors generated, and
  3. simplifying your methodology to apply these steps.

As Samsdram pointed out, correctly typing your functions will help a great deal. Don’t forget about the : form of Pattern as it is sometimes easier to express some patterns in this form, e.g. x:{{_, _} ..}. Obviously, when that isn’t sufficient PatternTests (?) and Conditions (/;) are the way to go. Samdram covers that pretty well, but I’d like to add that you can create your own pattern test via pure functions, e.g. f[x_?(Head[#]===List&)] is equivalent to f[x_List]. Note, the parentheses are necessary when using the ampersand form of pure functions.

The simplest way to deal with errors generated is obviously Off, or more locally Quiet. For the most part, we can all agree that it is a bad idea to completely shut off the messages we don’t want, but Quiet can be extremely useful when you know you are doing something that will generate complaints, but is otherwise correct.

Throw and Catch have their place, but I feel they should only be used internally, and your code should communicate errors via the Message facilities. Messages can be created in the same manner as setting up a usage message. I believe the key to a coherent error strategy can be built using the functions Check, CheckAbort, AbortProtect.

Example

An example from my code is OpenAndRead which protects against leaving open streams when aborting a read operation, as follows:

OpenAndRead[file_String, fcn_]:=
Module[{strm, res},
  strm = OpenRead[file];
  res = CheckAbort[ fcn[strm], $Aborted ];
  Close[strm];
  If[res === $Aborted, Abort[], res] (* Edited to allow Abort to propagate *)
]

which, Until recently, has the usage

fcn[ file_String, <otherparams> ] := OpenAndRead[file, fcn[#, <otherparams>]&]
fcn[ file_InputStream, <otherparams> ] := <fcn body>

However, this is annoying to do every time.

This is where belisarius solution comes into play, by creating a method that you can use consistently. Unfortunately, his solution has a fatal flaw: you lose support of the syntax highlighting facilities. So, here’s an alternative that I came up with for hooking into OpenAndRead from above

MakeCheckedReader /: 
    SetDelayed[MakeCheckedReader[fcn_Symbol, symbols___], a_] :=
    Quiet[(fcn[file_String, symbols] := OpenAndRead[file, fcn[#, symbols] &];
           fcn[file_Symbol, symbols] := a), {RuleDelayed::"rhs"}]

which has usage

MakeCheckedReader[ myReader, a_, b_ ] := {file$, a, b} (*as an example*)

Now, checking the definition of myReader gives two definitions, like we want. In the function body, though, file must be referred to as file$. (I have not yet figured out how to name the file var as I’d wish.)

Edit: MakeCheckedReader works by not actually doing anything itself. Instead, the TagSet (/:) specification tells Mathematica that when MakeCheckedReader is found on the LHS of a SetDelayed then replace it with the desired function definitions. Also, note the use of Quiet; otherwise, it would complain about the patterns a_ and b_ appearing on the right side of the equation.

Edit 2: Leonid pointed out how to be able to use file not file$ when defining a checked reader. The updated solution is as follows:

MakeCheckedReader /: 
    SetDelayed[MakeCheckedReader[fcn_Symbol, symbols___], a_] :=
    Quiet[(fcn[file_String, symbols] := OpenAndRead[file, fcn[#, symbols] &];
           SetDelayed @@ Hold[fcn[file_Symbol, symbols], a]), 
           {RuleDelayed::"rhs"}]

The reasoning for the change is explained in this answer of his. Defining myReader, as above, and checking its definition, we get

myReader[file$_String,a_,b_]:=OpenAndRead[file$,myReader[#1,a_,b_]&]
myReader[file_Symbol,a_,b_]:={file,a,b}

Leave a Comment