I have answered almost exactly this question (somewhat more general one, if we interpret this one as being concerned only with Throw
and Catch
) here. Since you asked a more narrow one, I feel it may be appropriate to borrow a part of my answer, to have it here.
The method
This method is IMO almost never appropriate for the top-level functions that are exposed to the user. Mathematica exceptions are not checked (in the sense of say checked exceptions in Java), and mma is not strongly typed, so there is no good language-supported way to tell the user that in some event exception may be thrown. However, it may be very useful for inner functions in a package. Here is a toy example:
ClearAll[ff, gg, hh, failTag];
hh::fail = "The function failed. The failure occured in function `1` ";
ff[x_Integer] := x^2 + 1;
ff[args___] := Throw[$Failed, failTag[ff]];
gg[x_?EvenQ] := x/2;
gg[args___] := Throw[$Failed, failTag[gg]];
hh[args__] :=
Module[{result},
Catch[result =
gg[ff[args]], _failTag, (Message[hh::fail, Style[First@#2, Red]];
#1) &]];
and some example of use:
In[219]:= hh[1]
Out[219]= 1
In[220]:= hh[2]
During evaluation of In[220]:= hh::fail: The function failed.
The failure occured in function gg
Out[220]= $Failed
In[221]:= hh[1,3]
During evaluation of In[221]:= hh::fail: The function failed.
The failure occured in function ff
Out[221]= $Failed
It is important to never use a single-argument Throw
- always use exception tags. I would go even further and say that in my opinion, the possibility to use Throw
without a tag is a defect of the language.
I found this technique very useful, because when used consistently, it allows to locate the source of error very quickly. This is especially useful when using the code after a few months, when you no longer remember all details.
Meta-programming and automation
You may have noticed that lots of error-checking code is repetitive (boilerplate code). A natural thing to do seems to try automating the process of making error-checking definitions. I will give one example to illustrate the power of mma meta-programming by automating the error-checking for a toy example with internal exceptions discussed above.
Here are the functions that will automate the process:
General::interr =
"The function `1` failed due to an internal error. The failure occured in function `2`";
ClearAll[setConsistencyChecks];
Attributes[setConsistencyChecks] = {Listable};
setConsistencyChecks[function_Symbol, failTag_] :=
function[___] := Throw[$Failed, failTag[function]];
ClearAll[catchInternalError];
Attributes[catchInternalError] = {HoldAll};
catchInternalError[code_, f_, failTag_] :=
Catch[code, _failTag,
Function[{value, tag},
Message[General::interr , Style[f, Red], Style[First@tag, Red]];
value]];
This is how our previous example would be re-written:
ClearAll[ff, gg, hh];
Module[{failTag},
ff[x_Integer] := x^2 + 1;
gg[x_?EvenQ] := x/2;
hh[args__] := catchInternalError[gg[ff[args]], hh, failTag];
setConsistencyChecks[{ff, gg}, failTag]
];
You can see that it is now much more compact, and we can focus on the logic, rather than be distracted by the error-checking or other book-keeping details. The added advantage is that we could use the Module
- generated symbol as a tag, thus encapsulating it (not exposing to the top level). Here are the test cases:
In[34]:= hh[1]
Out[34]= 1
In[35]:= hh[2]
During evaluation of In[35]:= General::interr: The function hh failed
due to an internal error. The failure occured in function gg
Out[35]= $Failed
In[36]:= hh[1,3]
During evaluation of In[36]:= General::interr: The function hh failed
due to an internal error. The failure occured in function ff
Out[36]= $Failed
Many error-checking and error-reporting tasks may be automated in a similar fashion. For a more complete discussion of the error-checking, see the original answer I linked to.
Applicability
Now, why would you choose exceptions over, say, returning $Failed
, or perhaps, some custom return codes which can be analyzed? One good reason to use exceptions is when you have long chain of function calls. Without exceptions, we would need to propagate $Failed
or some other error codes through the entire chain of functions until we reach one which is in a position to make decision and / or execute some recovery code. This may add a lot of needless complexity to the code.
Another good reason is that since Mathematica is a functional programming language and emphasizes immutable code, functions normally don't have much state. Therefore, abrupt jump within an execution stack, which is what exception is, does not usually lead to system getting into an invalid state. In other world, most functions we write in Mathematica don't require any clean-up code to be executed when exception is thrown.
Yet another feature of this error-checking method, which I consider good, is that it takes a zero-tolerance approach to errors. If you use it consistently, any error inside the code will lead to a program failure, during the development stage. This makes code self-testing to a large degree. A similar effect can be achieved with assertions. If you are disciplined enough, it is best not to use exceptions in places where assertions can and should be used. However, the two methods can complement each other, because you can only use assertions on rather specific conditions, while exceptions can be used as catch-all ones, and can also catch cases you missed when designing the assertions.
In some cases, returning $Failed
is still more appropriate. In particular, this is so when there is a well-defined procedure of what to do on failure, and the chain of function calls from the place where error happens to the actual error-handling function is not long.