Debugging with Breakpoints
I believe I've figured out the correct way to use breakpoints :D
The Chinese edition of this answer can be found here.
In short, Breakpoints can only be set on a non-atomic incomplete expression, Watchpoints can only be set on Symbol
.
…OK, maybe a bit too concise, let me elaborate a little.
1. Breakpoints must be set on an incomplete expression
a = 1;
Print[a]
a - 1
The break point set on Print[a]
won't work
because Print[a]
is already a piece of complete code, while Breakpoints can only be set on code piece that is inside a expression. Once Print[a]
becomes a part of bigger code, no matter it's a head or an argument, the breakpoint will work. For example
a = 1;
1[Print[a]]
a - 1
Another example:
a = 1;
Print[a]@1
a - 1
A ;
after Print[a]
is also OK, don't forget ;
is also a function:
a = 1;
Print[a];
a - 1
One thing that needs special attention is parenthesis.
If the expression is an argument or a head, and it's wrapped by a pair of parentheses, for example
f[(a + b)] (a + b)[f]
then the breakpoint can't be set on
a + b
:I think this can be explained as: the debugger considers the code inside parentheses as a complete expression.
In these cases, breakpoints must be set together with the parentheses:
However, if the expression is already a complete expression, and it's wrapped by a pair of parentheses, for example
(a + b)
then the breakpoint can be set on
a + b
: Currently I consider this as an exception.Of course in this case breakpoint can't be set on
(a + b)
:
2. Breakpoints can't be set on atom.
1
a[b];
2
The break point set on b
won't work
because b
is an atom:
AtomQ@b
(* True *)
Notice that even if a non-atomic expression is stored in b
, the breakpoint still won't work
b = c + d
a[b];
2
because this doesn't change the truth that b
is an atom:
AtomQ@Unevaluated@b
(* True *)
3. Watchpoints is for breaking at
Symbol
Now that we know Breakpoints can't be set on atom, is there any way to break at atom? The answer is yes, at least for atoms with head Symbol
. We just need to set Watchpoints.
The Watchpoints panel can be found at the bottom of Breakpoints(Local) window. We just need to type in the interested Symbol
and press Enter:
With this watchpoint, the evaluation will:
If you have the "Break on Assignment" checked, evaluation will break at position like
f = 1
Notice
SetDelayed
seems not to be considered as assignment by the debugger i.e. the evaluation won't break atf := 1
If you have the "Break on Evaluation" checked, evaluation will break whenever
f
is evaluated i.e. it'll even break atf
If you have the "Break on use as Function", evaluation will break at positions like:
f[1] := 2 f[1]
f[1] = 2; f[1]
f[x_] = Sin[x]; f[2]
f[x_] := Sin[x] f[2]
Notice all the built-in functions and symbols are also atoms with Head
Symbol
:
Head/@{Print, E}
(* {Symbol, Symbol} *)
so Watchpoints can be set on them. An interesting example is, by setting watchpoint on $Post
, you can execute code line by line. You just need to
Store a trivial pure function in
$Post
:$Post = #&;
Set watchpoint on
$Post
and check the "Break on Evaluation":As shown above, the evaluation now breaks line by line. To run the next line, just press the "Continue" button on the "(Local)" panel.
4. Watchpoints is only for breaking at
Symbol
Not to mention those non-automatic expressions like 1 + 1
, f[a]
, etc., atoms whose Head
aren't Symbol
, for example "a"
, 1
, 1.2
, can not be set as Watchpoints, either.
All the examples above are tested under v9.0.1, win10 64bit. So far no counter-example for the aformentioned rule is found, at least in my limited test.
There is a fundamental problem with breakpoints and Mathematica: Mathematica code is not typically attached to a source code file. It simply exists in-memory as a set of transformation rules on expressions. These transformation rules can be created (among other ways) by:
Loading a package file. In this case they correspond to the source code in the file.
Evaluating an input cell. In this case they still correspond to the code in the cell. But cells are too easy to change ... what if the cell gets modified (as it often does) or it gets deleted?
Completely programmatically. There is no corresponding source code (i.e. text) at all.
Most other languages have a workflow where we (1) write the code in a file (2) run the code. A Mathematica workflow is typically much more interactive: we write code in input cells, evaluate it, modify the input, re-evaluate, etc.
Breakpoints only work well if there is a stable source file, which does not get modified often. Breakpoints are set within the source (text) of the program. They are often tied to line numbers (another thing that's not very useful with Mathematica code).
My suggestion is: Do not use traditional breakpoints!
There will always be problems with traditional breakpoints because they are just not a good fit for Mathematica as a programming language, and and even worse fit for the typical Mathematica workflow.
What I do instead is that I insert explicit an Assert[False]
into the code at the point where I want to allow a break. Then I turn on "Break at Asserts". This behaves like a breakpoint, but since it is tied to the abstract expressions that make up a Mathematica program, not to the textual source code. Thus it is much more reliable, and eliminates most of the frustrating problems with the debugger.
While I don't use the debugger often, I do keep using it, and I do find that it helps pinpoint problems. I always use this method to break at the point of my choice.
The debugger in Mathematica is, in fact, a bit hard to use. However, it is functional. To create a breakpoint, one must select the WHOLE command. Just double clicking on the command name will do. Then you press the "Break at Selection" button in the debugger control panel. I still have no clear understanding what is considered a whole command from the Mathematica point of view. If the breakpoint doesn't work, I'll select the expression around it and try to make that a breakpoint.
In the example attached, selecting the "Print[...]" statement worked as a breakpoint, while selection only a "ToString[...]" command didn't stop the execution.