13

A typical situation I run into when notebook grows beyond a couple of functions -- I evaluate an expression, but instead of correct answer I get Beep followed by dozens of useless warnings followed by "further Output of ... will be suppressed"

One thing I found useful -- use Python-like "assert" inside functions to enforce internal consistency. Any other tips?

Assert[expr_, msg_] := If[Not[expr], Print[msg]; Abort[], None]

edit 11/14 A general cause of a warning avalanche is when a subexpression evaluates to "bad" value. This causes the parent expression to evaluate to a "bad" value and this "badness" propagates all the way to the root. Built-ins evaluated along the way notice the badness and produce warnings. "Bad" could mean an expression with wrong Head, list with wrong number of elements, negative definite matrix instead of positive definite, etc. Generally it's something that doesn't fit in with the semantics of the parent expression.

One way do deal with this is to redefine all your functions to return unevaluated on "bad input." This will take care of most messages produced by built-ins. Built-ins that do structural operations like "Part" will still attempt to evaluate your value and may produce warnings.

Having the debugger set to "break on Messages" prevents an avalanche of errors, although it seems like an overkill to have it turned on all the time

Yaroslav Bulatov
  • 57,332
  • 22
  • 139
  • 197

5 Answers5

10

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}
Community
  • 1
  • 1
rcollyer
  • 10,475
  • 4
  • 48
  • 75
  • Thanks for pointing out the flaw, Although I am not so sure how "fatal" it is since you may check the function syntax "normally" and the convert it to the /circleminus syntax with minimal effort. I am still pounding on this one. I really want to find a non-intrusive way of aborting on errors without having overhead code. – Dr. belisarius Nov 14 '10 at 06:16
  • BTW +1 ... I need to investigate TagSet ... never used it – Dr. belisarius Nov 14 '10 at 06:28
  • Again, answers like these keep me coming back every day :-). Although, I have to say that manually getting around Mathematica's weak typing seems a bit counter intuitive. I have come up against the problem that doing so in large projects (let's say 4 or 5 layers of modules and functions) there is a significant slow down and lots of added complexity from implementing the necessary propagation of error messages back to the main routine. – Timo Nov 14 '10 at 07:14
  • +1 for typing: This is a late habit for me, but it have made everything so much nicer: You don't get cascades from hell, and there is the added bonus that the typechecks often act as very concise documentation of the calling syntax. – Janus Nov 14 '10 at 13:51
  • @Simon: I can't recognize the slowdown problem -- are you maybe typing too hard? If a function takes a positive definite matrix, I will type it to take any matrix: This catches stupid bugs and stops avalanches: If I was somehow sending it non-PD matrices, this would be an algorithm problem that would probably sneak around typechecks anyway. – Janus Nov 14 '10 at 13:54
  • 2
    @belisarius: `TagSet`, `UpSet` (`^=`), and `UpSetDelayed` (`^:=`) generate upvalues for the associated function. Since the built in functions are `Protected`, it would be difficult to create new objects with any sort of mathematical behavior. These functions get around that by associating the transformation with the object itself, not the operations. I learned about them from Quantum Methods with Mathematica (http://www.amazon.com/Quantum-Methods-Mathematica-James-Feagin/dp/0387953655/ref=tmm_pap_title_0?ie=UTF8&qid=1289761092&sr=8-2) – rcollyer Nov 14 '10 at 19:03
  • @rcollyer Tnx for the reference. I find the embedded help system rather obscure on these features. – Dr. belisarius Nov 15 '10 at 00:08
  • @belisarius, the help system prior to v. 6 was very good for finding connections between things. the more recent help system isn't nearly as good. – rcollyer Nov 15 '10 at 00:10
  • @rcollyer Could you recommend some good books on Mathemetica? If I post this as a question it'll be voted down ... no Community Wiki anymore ... – Dr. belisarius Nov 15 '10 at 00:13
  • @belisarius, the only other book I used to use was the "Mathematica Book for Students", which was excerpted from the "Mathematica Book" (http://www.amazon.com/Mathematica-Book-Fourth-Stephen-Wolfram/dp/0521643147/ref=sr_1_1?ie=UTF8&qid=1289837895&sr=8-1) which is just okay, but not great. Other than that, I learned using the help files prior to v.6. I also look through the packages included with Mathematica for some ideas. – rcollyer Nov 15 '10 at 16:21
  • @rcollyer Tnx a lot. Perhaps I should get a museum tour and install v5 :) – Dr. belisarius Nov 15 '10 at 16:49
  • @belisarius, unfortunately. Of course, the only thing to recommend v.5 over v.7 is the help system, but it is sorely missed. My biggest problem with the current system is the removal of anything resembling a real example. All of the examples now seem watered down versions, and rarely have anything beyond the simplest of "how to work this function" type. I figure they were trying to lower the barrier to entry, but butchered one of the truly nice things about the old system. Although, I do prefer the page layout of the new system v. the old. – rcollyer Nov 15 '10 at 17:18
  • @belisarius, ran out of room. One of the examples from v. 5, was a Plot3D where the surface was color coded by its inclusion within a range defined by another function. The example was non-trivial and required some time to parse apart, but there was a lot of good programming in it. – rcollyer Nov 15 '10 at 17:21
  • @rcollyer Thank you so much for your time answering my comments. As I learned Mathematica just reading the help system, I am probably missing a lot. For example, the connections between Combinatorica, ComputationalGeometry and embedded graph functions drive me crazy sometimes ... – Dr. belisarius Nov 15 '10 at 17:59
  • @belisarius, you're welcome. Since, your profile says your a former physicist, the QM book should do you good. It's based on v.2, though. Just noticed that there is a Schaum's Outline (http://www.amazon.com/Schaums-Outline-Mathematica-2ed/dp/0071608281/ref=tmm_pap_title_0), also. – rcollyer Nov 15 '10 at 18:02
  • @rcollyer I usually HATE Schaum's approaches. :D. Will try to get the QM book instead! – Dr. belisarius Nov 15 '10 at 18:16
  • @belisarius, due to the length of this comment thread, I think it would be a worthwhile question. But, it may be best posted on http://programmers.stackexchange.com – rcollyer Nov 15 '10 at 18:46
  • 1
    @rcollyer Question posted at http://programmers.stackexchange.com/questions/19146/mathematica-wolfram-research-books-recommendations – Dr. belisarius Nov 15 '10 at 19:28
  • @rcollyer Nice solution! The only thing that I don't like in `OpenAndRead` is that it swallows aborts, which means that you can not nest it deeper than top-level and expect Abort-s to propagate. I mean, execute `f[OpenAndRead[someFile, Abort[] &]]` with some existing file and undefined `f`, and you get `f[$Aborted]` while I'd rather have just `$Aborted` as a result (this is similar to swallowing an exception in Java). There is an easy fix: change the last line from `res` to `If[res === $Aborted, Abort[], res]`, if you agree with my viewpoint. In any case, +1. – Leonid Shifrin Sep 23 '11 at 19:10
  • @Leonid, its a reasonable change, so I made it. My question is how to use `file` not `file$` when using `MakeCheckedReader`? Do you have any thoughts, or should I move it to a proper question? – rcollyer Sep 23 '11 at 19:23
  • @rcollyer Yes, this is relatively easy. You have to fool the var-renaming mechanism of the outer `SetDelayed`. I described the procedure in this post: http://stackoverflow.com/questions/6236458/plot-using-with-versus-plot-using-block-mathematica/6236808#6236808, using `With` as an example. For the case at hand, change the line `fcn[file_Symbol, symbols] := a` to `SetDelayed @@ Hold[fcn[file_Symbol, symbols], a]`. This makes this definition dynamically-generated and the outer `SetDelayed` can no longer spot the inner one, thus does not attempt to rename. – Leonid Shifrin Sep 23 '11 at 19:48
  • @Leonid, I have trouble believing it was that simple. Thanks. Updating the answer. – rcollyer Sep 23 '11 at 19:56
  • @rcollyer You've got to believe :). Seriously, I discovered this method relatively recently (it came to my mind after I gave it a serious thought and analyzed how the renaming mechanism in mma can possibly work), previously was using longer and less elegant work-arounds. What I like perhaps the most about mma is that it is so hackable. And, while you are at updating, you may want to change the comment "not propagate" to "propagate" - we do want `Abort`-s to propagate. – Leonid Shifrin Sep 23 '11 at 20:00
  • @Leonid, fixed. I used to use Excel extensively, and always hated that it was very easy to run into its limitations. While its rigidity makes it easier to use, mma is so much more powerful because of the lack of similar constraints. – rcollyer Sep 23 '11 at 20:06
9

I'm coming late to the party, with an accepted answer and all, but I want to point out that definitions of the form:

f[...] := Module[... /; ...]

are very useful in this context. Definitions of this kind can perform complex calculations before finally bailing out and deciding that the definition was not applicable after all.

I will illustrate how this can be used to implement various error-handling strategies in the context of a specific case from another SO question. The problem is to search a fixed list of pairs:

data = {{0, 1}, {1, 2}, {2, 4}, {3, 8}, {4, 15}, {5, 29}, {6, 50}, {7,
     88}, {8, 130}, {9, 157}, {10, 180}, {11, 191}, {12, 196}, {13, 
    199}, {14, 200}};

to find the first pair whose second component is greater than or equal to a specified value. Once that pair is found, its first component is to be returned. There are lots of ways to write this in Mathematica, but here is one:

f0[x_] := First @ Cases[data, {t_, p_} /; p >= x :> t, {1}, 1]

f0[100] (* returns 8 *)

The question, now, is what happens if the function is called with a value that cannot be found?

f0[1000]
error: First::first: {} has a length of zero and no first element.

The error message is cryptic, at best, offering no clues as to what the problem is. If this function was called deep in a call chain, then a cascade of similarly opaque errors is likely to occur.

There are various strategies to deal with such exceptional cases. One is to change the return value so that a success case can be distinguished from a failure case:

f1[x_] := Cases[data, {t_, p_} /; p >= x :> t, {1}, 1]

f1[100] (* returns {8} *)
f1[1000] (* returns {} *)

However, there is a strong Mathematica tradition to leave the original expression unmodified whenever a function is evaluated with arguments outside of its domain. This is where the Module[... /; ...] pattern can help out:

f2[x_] :=
  Module[{m},
    m = Cases[data, {t_, p_} /; p >= x :> t, {1}, 1];
    First[m] /; m =!= {}
  ]

f2[100] (* returns 8 *)
f2[1000] (* returns f2[1000] *)

Note that the f2 bails out completely if the final result is the empty list and the original expression is returned unevaluated -- achieved by the simple expedient of adding a /; condition to the final expression.

One might decide to issue a meaningful warning if the "not found" case occurs:

f2[x_] := Null /; Message[f2::err, x] 
f2::err = "Could not find a value for ``.";

With this change the same values will be returned, but a warning message will be issued in the "not found" case. The Null return value in the new definition can be anything -- it is not used.

One might further decide that the "not found" case just cannot occur at all except in the case of buggy client code. In that case, one should cause the computation to abort:

f2[x_] := (Message[f2::err, x]; Abort[])

In conclusion, these patterns are easy enough to apply so that one can deal with function arguments that are outside the defined domain. When defining functions, it pays to take a few moments to decide how to handle domain errors. It pays in reduced debugging time. After all, virtually all functions are partial functions in Mathematica. Consider: a function might be called with a string, an image, a song or roving swarms of nanobots (in Mathematica 9, maybe).

A final cautionary note... I should point out that when defining and redefining functions using multiple definitions, it is very easy to get unexpected results due to "left over" definitions. As a general principle, I highly recommend preceding multiply-defined functions with Clear:

Clear[f]
f[x_] := ...
f[x_] := Module[... /; ...]
f[x_] := ... /; ...
Community
  • 1
  • 1
WReach
  • 18,098
  • 3
  • 49
  • 93
  • Thanks, that seems like a useful pattern. I don't understand the meaning of "Condition[...,Message]"...Documentation suggests it matches only if second argument evaluates to "True", but Message returns Null, so why does it work? – Yaroslav Bulatov Nov 21 '10 at 01:11
  • Another question, is there an easy way to abort the evaluation mid-way and return unevaluated? Right now, I do If[somethingbadhappened,Abort[]], but that returns $Aborted – Yaroslav Bulatov Nov 21 '10 at 01:20
  • @Yaroslav Bulatov: When a condition is a guard on a definition, any value other than True will cause the evaluator to move on to the next matching definition (if any). In this case, Null =!= True so the evaluator moves on -- but there is no further matching definition so the expression is returned unchanged. – WReach Nov 21 '10 at 04:53
  • @Yaroslav Bulatov: Strictly speaking, aborting and returning unevaluated are mutually exclusive, but I think I know what you mean: f[x_] := Module[{t}, t = someComplexComputation[x]; moreComplexComputation[t] /; !somethingBadHappened[t] ] – WReach Nov 21 '10 at 05:03
  • +1, for a good example/pattern for dealing with errors generated consistently. – rcollyer Nov 23 '10 at 16:21
3

The problem here is essentially one of types. One function produces a bad output (incorrect type) which is then fed into many subsequent functions producing lots of errors. While Mathematica doesn't have user defined types like in other languages, you can do pattern matching on function arguments without too much work. If the match fails the function doesn't evaluate and thus doesn't beep with errors. The key piece of syntax is "/;" which goes at the end of some code and is followed by the test. Some example code (and output is below).

Input:
Average[x_] := Mean[x] /; VectorQ[x, NumericQ]
Average[{1, 2, 3}]
Average[$Failed]

Output:
2
Average[$Failed]

If the test is simpler, there is another symbol that does similar pattern testing "?" and goes right after an argument in a pattern/function declaration. Another example is below.

Input:
square[x_?NumericQ] := x*x
square[{1, 2, 3}]
square[3]

Output:
square[{1, 2, 3}]
9
Samsdram
  • 1,615
  • 15
  • 18
3

It can help to define a catchall definition to pick up error conditions and report it in a meaningful way:

f[x_?NumericQ] := x^2;
f[args___] := Throw[{"Bad Arguments: ", Hold[f[args]]}]

So your top level calls can use Catch[], or you can just let it bubble up:

In[5]:= f[$Failed]

During evaluation of In[5]:= Throw::nocatch: Uncaught Throw[{Bad Args: ,Hold[f[$Failed]]}] returned to top level. >>

Out[5]= Hold[Throw[{"Bad Args: ", Hold[f[$Failed]]}]]
Joshua Martell
  • 7,074
  • 2
  • 30
  • 37
3

What I'd love to get is a way to define a general procedure to catch error propagation without the need to change radically the way I write functions right now, preferentially without adding substantial typing.

Here is a try:

funcDef = t_[args___]  :c-:  a_ :> ReleaseHold[Hold[t[args] := 
                         Check[a, Print@Hold[a]; Abort[]]]];
Clear@v;
v[x_, y_] :c-: Sin[x/y] /. funcDef;
?v
v[2, 3]
v[2, 0] 

The :c-: is of course Esc c- Esc, an unused symbol (\[CircleMinus]), but anyone would do.

Output:

Global`v
v[x_,y_]:=Check[Sin[x/y],Print[Hold[Sin[x/y]]];Abort[]]

Out[683]= Sin[2/3]

During evaluation of In[679]:= Power::infy: Infinite expression 1/0 encountered. >>

During evaluation of In[679]:= Hold[Sin[2/0]]

Out[684]= $Aborted

What we changed is

       v[x_, y_] := Sin[x/y]

by

       v[x_, y_] :c-: Sin[x/y] /. funcDef;  

This almost satisfies my premises.

Edit

Perhaps it's also convenient to add a "nude" definition for the function, that does not undergo the error checking. We may change the funcDef rule to:

funcDef = 
     t_[args___]  \[CircleMinus] a_ :> 

            {t["nude", args] := a, 

             ReleaseHold[Hold[t[args] := Check[a, Print@Hold[a]; Abort[]]]]
            };  

to get for

 v[x_, y_] :c-: Sin[x/y] /. funcDef;  

this output

v[nude,x_,y_]:=Sin[x/y]

v[x_,y_]:=Check[Sin[x/y],Print[Hold[Sin[x/y]]];Abort[]]
Dr. belisarius
  • 60,527
  • 15
  • 115
  • 190
  • As per my comment above of this solution being unable to tap into the syntax highlighting system, check out the function `SyntaxInformation`. According to the help files, it allows you to tell Mathematica how things are to be highlighted. I haven't tried it, but you may be able to have the symbols in `args` highlighted correctly on both sides of `:c-:`. – rcollyer Jan 11 '11 at 20:40
  • +1 for using an infix symbol for a custom definition operator. By defining a DownValue for `:c-:` you won't have to to ` /. funcDef`. – masterxilo Aug 29 '16 at 15:48