18

Whenever I create a large Mathematica project I run into this problem: Preventing avalanche of runtime errors in Mathematica, i.e., Mathematica's error message are opaque, archaic, and legion.

The idea then is to disable all of Mathematica's own error messages and implement type checking and error messages of your own in every Function and Module. However I have not found a simple and efficient way of doing this and end up with, e.g., some function generating an error 20 function calls deep and then get a whole cascade of error messages all the way back up to the main routine.

How would you set up a simple mechanism for this that only generates one error message at the function that experiences the error and a simple list of the chain of function calls?

EDIT: Since it has come up in a couple of answers; I am specifically looking for something lightweight regarding the output it produces (otherwise I could just stick with Mathematica's error messages) and obviously also lightweight in computational overhead. So while Stack and Trace are definitely light on the overhead, their output in complex projects is not quick to parse and some work needs to be done simplifying it.

Community
  • 1
  • 1
Timo
  • 4,246
  • 6
  • 29
  • 42
  • I like the idea of simple mechanism! Mathematica's "warnings" usually correspond to unrecoverable errors, hence no sense continuing evaluation. It would be nice to have something that interrupts evaluation on first message. – Yaroslav Bulatov Nov 14 '10 at 20:12
  • Terminating evaluation after the first Message might be done by messing with `Message[]` itself... interesting approach! – Timo Nov 14 '10 at 21:47
  • 2
    Btw, built-in debugger in version 7 does something similar -- if you check "Break at Messages", it'll break at each message and show you the Stack – Yaroslav Bulatov Nov 15 '10 at 07:00
  • Rewrote my answer. It still needs some work, but I think it is the easiest way to type check and print the stack without a major rewrite of existing code. – Samsdram Nov 17 '10 at 00:33
  • @Samsdram Nice idea, but instead of Return[False] you should simply Abort[]. Otherwise you end up with a giant error cascade if the error is several function calls deep. – Timo Nov 17 '10 at 07:45
  • I believe that by returning false the pattern that it testing on won't evaluate at all. I thought this would be nice so that you could see the entire call that generated the error. But you are right that if all the functions don't have this kind of type checking then it could cause a cascading error. – Samsdram Nov 17 '10 at 18:37

5 Answers5

9

YAsI - Yet Another (silly?) Idea ...

Re-reading your question ...

The idea then is to disable all of Mathematica's own error messages and implement type checking and error messages of your own in every Function and Module.

Found this:

$MessagePrePrint = ( #; Print[Stack[_][[;; -5]]]; Abort[]) &  

v[x_, y_] := w[x, y];
w[x_, y_] := x/y;

StackComplete@v[1, 0];

During evaluation of In[267]:= {StackComplete[v[1,0]];,
          StackComplete[v[1,0]], v[1,0], w[1,0], 1/0, 1/0, Message[Power::infy,1/0]}

Out[267]= $Aborted

conclusion ... Aborts at first message and leaves a "reasonable" stack trace. "Reasonable" means "Should be improved".

But it is completely non-intrusive!

Dr. belisarius
  • 60,527
  • 15
  • 115
  • 190
  • 1
    Very nice! I'll have to play around with this, but it seems that a proper format on $MessagePrePrint would mean that you only need to add one single line, i.e. `StackComplete@funcName[args]`, into Functions at the point where you error / type check. – Timo Nov 16 '10 at 07:12
  • @Timo please post if you make some progress. I'll start testing this one too. I think the trace is too linear (something like TreePlot could be a killer), the arguments logging is a great free bonus over my previous try (your theStack idea). Also, setting a debug flag (If[deBug,..] may allow the same code running almost without performance penalty. – Dr. belisarius Nov 16 '10 at 12:13
  • Several of the other solutions are also very good but you get the checkmark for style! I'll try editing the op with a concise example later. – Timo Nov 17 '10 at 07:51
3

To get the ball rolling here is one idea that I've been toying with; the creation of a pseudo stack.

First make a global variable theStack={} and then in every Function or Module start with AppendTo[theStack,"thisFuncName"] and end with theStack=Most@theStack. Assuming moderate (~a few tens) depth of function calls, this should not add any significant overhead.

Then implement your own typing/error checking and use Print@theStack;Abort[]; on errors.

Refinements of this method could include:

  1. Figuring out a way to dynamically get "thisFuncionName" so that the AppendTo[] can be made into an identical function call for all Functions and Module.
  2. Using Message[] Instead of Print[].
  3. Pushing other important variables / stateful information on theStack.
Timo
  • 4,246
  • 6
  • 29
  • 42
  • Tried to implement (first try) in my answer – Dr. belisarius Nov 15 '10 at 01:35
  • A `Stack` already exists (http://reference.wolfram.com/mathematica/ref/Stack.html?q=Stack&lang=en), and it has a number of associated functions which would do the same thing. – rcollyer Nov 15 '10 at 19:57
  • `Stack` has, I think, the same problem as `Trace` which is that you need to do a lot of extra work to get a simple readable output. When writing / debugging large projects I find it almost always sufficient to know which Function generated the error, from where it was called and with what arguments. Both `Trace` and `Stack` provide this information but buried in a mountain of other stuff. I want something that I can glance at and immediately know where to go look for the bug. – Timo Nov 15 '10 at 21:40
3

A suggestion for extracting stack, maybe something that relies on Trace?

An example of using Trace below, from Chris Chiasson. This code saves evaluation tree of 1 + Sin[x + y] + Tan[x + y] into ~/temp/msgStream.m

Developer`ClearCache[];
SetAttributes[recordSteps, HoldAll];
recordSteps[expr_] :=

  Block[{$Output = List@OpenWrite["~/temp/msgStream.m"]}, 
   TracePrint[Unevaluated[expr], _?(FreeQ[#, Off] &), 
    TraceInternal -> True];
   Close /@ $Output;
   Thread[
    Union@Cases[
      ReadList["~/temp/msgStream.m", HoldComplete[Expression]], 
      symb_Symbol /; 
        AtomQ@Unevaluated@symb && 
         Context@Unevaluated@symb === "System`" :> 
       HoldComplete@symb, {0, Infinity}, Heads -> True], 
    HoldComplete]
   ];
recordSteps[1 + Tan[x + y] + Sin[x + y]]

To answer Samsdram's question, the code below (also from Chris) gives evaluation tree of a Mathematica expression. Here is the post from MathGroup with source code and examples.

(Attributes@# = {HoldAllComplete}) & /@ {traceToTreeAux, toVertex, 
  HoldFormComplete, getAtoms, getAtomsAux}
MakeBoxes[HoldFormComplete[args___], form_] := 
 MakeBoxes[HoldForm[args], form]
edge[{head1_, pos1_, xpr1_}, {head2_, pos2_, xpr2_}] := 
 Quiet[Rule[{head1, vertexNumberFunction@pos1, xpr1}, {head2, 
    vertexNumberFunction@pos2, xpr2}], {Rule::"rhs"}]
getAtomsAux[atom_ /; AtomQ@Unevaluated@atom] := 
 Sow[HoldFormComplete@atom, getAtomsAux]
getAtomsAux[xpr_] := Map[getAtomsAux, Unevaluated@xpr, Heads -> True]
getAtoms[xpr_] := Flatten@Reap[getAtomsAux@xpr][[2]]
toVertex[traceToTreeAux[HoldForm[heldXpr_], pos_]] := toVertex[heldXpr]
toVertex[traceToTreeAux[HoldForm[heldXprs___], pos_]] := 
 toVertex@traceToTreeAux[Sequence[], pos]
(*this code is strong enough to not need the ToString commands,but \
some of the resulting graph vertices give trouble to the graphing \
routines*)
toVertex[
  traceToTreeAux[xpr_, pos_]] := {ToString[
   Short@Extract[Unevaluated@xpr, 0, HoldFormComplete], StandardForm],
   pos, ToString[Short@First@originalTraceExtract@{pos}, StandardForm]}
traceToTreeAux[xpr_ /; AtomQ@Unevaluated@xpr, ___] := Sequence[]
traceToTreeAux[_HoldForm, ___] := Sequence[]
traceToTreeAux[xpr_, pos_] := 
 With[{lhs = toVertex@traceToTreeAux[xpr, pos], 
   args = HoldComplete @@ Unevaluated@xpr}, 
  Identity[Sequence][
   ReleaseHold[
    Function[Null, edge[lhs, toVertex@#], HoldAllComplete] /@ args], 
   ReleaseHold@args]]
traceToTree[xpr_] := 
 Block[{vertexNumber = -1, vertexNumberFunction, 
   originalTraceExtract}, 
  vertexNumberFunction[arg_] := 
   vertexNumberFunction[arg] = ++vertexNumber; 
  originalTraceExtract[pos_] := 
   Extract[Unevaluated@xpr, pos, HoldFormComplete]; {MapIndexed[
    traceToTreeAux, Unevaluated@xpr, {0, Infinity}]}]
TraceTreeFormPlot[trace_, opts___] := 
  Block[{$traceExpressionToTree = True}, 
   Through@{Unprotect, Update}@SparseArray`ExpressionToTree; 
   SparseArray`ExpressionToTree[trace, Infinity] = traceToTree@trace; 
   With[{result = ToExpression@ToBoxes@TreeForm[trace, opts]}, 
    Through@{Unprotect, Update}@SparseArray`ExpressionToTree; 
    SparseArray`ExpressionToTree[trace, Infinity] =.; 
    Through@{Update, Protect, Update}@SparseArray`ExpressionToTree; 
    result]];

TraceTreeFormPlot[Trace[Tan[x] + Sin[x] - 2*3 - 55]]
Chris Chiasson
  • 547
  • 8
  • 17
Yaroslav Bulatov
  • 57,332
  • 22
  • 139
  • 197
  • Could you post the link, please? – Dr. belisarius Nov 15 '10 at 01:39
  • Sorry, don't have the link, I copied this from some MathGroup mailing list message years ago – Yaroslav Bulatov Nov 15 '10 at 01:51
  • Trace is very nice, but as you see it requires a lot of work to make its output readable :-). Also it feels like taking an orbital missile command to a knife fight. I like the idea of saving the output of `Trace` somewhere else until it is possibly needed. – Timo Nov 15 '10 at 11:54
3

One attempt to implement @Timo's idea (theStack)

Incomplete and perhaps flawed, but just to keep thinking about it:

Clear["Global`*"];
funcDef = t_[args___]  \[CircleMinus] a_ :>
   {t["nude", args] := a,
    ReleaseHold[Hold[t[args] :=
       (If[! ValueQ[theStack], theStack = {}];
        AppendTo[theStack, ToString[t]];
        Check[ss = a, Print[{"-TheStack->", Evaluate@theStack}]; 
         Print@Hold[a]; Abort[]];
        theStack = Most@theStack;
        Return[ss])
      ]]};
v[x_, y_]\[CircleMinus]  (Sin@ g[x, y]) /. funcDef;
g[x_, y_]\[CircleMinus]  x/y /. funcDef;
v[2, 3]
v[2, 0]

Output:

Out[299]= Sin[2/3]

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

During evaluation of In[295]:= {-TheStack->,{v,g}}

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

Out[300]= $Aborted
Dr. belisarius
  • 60,527
  • 15
  • 115
  • 190
  • BTW ... I didn't find a way to make funcDef to store function arguments in theStack. Ideas? – Dr. belisarius Nov 15 '10 at 02:23
  • I was thinking of just pushing and removing lists, e.g., AppendTo[theStack,{"funcName",{funArg1,..}}]. – Timo Nov 15 '10 at 08:11
  • @Timo The intent is taking care of Messages, by aborting and dumping "theStack". I am not still able to enter the function arguments, and that is clearly a flaw. – Dr. belisarius Nov 15 '10 at 11:26
  • @belisarus After playing around with your solution for a while I see what you mean. Very interesting approach though! I wouldn't have thought of making the whole thing effectively a lambda function. – Timo Nov 15 '10 at 11:48
  • @belisarus, see my comment on Timo's answer. – rcollyer Nov 15 '10 at 19:58
2

Perhaps we have been over thinking this. What if we just tweaked the pattern matching on the arguments a little. For instance, if we modified the function to check for a numeric quantity and added some code to print an error if it fails. For instance,

 TypeNumeric[x_] :=   If[! NumericQ[Evaluate[x]],
 Print["error at "]; Print[Stack[]];    Print["Expression "]; Print[x];    Print["Did   
 not return a numeric value"];Return[False], 
 (*Else*)
 Return[True];] 
 SetAttributes[TypeNumeric, HoldAll];

Step 2: If you have a function, f[x_] that requires a numeric quantity, just write it with the standard pattern test and all should be well

Input:
f[x_?TypeNumeric] := Sqrt[x]
f[Log[y]]
f[Log[5]]
Output:
error at 
{f}
Expression 
Log[y]
Did not return a numeric value
f[Log[y]]

Sqrt[Log[5]]

I believe this will work and, it makes robust type checking as simple as a writing a function or two. The problem is that this could be hugely inefficient because this code evaluates the expression x twice, once for the type checking and once for real. This could be bad if an expensive function call is involved.

I haven't figured out the way around this second problem and would welcome suggestions on that front. Are continuations the way out of this problem?

Hope this helps.

Samsdram
  • 1,615
  • 15
  • 18
  • @Samsdram Are you looking for something like TreeForm[]? – Dr. belisarius Nov 15 '10 at 00:30
  • Why are contracts better than If,Print,Abort checks? Strict type-checking might be useful for large/collaborative projects, but personally I want something that helps with errors without requiring me to change how I write Mathematica functions now – Yaroslav Bulatov Nov 15 '10 at 00:48
  • btw, I added an edit in my answer which gives something like TreeForm, but for evaluation tree instead of parse tree – Yaroslav Bulatov Nov 15 '10 at 00:52
  • Agree with @Yaroslav. For large projects this seems the way to go, but too cumbersome for bread and butter calculations – Dr. belisarius Nov 15 '10 at 01:38
  • Contracts are better than If/Print/Abort etc because errors are often two or three function calls removed. For instance define f[x_]:=x/;x>=0 and then call f[g[x]]. Simple tools can catch the error when it gets to the function f, but when you have a lot of code what you really want to know is that g[x] was the cause of the problem. I am not sure you can do this with the stack approach suggested earlier because g[x] will have finished evaluating before the error is caught in f[x], should be no record of it on the stack. Overkill for small projects, but the question asked about large projects. – Samsdram Nov 15 '10 at 18:20
  • if Mathematica were like Java, then an assert(x>0) inside f would abort and you would see line with "f(g(x))" on the stack trace, is that the idea? – Yaroslav Bulatov Nov 15 '10 at 20:00
  • @Samsdram You are right about the project size considerations, I was just answering your previous suggestion *As there seems to be quite a few people that are interested in this problem ...*, as I'm not generally involved in really big projects in Mma, but are aware of the benefits of cascade error control for small projects too. Your approach seems promising. – Dr. belisarius Nov 17 '10 at 01:05
  • @Samsdram Don't forget to take a look at Eiffel contracts http://www.eiffel.com/developers/design_by_contract.html – Dr. belisarius Nov 17 '10 at 01:09