Mathematica Programming » Code Structure

Error Handling

As we noted before, errors in Mathematica are much more subtle than in many languages. There is no compiler, so nothing will warn you about compile-time errors, and there is no real concept of an automatic runtime errori.e. there is nothing in the language enforcing any type of strict semantics. Because of this we need to be particularly careful about validating errors ourselves.

When we catch one of these errors we then have the question of what to do with it. To compound the difficulty, there aren't any official guidelines on the best ways to handle errors. I have my own preferences, and so we'll work through the various cases in order of least-to-most suggested.

Abort

When an error is caught, we could simply do as most programming languages do and simply abort the calculation, in this case via the function Abort .

The problem with this is that it is a potential overreaction as if we are producing code that others will use, this Abort will be passed up into any of their functions, too.

Message

The standard way Mathematica functions handle cases like this is by emitting a Message and then returning some type of failure tag, e.g. something like this:

 Integrate[x, {1, 10}]
Integrate::ilim: Invalid integration variable or limit(s) in {1,10}.
 Integrate[x,{1,10}]

Since this was an unhandled-arguments type of error, the function simply bottoms out and says: "I'm going to stay unevaluated".

For those interested, this type of behaviour most commonly occurs when a Condition is failed in either the pattern test of the function arguments or if a Condition is found at the end of a scoping construct, e.g.:

 doNothing[]:=
  Module[
    {},
    doNothing::no="Nope :)";
    "Something!"/;If["Something"=!=Nothing, Message[doNothing::no], True]
    ]
doNothing[]
doNothing::no: "Nope :)"
 doNothing[]

If Abort is too much, this approach is too little. An error caught deep inside a function shouldn't have to be passed step-by-step up through the rest of the function.

Throw / Catch

The Throw / Catch mechanism provides a much more robust way to handle errors in Mathematica. Catch takes a second argument that can be a pattern to catch the different error tags that Throw can attach to its results. By doing this we can get deeply-nested functions to throw to high-level and be caught only by the handlers we want, for instance we can do something like:

 main[]:=
  NumberForm[Catch[{sub1[], sub2[], sub3[]}, "err1"|"err2"|"err3"], 3]
sub1[]:=
  If[#<.25, Throw[StringForm["`` too large", #], "err1"], #]&@RandomReal[]
sub2[]:=
  If[#<.5, Throw[StringForm["`` two large", #], "err2"], #]&@RandomReal[]
sub3[]:=
  If[#<.75, Throw[StringForm["`` three large", #], "err3"], #]&@RandomReal[]

And when we use this:

 ToString/@Table[main[], 10]
 {"0.137 two large","0.18 too large","0.704 three large","0.18 too large","0.0282 three large","0.463 three large","0.0129 too large","0.634 three large","0.104 too large","0.464 two large"}

We see that the different errors were caught, each with their messages, and we didn't have to catch and pass up on each round.

Standardized Error Handling

We can combine these approaches to make a more robust approach to error handling. We'll use Message to tell the user about the source of the error, Throw and Catch to pass the error to top-level cleanly, and finally we'll use the Failure object to capture the message feed them to Throw .

We can go one step further, though, and allow any exceptions caught by Catch to optionally be passed up

Here's a sample way this could work:

 Protect[$$tagStack];
catchExceptions[expr_, tags:{___}, passUp:True|False:True]:=
  Block[
    {
      $$tagStack=
        If[ListQ@$$tagStack, {Join@@$$tagStack, tags}, {{}, tags}]
      },
    Catch[expr, 
      Alternatives@@tags,
      If[passUp,
        If[MemberQ[First@$$tagStack, #2], 
          Throw[##],
          #
          ]&,
        #&
        ]
      ]
    ];
catchExceptions~SetAttributes~HoldFirst;
raiseException[HoldPattern[msg:MessageName[sym_, tag_, ___]], params___]:=
  (
    Message[msg, params];
    Throw[
      Failure[tag, 
        <|"MessageTemplate":>msg, "MessageParameters"->{params}|>],
      tag
      ]
    );
raiseException~SetAttributes~HoldFirst;

We create a function that sets up a stack of tags being checked for, calls Catch with the set of currently tracked tags, if the error is caught, it conditionally checks whether the tag is in the head of the stack and if so passes it up.

Next we take a function that will Throw to this Catch block and return a nice structure to go with it.

Here's this in action:

 myCoolFunction::err1="Uh oh spaghetti-Os!";
myCoolFunction::err2="Oh deary me!";
myCoolFunction::err3="Why did you do this to me ``?";
myCoolFunction[name_]:=
  catchExceptions[
    winAwards@
      callMeBy@
        yourName@name,
    {"err1", "err2", "err3"}
    ];
winAwards[movie_]:=
  If[!StringQ@movie, 
    raiseException[myCoolFunction::err1], 
    StringForm["`` won an award!", movie]
    ];
callMeBy[name_]:=
  If[RandomReal[]<.5, 
    raiseException[myCoolFunction::err2],
    "Call Me By "<>name//Quiet
    ];
yourName[name_]:=
  If[StringQ@name&&StringFreeQ[name, " "], 
    raiseException[myCoolFunction::err3, name],
    name
    ];

And now we can use it on some examples:

 myCoolFunction/@
  Join[
    StringRiffle@*DeleteDuplicates/@
      Tuples[EntityValue[RandomEntity["GivenName", 2], "Label"], 2],
    {1}
    ]
myCoolFunction::err3: Why did you do this to me "Icelyn"?
myCoolFunction::err2: "Oh deary me!"
myCoolFunction::err3: Why did you do this to me "Shota"?
myCoolFunction::err1: "Uh oh spaghetti-Os!"
2112errorhandling-6809837667784981244

And we can nest this without end and it will always throw all the way up to top-level:

 catchExceptions[
  catchExceptions[myCoolFunction[1], {"err1", "err2", "err3"}], 
  {"err1", "err2", "err3"}
  ]
myCoolFunction::err1: "Uh oh spaghetti-Os!"
2112errorhandling-6070949172998910373

More extensions can be built on top of this, but this gets us a good ways beyond passing failed arguments up through our call stack.

See Also: