我的经验是,当你使用有状态的编程风格(变量、赋值等)时,调试器通常更有用 - 至少在我看来是这样。对于符合惯用法的Mathematica编程(基于函数/规则),某些版本的Print
语句至少同样有效。您可以查看此帖子,了解一些变体的调试打印实用程序。我将提供我从此 Mathgroup 帖子中获取的版本。
SetAttributes[ShowIt, HoldAll];
ShowIt[code_] :=
Module[{y},
Print[ToString[Unevaluated[code]], " = ", y = code];
y];
这个想法是你可以将这样的函数调用插入到“管道”中的其他函数调用中 - 它会输出值,然后将其传递给下一个(外围的)函数。这是一个简单的例子:
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}
在大多数情况下,这应该可以正常工作(除了一些周围函数持有其参数并以非平凡方式对它们进行操作的情况可能除外)。该方法之所以在Mathematica中非常有效,是因为函数式编程会导致程序中的每个部分(几乎)都可以单独理解 - 因为一个函数的结果通常直接传递给封闭函数。
话虽如此,您肯定可以使用调试器,在交互式会话和WorkBench中使用"Debug As Mathematica"模式。虽然我自己经常使用WorkBench,但我从未发现这是必要的,但结果可能会有所不同。
另一个非常有用的功能是内置的Trace命令。我建议阅读它的文档 - 它具有许多高级选项,并且可以根据需要进行自定义。我将给出一个简单而又不平凡的示例:跟踪合并排序算法的执行,使用以下(简单化的)实现:
Clear[merge];
merge[{}, {x__}] := {x};
merge[{x__}, {}] := {x}
merge[{x__?NumericQ}, {y__?NumericQ}] /; First[{x}] <= First[{y}] :=
Flatten[{First[{x}], merge[Rest[{x}], {y}]}];
merge[{x__?NumericQ}, {y__?NumericQ}] := merge[{y}, {x}];
Clear[mergesort];
mergesort[x : {} | {_}] := x;
mergesort[x : {__?NumericQ}] :=
With[{splitlen = IntegerPart[Length[x]/2]},
merge[mergesort[Take[x, splitlen]], mergesort[Drop[x, splitlen]]]]
我们将采用一个非常小的输入列表,只是为了减少输出的长度:
In[41]:= testlst = RandomInteger[10, 5]
Out[41]= {0, 6, 9, 8, 8}
你可以直接使用
Trace[mergesort[testlst]];
,但输出结果不太易于阅读,因为它包含了所有步骤。 通过使用
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}]}}}}
递归函数调用的过程非常清晰。您可以深入了解merge
函数的动态。为此,您必须处理Trace
的结果(它也是一个Mathematica表达式!):
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}]}
这个例子说明即使直接配置Trace
以过滤不必要的执行步骤很困难,也可以通过使用Mathematica提供的表达式解构标准手段(如Cases
)后处理Trace
的结果。
另外,我还要提到一位专业的Mathematica用户和顾问David Bailey编写了一个名为DebugTrace的软件包,它被认为是一种替代调试器。我还没有试用过,但我相信这值得一试。
最后,虽然这与调试无直接关系,但WorkBench具有集成单元测试框架MUnit,我发现它非常有用。它的精神类似于其他语言中著名的单元测试框架,如Java的JUnit。对于大规模开发,这可以帮助很多。
关于WorkBench的用途,我认为除了最小的项目(甚至包括它们)之外,使用它确实是值得的。它基于Eclipse,您可以得到同样好的东西,例如带有代码高亮的编辑器,“转到函数定义”功能、导航、搜索、CVS/SVN集成等。同时,您在交互性方面几乎不会失去任何东西——当在“作为Mathematica运行”模式下工作时,您仍然可以在与WorkBench链接的交互式Mathematica会话中开发新功能。对于涉及许多软件包的大型项目,我只看到使用它的理由。