Prolog继承符号表示不完整的结果和无限循环

时间:2012-04-13 11:03:52

标签: prolog infinite-loop failure-slice successor-arithmetics non-termination

我开始学习Prolog,并首先了解了继承符号。

这就是我在Prolog中发现写Peano公理的地方。

请参阅PDF的第12页:

sum(0, M, M).
sum(s(N), M, s(K)) :-
    sum(N,M,K).

prod(0,M,0).
prod(s(N), M, P) :-
    prod(N,M,K),
    sum(K,M,P).

我将乘法规则放入Prolog中。然后我做查询:

?- prod(X,Y,s(s(s(s(s(s(0))))))).

这意味着基本上找到6的因子。

以下是结果。

X = s(0),
Y = s(s(s(s(s(s(0)))))) ? ;
X = s(s(0)),
Y = s(s(s(0))) ? ;
X = s(s(s(0))),
Y = s(s(0)) ? ;
infinite loop

这个结果有两个问题:

  1. 并未显示所有结果,请注意结果X = 6,Y = 1缺失。
  2. 除非我按Ctrl + C然后选择中止,否则它不会停止。
  3. 所以...我的问题是:

    1. 为什么?我尝试转换“prod”和“sum”。结果代码给了我所有结果。再说一遍,为什么?它仍然是死循环。
    2. 如何解决?
    3. 我在无限循环中阅读了另一个答案。但我很感激有人根据这个场景做出回答。这对我很有帮助。

2 个答案:

答案 0 :(得分:31)

如果您想深入研究终止属性,使用的程序是理想的研究对象:您知道先验他们应该描述的内容,因此您可以专注于更具技术性的细节。您需要了解几个概念。

通用终止

解释它的最简单方法是考虑Goal, false。如果Goal普遍终止,则终止。那就是:查看跟踪器是最无效的方式 - 它们只向您显示一条执行路径。但你需要立刻了解所有这些!当你想要普遍终止时,也永远不会看答案,它们只会分散你的注意力。你已经看到了上面的内容:你有三个简洁而正确的答案,只有你的程序循环。因此,使用false更好地“关闭”答案。这消除了所有分心。

故障片

您需要的下一个概念是failure slice。采用纯粹的单调逻辑程序并投入一些目标false。如果生成的故障片没有终止(普遍),原始程序也不会终止。在您的例子中,请考虑:

prod(0,M,0) :- false.
prod(s(N), M, P) :-
    prod(N,M,K), false,
    sum(K,M,P).

这些false目标有助于删除程序中不相关的装饰品:其余部分清楚地向您展示了prod(X,Y,s(s(s(s(s(s(0))))))).未终止的原因。它不会终止,因为该片段根本不关心P!你希望第三个参数有助于使prod/3终止,但是片段显示它都是徒劳的,因为P没有出现在任何目标中。不需要繁琐的示踪剂。

通常,找到最小的故障切片并不容易。但是一旦找到了它,就可以确定它的终止或非终止属性。一段时间后,您可以使用直觉来想象一个切片,然后您可以使用您的理由来检查该切片是否相关。

失败片段的概念是如此引人注目:如果你想改进程序,你必须修改上面片段中可见部分的程序!只要你不改变它,问题就会持续存在。因此,失败切片是您计划中非常重要的一部分。

终止推断

这是您需要的最后一件事:像cTI这样的终止推理器(或分析器)将帮助您快速识别终止条件。查看prod/3和改进的prod2/3 here的推断终止条件!


编辑:由于这是一个家庭作业问题,我还没有发布最终解决方案。但为了说清楚,这是迄今为止获得的终止条件:

    prod(A,B,C)terminates_if b(A),b(B).
    prod2(A,B,C)terminates_if b(A),b(B);b(A),b(C).

所以新的prod2/3严格要比原来的程序好!

现在,您可以找到最终的程序。终止条件是:

   prod3(A,B,C)terminates_if b(A),b(B);b(C).

首先,尝试找到prod2(A,B,s(s(s(s(s(s(0)))))))的失败切片!我们希望它终止,但它仍然没有。因此,请使用该程序并手动添加false个目标!剩下的部分会告诉你钥匙!

作为最后的提示:你需要添加一个额外的目标和一个事实。


编辑:根据请求,这是prod2(A,B,s(s(s(s(s(s(0)))))))的失败切片:

prod2(0,_,0) :- false.
prod2(s(N), M, P) :-
   sum(M, K, P),
   prod2(N,M,K), false.

sum(0, M, M).
sum(s(N), M, s(K)) :- false,
    sum(N,M,K).

请注意sum/3的明确简化定义。它只说:0加任何东西都是什么。不再。因此,即使更专业的prod2(A,0,s(s(s(s(s(s(0)))))))将循环,而prod2(0,X,Y)优雅地终止......

答案 1 :(得分:17)

第一个问题(为什么)相当容易发现,特别是如果了解left recursion。当C 绑定时,sum(A,B,C) 绑定 A和B,但原始程序prod(A,B,C)不使用该绑定,而是递归还有A,B未绑定。

如果我们交换sum,prod我们从sum获得2个有用的绑定用于递归调用:

sum(M, K, P)

现在绑定了M,并将用于终止左递归。我们可以交换N和M,因为我们知道产品是可交换的。

sum(0, M, M).
sum(s(N), M, s(K)) :-
    sum(N, M, K).

prod3(0, _, 0).
prod3(s(N), M, P) :-
    sum(M, K, P),
    prod3(M, N, K).

注意,如果我们交换M,K(即sum(K,M,P)),当用P未知调用prod3时,我们再次有一个非终止循环,但总之。

?- prod3(X,Y,s(s(s(s(s(s(0))))))).
X = s(s(s(s(s(s(0)))))),
Y = s(0) ;
X = s(s(s(0))),
Y = s(s(0)) ;
X = s(s(0)),
Y = s(s(s(0))) ;
X = s(0),
Y = s(s(s(s(s(s(0)))))) ;
false.

OT 我对cTI报告感到困惑:prod3(A,B,C)终止_if b(A),b(B); b(A),b(C)。