Debuggers are generally more useful when you program in the stateful style (variables, assignments, etc) - at least that has been my experience. For idiomatic Mathematica programming (functional/rule-based), some versions of Print
statements are at least as effective. You may look at this post for some variants of debug print utility. I will throw in my version taken from this Mathgroup post.
SetAttributes[ShowIt, HoldAll]; ShowIt[code_] := Module[{y}, Print[ToString[Unevaluated[code]], " = ", y = code]; y];
The idea is that you can insert such function call into a "pipe" of function calls - it prints the value but then passes it to the next (surrounding) function. As a simple example:
In[29]:= Map[#^2&,ShowIt@Select[Range[10],EvenQ]] During evaluation of In[29]:= Select[Range[10], EvenQ] = {2,4,6,8,10} Out[29]= {4,16,36,64,100}
This should work fine in most cases (except possibly those where the surrounding function holds its arguments and acts on them non-trivially). One of the reasons that this approach is very effective in Mathematica is that functional programming leads to programs in which (almost) every piece makes sense by itself - since the result of one function is typically passed directly to the enclosing function.
That said, you can certainly use the debugger, both within interactive session and in the WorkBench, using "Debug As Mathematica" regime. While I use WorkBench a lot myself, I never found this necessary, but YMMV.
Another great facility that helps a lot is a built-in Trace command. I recommend reading the documentation on it - it has a number of advanced options and can be customized to help a great deal. I will give one simple but non-trivial example: tracing the execution of the mergesort algorithm, with the following (simplistic) implementation:
Clear[merge]; merge[{}, {x__}] := {x}; merge[{x__}, {}] := {x} merge[{x__?NumericQ}, {y__?NumericQ}] /; First[{x}]
We will take a very small input list, just to reduce the length of the output:
In[41]:= testlst = RandomInteger[10, 5] Out[41]= {0, 6, 9, 8, 8}
You could just use Trace[mergesort[testlst]];
, but the output is not very easy to read, since it contains all the steps. By using
In[42]:= Trace[mergesort[testlst],_mergesort] Out[42]= {mergesort[{0,6,9,8,8}],{mergesort[{0,6}],{mergesort[{0}]}, {mergesort[{6}]}},{mergesort[{9,8,8}],{mergesort[{9}]},{mergesort[{8,8}], {mergesort[{8}]},{mergesort[{8}]}}}}
You get a very clear picture of recursive function calls. You can go deeper and trace the dynamics of merge
function. For that, you have to process the result of Trace
(which is also a Mathematica expression!):
In[43]:= Cases[Trace[mergesort[testlst],_merge],merge[x__List]/;FreeQ[{x},mergesort]:> HoldForm[merge[x]],Infinity] Out[43]= {merge[{0},{6}],merge[{},{6}],merge[{8},{8}],merge[{},{8}], merge[{9},{8,8}],merge[{8,8},{9}],merge[{8},{9}],merge[{},{9}],merge[{0,6}, {8,8,9}],merge[{6},{8,8,9}],merge[{},{8,8,9}]}
This last example illustrates that, even when it is hard to configure Trace
directly to filter out unwanted execution steps, one can simply post-process the results of Trace
using standard means that Mathematica provides for expression destructuring (such as Cases
).
Let me also mention that an expert Mathematica user and consultant David Bailey wrote a package DebugTrace, which is supposed to be an alternative debugger. I did not have a chance yet to try it, but I am sure it is worth the try.
Finally, while this is not directly related to debugging, WorkBench has an integrated unit testing framework MUnit, which I found very useful. It is similar in spirit to well-known unit-testing frameworks in other languages, such as JUnit for Java. For large-scale development, this can be a real help.
Regarding the uses of WorkBench, I'd say that it really pays off to use it for anything except the smallest projects (or even for them). It is based on Eclipse, and you get the same nice things, such as the editor with code highlighting, "go to function definition" capability, navigation, search, CVS/SVN integration, etc. At the same time, you don't lose almost anything in terms of interactivity - you can still develop the new functionality in the interactive Mathematica session linked to WorkBench when working in the "Run as Mathematica" regime. For larger projects involving many packages, I just don't see any reason not to use it.
Using the debugger in Wolfram Workbench makes debugging simple and effective. The reason I started to use Workbench was the debugger. The Workbench also supports MUnit the Mathematica variant of JUnit. - "Test first, then code."
The debugger in Workbench supports everything that I expected from a debugger. I have used the Java debuggers in Eclipse and NetBeans.
At least give the debugger a try, so that you can compare. There is a tutorial on the Workbench Docs site.
Here are some variations of ShowIt described by Leonid. Defining them in the System context allows to use them easily in packages.
SetAttributes[System`ShowIt, HoldAll]; System`ShowIt[code__] := System`ShowIt[{code}]; System`ShowIt[code_] := With[{y = code}, Print[Defer[code = y]]; y ]; SetAttributes[System`PrintIt, {HoldAll,Listable}]; System`PrintIt[expr__]:=System`PrintIt[{expr}]; System`PrintIt[expr_] := System`ShowIt[expr];
Example:
ShowIt[{x=2,x=3}] PrintIt[{x=2,x=3}]
The output of these functions can be easily reused in the frontend by changing its style to "Input".