Can Trace[] output during evaluation (and not only after)?
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:
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:
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.
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.