28
$\begingroup$

Mathematica provides several mechanisms for checking arguments and trapping errors in a function definition.

Typical methods are :

Definitions with argument patterns, definitions with Condition, definitions with Which or Switch.

All of them combined with Message

I have also seen a technique which uses Throw and Catch.

This is probably not so well-known. Perhaps someone could explain how it works and possible pros and cons/limits?

$\endgroup$

1 Answer 1

27
$\begingroup$

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.

$\endgroup$
6
  • 2
    $\begingroup$ I really dislike the idea of temporarily defining a message instead a function. $\endgroup$ Jan 24, 2012 at 23:56
  • $\begingroup$ @BrettChampion: Why? This is functional programming at its best ... $\endgroup$ Jan 25, 2012 at 0:50
  • $\begingroup$ @Brett I see what you mean, but these messages are for the developers anyway - the user is supposed to report a bug when seeing one of these, not to dig in and debug. A simple alternative would be of course to define some general message that would take a name of the function as another parameter. Perhaps that would be somewhat cleaner. $\endgroup$ Jan 25, 2012 at 7:31
  • $\begingroup$ @Rolf Because then when you decide that you need to support alternative languages you're digging through the code. Plus, I'm not sure how this would interact with Off or attempts to find out information about messages. $\endgroup$ Jan 25, 2012 at 15:16
  • 1
    $\begingroup$ @LeonidShifrin Or you could define General::interr and call Message[ff::interr, args]. $\endgroup$ Jan 25, 2012 at 15:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.