As others have pointed out, there are three ways to deal with errors in a consistent manner:
- correctly typing parameters and setting up conditions under which your functions will run,
- dealing correctly and consistently with errors generated, and
- 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 PatternTest
s (?
) and Condition
s (/;
) 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}