2

如果我有一个用作命令行工具的程序,我有哪些调试选项?

为了这个例子,假设程序看起来像这样。

清单do_stuff.pl

main :-
    current_prolog_flag(argv, Argv),
    do_stuff(Argv),
    halt.
main :-
    halt(1).

使用 SWI-Prolog,我可以编译它:

swipl --goal=main -o do_stuff -c do_stuff.pl

我可以通过简单地调用来运行它

$ ./do_stuff foo bar baz

就目前而言,如果do_stuff/1失败,它将以 1 退出。我怎样才能看到失败的第一个(最早、最深的)目标?或者更好的是,整个回溯?我假设我应该能够使用debugand leash,例如:

main :-
    current_prolog_flag(argv, Argv),
    debug, leash(+fail),
    do_stuff(Argv),
    halt.

...但我没有尝试过任何有效的方法。

我唯一半途而废的想法是为每个我希望确定性成功但没有成功的谓词抛出一个错误。这当然是可行的,但似乎有点过分?

动机

用作命令行工具的程序(通常)意味着运行一次,获取其参数,读取其输入,写入输出。在这种情况下,失败意味着什么?我的解释是,意外失败是程序中的错误。

单元测试可能会有所帮助(单独测试谓词);但是,根据定义,这对于由于程序员对问题、范围或工具缺乏了解而导致的错误没有帮助。只有使用实际输入运行程序才能捕获此类错误。

所以,给定上面的例子,如果某个用例导致do_stuff/1失败,并且程序以非零代码退出,程序员有什么选择来确定哪个谓词失败?

评论中链接的答案提供了一种解决方案。但是(如果我理解正确的话)这确实需要程序员系统地检查执行流程,直到找到有问题的谓词调用。

这正是我希望避免的。

4

1 回答 1

3

与更多面向命令的语言相比,失败在 Prolog 中是一件非常不寻常的事情。它从第一天起就引起了人们的兴趣。事实上,即使在 Prolog 0(Prolog I 之前的版本)中,除了跟踪选项之外,还有一个仅显示失败ECRIRE的特殊选项。IMPASSES

后来,Mirelle Ducassé 的特别工作试图自动找出失败的原因。

失败的奇怪之处在于它们并不一定表明出了问题。但有时,他们是。

我想说,如何理解失败有两个不同的方向。第一个是程序性的,第二个是声明性的。

注释

在许多程序中,我(@)/1用来表示我希望目标总是成功。感谢操作符声明,这只是一个额外的字符:

   ...,
   @goal_aux_togoalaux_spec(OQuery, FVect0, Query, Spec),
   ...

如果目标失败,则会发出错误。记录嵌套异常也很重要。如果有一些时间紧迫的东西,这些@必须被删除。但是,我只计算了 120kLOP 中的约 400 个。

请注意,这@也适用于具有多个答案的目标。喜欢 @member(1,[X,Y])

这种技术适用于事实上的模拟程序。想想的准备工作(就是上面的例子)。在那里,你主要在想:这是一个程序,什么是合适的切片?在这种情况下,答案:“不,没有切片”将不是答案。你真的希望它永远成功。如果您没有这样的模式程序,您通常可以通过强制稳定来转换现有的未模式程序:

p(X, Y) :-
   wellformed(X),
   @p_old(X, Yc),
   Yc = Y.

该技术在纯关系、声明性代码中迅速失去吸引力。以最近为例。在那里,几乎不可能添加@- 除了第一个目标。在这种情况下,需要一种更具声明性的方法,如下所示。

概括

对于更复杂的问题,@效果不太好。相反,需要程序修改/切片。需要通过添加前缀来概括程序*。有关手动使用此技术的 SO 上的此类调试会话的集合,请参阅此答案。这种技术的要点是,在确定最大泛化时,您不必理解程序的真正含义。你只需要关注失败的目标。

理想情况下,这样的概括会自动产生。然而,有很多障碍。一方面,它们只适用于纯单调代码(事实上,这是一个很好的动机,为什么人们应该坚持这样的代码)。因此,首先必须对现有代码进行分析和分类。如果系统不符合并随机改变其行为(如您提到的系统),这将更加困难。

于 2016-04-16T19:23:00.883 回答