9
$\begingroup$

I'm trying to debug a function that goes into an endless loop. I tried to Trace[] some expressions inside the the function, but apparently Trace[] outputs its information only after the expression has completely evaluated. For example:

 (*notice the bug: counter is always >=0, this will never halt*)   
aSummer[x_]:=(counter=0;sum=0;While[counter>=0,sum=sum+counter;counter++];sum)

    Trace[aSummer[10],sum] (*farewell, never to return - and no 'sum's either*)

Is there a way to trace into a non-halting function like this?

$\endgroup$
2
  • 1
    $\begingroup$ TracePrint will do this. $\endgroup$
    – Szabolcs
    Commented Jan 17, 2015 at 0:34
  • 1
    $\begingroup$ Executing TracePrint[aSummer[10], sum] is a good way to crash the frontend LOL... you might want to stick a Pause[1] into your loop, otherwise you will be inundated with so much text that the notebook will freeze. $\endgroup$ Commented Jan 17, 2015 at 1:08

2 Answers 2

13
$\begingroup$

Monitor

For the trivial example shown in the question, we could use Monitor to watch the global variable sum change in real time:

Monitor[aSummer[10], sum]

But, in general, we will probably not be so lucky that the variable or expression that we wish to monitor happens to be global -- that's why we were using Trace in the first place.

TracePrint

TracePrint is like Trace, except that it prints the tracing information as execution proceeds instead of returning that information in the final evaluation result. Thus, TracePrint meets the stated requirement. However...

The output of TracePrint tends to be verbose. For the example at hand, it is potentially endless. If we are using TracePrint in the notebook front-end, then the UI will be overwhelmed and, unless we are very quick on the draw, it will crash. Also, the front-end is slow to respond to Abort Evaluation requests when large quantities of output are present.

TraceScan

TraceScan is an alternative to TracePrint that allows us to process the trace information with arbitrary functions.

As an example, we can use TraceScan to show a continuous rolling display of the variablesum:

DynamicModule[{watch}
, PrintTemporary[Dynamic[watch]]
; TraceScan[Null&, aSummer[10], sum, Function[Null, watch=HoldForm[#2], HoldAllComplete]]
]

This technique does not require any modification to the function being monitored. Here it is in action:

screenshot showing continuous trace

This example works by creating a temporary cell to watch the "live" trace information. TraceScan allows us to specify two functions: one that is called on all selected trace expressions prior to evaluation, and a second that is called on those expressions after evaluation. Since we are only interested in the result of evaluation, we supply a no-op Null& for the first function. The second function assigns the result of evaluation to the variable watch, which is being displayed by Dynamic[watch].

The documentation for TraceScan refers to the two functions as f and fp. It tells us that fp takes two arguments, namely the form before and after evaluation. We are interested in the "after" value, which is the second argument (#2). In our case, a notional call to fp would look something like fp[HoldForm[sum], 1234].

The documentation claims that both arguments to fp are wrapped in HoldForm to prevent inadvertent evaluation. This statement is incorrect: only the first is wrapped. Our code takes care to prevent further evaluation of the second argument. This elaboration is unnecessary for this simple example since we only view inert integers. But in the general case it is important to avoid extra evaluations that would disturb the computation.

A Custom Trace Display

Since the functions used here are under our control, we are limited only by our imagination as to how to show the trace information. Perhaps we would like to plot the progressive values of sum instead:

DynamicModule[{watch = {}}
, PrintTemporary[Dynamic[ListPlot[watch], UpdateInterval->1, TrackedSymbols->{}]]
; TraceScan[Null&, aSummer[10], sum, AppendTo[watch, #2]&]
]

We have dispensed with the HoldForm business since we know that in our case it doesn't matter. Also, the Dynamic display is throttled to update only once per second to avoid using all our CPU power to draw the plot.

The result looks like this:

screenshot showing continuous plot of <code>sum</code>

In summary, this technique has two advantages. First, no modifications need to be made to the code under test. Second, we have the full power of Mathematica available to visualize the trace information we gather.

What About the Debugger?

If I were really stumped by the behaviour of some complex function, and I was getting impatient, I would probably forego all of this and simply fire up the built-in debugger. Some (most?) people might disagree, but I find the debugger to be useful despite its finicky nature. This post won't discuss the debugger further as it is discussed elsewhere on StackExchange already, such as here.

$\endgroup$
4
  • $\begingroup$ Very clear, but I don't understand the "wrap up" functions of TraceScan[] taking the parameter #2 in watch=HoldForm[#2] and AppendTo[watch, #2]&. Is this function applied to aSummer[10], sum or both? And why do you specify it taking a second argument (there seems to be only one argument). Finally, what is the "Null" in Function[Null,...] $\endgroup$
    – yippy_yay
    Commented Jan 17, 2015 at 16:07
  • $\begingroup$ The documentation for TraceScan refers to the two functions as f and fp. It tells us that fp takes two arguments, namely the form before and after evaluation. We are interested in the "after" value, which is the second argument (#2). The documented statement that both arguments are wrapped in HoldForm is incorrect (only the first is wrapped). In our case, a notional call to fp would look something like fp[HoldForm[sum], 1234]. $\endgroup$
    – WReach
    Commented Jan 17, 2015 at 16:47
  • $\begingroup$ Function[Null, ...] specifies that the arguments to the function are anonymous and must be referenced using slot notation (#, #2, etc). This full-form expression is necessary to specify the HoldAllComplete attribute for the pure function. $\endgroup$
    – WReach
    Commented Jan 17, 2015 at 16:48
  • $\begingroup$ Great answer. You said all I would have if I weren't feeling lazy, and then you took it to the next level. :-) $\endgroup$
    – Mr.Wizard
    Commented Jan 17, 2015 at 18:30
7
$\begingroup$

One way to inspect such things without tracing is the following: Put a Dialog[] anywhere inside your loop, maybe with a condition that fires when you know things ran out of hand. Here an example which calls Dialog when counter is over 10000:

aSummer[x_] := (counter = 0; sum = 0; 
  While[counter >= 0, sum = sum + counter;
   If[counter > 10000, Dialog[]];
   counter++]; sum)

aSummer[10]

When you run this, you see that the cell bracket of the running cell looks slightly different, which shows that you are now in a subsidiary dialog session and you can evaluate whatever code that helps you to inspect the current problematic code

Stack[_While]
(* {While[counter>=0,sum=sum+counter;If[counter>10000,Dialog[]];counter++]} *)

or

sum
(* 50015001 *)

Note that you can simply modify the current state of the running evaluation:

counter = -10    
(* -10 *)

and with a Return[] you end the dialog session and, in this case, stop the loop. Btw, uou could have called Return[Unevaluated[Break[]]] to stop the loop too.

$\endgroup$

Your Answer

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

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