调试命令行程序

时间:2016-04-12 08:43:01

标签: debugging prolog swi-prolog failure-slice

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

为了举例,我们假设程序看起来像这样。

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退出。我怎样才能看到第一个(最早的,最深的)失败的目标?甚至更好,整个回溯?我假设我应该可以使用debugleash,例如:

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

......但是我没有尝试过任何有用的东西。

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

动机

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

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

因此,鉴于上面的示例,如果某个用例导致do_stuff/1失败,并且程序以非零代码退出,那么程序员在确定哪个谓词失败时会有哪些选项?< / p>

answer linked in the comments提供了一个解决方案。但是(如果我理解正确的话)这确实需要程序员系统地检查执行流程,直到找到违规的谓词调用。

这正是我希望避免的。

1 个答案:

答案 0 :(得分:3)

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

后来,MirelleDucassé特别致力于自动弄清楚如何解释失败。

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

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

注解

在许多程序中,我使用(@)/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.

该技术在纯粹的关系式声明性代码中迅速失去吸引力。获取recent example。在那里,实际上不可能添加@ - 除了最初的目标。在这种情况下,需要采用更具声明性的方法,如下所示。

推广

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

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