如何在使用Mathematica编写小型或大型代码时进行调试?使用工作台? MMA调试器?还是其他方法?

12

我在mathkb.com上发现了一篇有趣的文章"Another review of Mathematica's debugger"(作者为berniethejet),其中讨论了Wolfram Workbench中的调试功能。

http://www.mathkb.com/Uwe/Threads/List.aspx/mathematica/20986

我认为这是一个值得讨论的好问题,我想听听使用工作台的经验,尽管我从未接触过工作台。

  1. 工作台是一个真正的调试器还是一个监视器?它相对于Mathematica有什么优势?
  2. 当您编写大或小代码时,如何进行调试?也许工作台适用于调试小代码,而mma调试器适用于大代码?
  3. 对于轻量级和重度Mathematica用户的任何调试建议?

这可能与StackOverflow的主题无关。我不活跃于http://programmers.stackexchange.com,但我认为在那里提问会更好。 - Mr.Wizard
谢谢,巫师。我在programmers.stackexchange.com上搜索了“mathematica”,只有几个非常有用的结果。 - FreshApple
1
这可能是一个“先有鸡还是先有蛋”的问题,但我希望这个问题能够引起关注,特别是如果它从这里迁移过来的话。如果您认为我是正确的,您可以点击您的帖子下方的“标记”,选择“其他”,并要求管理员将其迁移到程序员板块。 - Mr.Wizard
4个回答

21

我的经验是,当你使用有状态的编程风格(变量、赋值等)时,调试器通常更有用 - 至少在我看来是这样。对于符合惯用法的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会话中开发新功能。对于涉及许多软件包的大型项目,我只看到使用它的理由。


感谢分享您的宝贵经验!Shifrin. :) - FreshApple
谢谢Leonid,好文章。对于Workbench,我发现它使用起来很麻烦,因为它没有集成在Mathematica笔记本本身中,必须启动另一个单独的程序来调试事情。我习惯于Matlab的调试器,我发现它更容易和更直观,因为它是直接构建在编辑器本身中的,我可以更轻松地打开和关闭它。我认为Mathematica调试器也应该作为笔记本编辑器/界面的一部分构建。我也有Workbench 2.0,并尝试过几次,但停止使用了。每次我想检查某些东西时,它太令人困惑了,而且设置起来太麻烦了。 - Nasser
@Leonid,我有点困惑,我以为WB只是一个调试器,从来没有想过要把它当作笔记本界面的替代品。通常我是这样做的:启动Mathematica,打开一个笔记本,在那里工作。当我想要调试一个函数时,将其移动到一个单独的单元格中,然后启动WB,导航到已经打开的笔记本,点击几个按钮,将函数代码复制并加载到WB中,还有其他一些我忘记了的事情,因为我上次使用WB已经是一年前的事了。如果我真的可以用WB来替代笔记本,我会逐渐习惯的,我得尽快试试看。 - Nasser
@Leonid:希望我在15分钟前就给你点赞了,那么你就能获得“硕士帽”徽章了!无论如何,恭喜你达到了5k! - user564376
@Nasser,WorkBench的整个重点在于它是一个完整的 IDE(集成开发环境)- 对于任何规模的项目都非常有用,除了可能是最小的项目。除了优秀的代码编辑器外,它还具有许多功能,可以简化项目结构、导航、簿记、版本控制集成等等。IDE 对于像 Java 和 C# 这样的语言最受欢迎(其中一个原因是它们是静态类型),但对于动态类型的语言也可以节省大量时间。调试器只是其中一项便利设施,WB 提供了更多功能。 - Leonid Shifrin
显示剩余3条评论

4
使用 Wolfram Workbench 中的调试器可以使调试变得简单而有效。我开始使用 Workbench 的原因是它的调试器。Workbench 还支持 MUnit,这是 Mathematica 版本的 JUnit,“先测试,再编码”。
Workbench 中的调试器支持我所期望的所有功能。我曾经使用过 Eclipse 和 NetBeans 中的 Java 调试器。
至少试用一下调试器,这样你就可以进行比较了。Workbench Docs 网站上有一个教程。

实际上,我们的研究小组计划编写一个小型的基于偏微分方程的多物理学包,这可能需要不断进行错误修复。我想工作台更适合这个项目。谢谢,ndroock1. :) - FreshApple

3
以下是Leonid描述的ShowIt的一些变体。在系统上下文中定义它们可以轻松地在软件包中使用。
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];

例子:

ShowIt[{x=2,x=3}]
PrintIt[{x=2,x=3}]

这些函数的输出可以很容易地通过将其样式更改为“输入”来在前端中重复使用。

2

我在使用调试器方面取得了有限的成功,主要是因为我从未花时间去学习它。然而,我经常使用一种技巧。我不会使用print语句,而是使用形式为Dynamic[var]的表达式来操作我的变量(或其他内容)。通过这种方式,您可以轻松实时监控任何文件全局变量,而不会产生大量输出。要查看操纵变量,请使用LocalizeVariables->False并执行相同的操作。在操纵上下文之外,变量是可见的,但不是动态的;因此,它们的监视方式是相同的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接