斐波那契数-超出全局堆栈错误

时间:2019-05-24 21:08:49

标签: prolog out-of-memory infinite-loop

考虑这个简单的Prolog程序:

nat(0).
nat(s(X)) :- nat(X).
plus(X, 0, X) :- nat(X).
plus(X, s(Y), s(Z)) :- nat(X), nat(Y), nat(Z), plus(X, Y, Z).
fib(0, 0).
fib(s(0), s(0)).
fib(s(s(K)), Z) :- fib(s(K), Y), fib(K, X), plus(X, Y, Z).

我已经测试过plus,并且似乎可以正常工作。让我们看看fib是否也可以正常工作...

?- fib(0, 0).
true.

?- fib(s(0), s(0)).
true ;
false.

?- fib(s(s(0)), s(0)).
true ;
false.

?- fib(s(s(s(0))), s(s(0))).
true ;
ERROR: Out of global stack

一切顺利,直到我想打印第三个数字,恰好是2!

我知道以这种方式模拟Peano算术远非有效,而且这里使用的指数算法不是最优的,尽管如此,我拒绝相信在我只计算时就出现了性能问题。第三个数字。因此,我的程序肯定会循环,因此是错误的。

...为什么循环?使其停止循环需要什么?

1 个答案:

答案 0 :(得分:1)

简短答案:之所以循环,是因为 plus(0, s(0), X). 将产生true,然后循环。

如果我们看一下trace,我们会看到:

[trace]  ?- fib(s(s(s(0))), s(s(0))).
   Call: (8) fib(s(s(s(0))), s(s(0))) ? creep
   Call: (9) fib(s(s(0)), _902) ? creep
   Call: (10) fib(s(0), _906) ? creep
   Exit: (10) fib(s(0), s(0)) ? creep
   Call: (10) fib(0, _910) ? creep
   Exit: (10) fib(0, 0) ? creep
   Call: (10) plus(0, s(0), _912) ? creep
   ...
   Exit: (9) plus(s(0), s(0), s(s(0))) ? creep
   Exit: (8) fib(s(s(s(0))), s(s(0))) ? creep
true .

但是口译员在该谓词中回溯,旨在寻找更多解决方案。这是由于您的nat(Z)每次都给出一个下一个值,然后解释器调用plus(X, Y, Z)来检查是否匹配,但是每次失败都如此。

当我们跟踪plus(0, s(0), X)时,我们可以看到:

[trace]  ?- plus(0, s(0), X).
   Call: (8) plus(0, s(0), _676) ? creep
   Call: (9) nat(0) ? creep
   Exit: (9) nat(0) ? creep
   Call: (9) nat(0) ? creep
   Exit: (9) nat(0) ? creep
   Call: (9) nat(_884) ? creep
   Exit: (9) nat(0) ? creep
   Call: (9) plus(0, 0, 0) ? creep
   Call: (10) nat(0) ? creep
   Exit: (10) nat(0) ? creep
   Exit: (9) plus(0, 0, 0) ? creep
   Exit: (8) plus(0, s(0), s(0)) ? creep
X = s(0) ;
   Redo: (9) plus(0, 0, 0) ? creep
   Fail: (9) plus(0, 0, 0) ? creep
   Redo: (9) nat(_884) ? creep
   Call: (10) nat(_888) ? creep
   Exit: (10) nat(0) ? creep
   Exit: (9) nat(s(0)) ? creep
   Call: (9) plus(0, 0, s(0)) ? creep
   Fail: (9) plus(0, 0, s(0)) ? creep
   Redo: (10) nat(_888) ? creep

当然nat(Z)成功之后,plus(0, s(0), X)提出的答案都不会成功。

但是无论如何,没有理由调用nat(Z),因为最终递归调用将落入将验证nat(X)的基本情况下,因此我们可以像这样实现它:

plus(X, 0, X) :- nat(X).
plus(X, s(Y), s(Z)) :- plus(X, Y, Z).

first 参数上使用不同的模式通常也更有效:

plus(0, X, X) :- nat(X).
plus(s(X), Y, s(Z)) :- plus(X, Y, Z).