为什么我在Prolog Fib/2中的谓词总是说;“超出本地堆栈”吗;?

为什么我在Prolog Fib/2中的谓词总是说;“超出本地堆栈”吗;?,prolog,stack,fibonacci,failure-slice,Prolog,Stack,Fibonacci,Failure Slice,我在Prolog中编写了一个谓词fib/2来计算Fibonacci数。 虽然它可以工作,但它总是显示“超出本地堆栈”,错误如下所示: ?- fib(10, F). F = 55 ; ERROR: Out of local stack 我的谓词如下: fib(0, 0). fib(1, 1). fib(N, NF) :- A is N - 1, B is N - 2, fib(A, AF), fib(B, BF), NF is AF + BF. 任何

我在Prolog中编写了一个谓词fib/2来计算Fibonacci数。 虽然它可以工作,但它总是显示“超出本地堆栈”,错误如下所示:

?- fib(10, F).
F = 55 ;
ERROR: Out of local stack
我的谓词如下:

fib(0, 0).
fib(1, 1).
fib(N, NF) :-
    A is N - 1, 
    B is N - 2,
    fib(A, AF), 
    fib(B, BF),
    NF is AF + BF.
任何人都知道这是为什么,以及如何修复它以获得以下内容:

% or the search might stop immediately, without pressing space.
?- fib2(10, F).
F = 55 ;
false. 

提前谢谢

本地堆栈外的
错误意味着程序使用了太多内存并超出了分配的空间;当程序陷入无限循环时,经常会发生这种情况。在您的情况下,以下是跟踪:

[trace] 2 ?- fib(2,M).
   Call: (6) fib(2, _G671) ? creep
^  Call: (7) _G746 is 2+ -1 ? creep
^  Exit: (7) 1 is 2+ -1 ? creep
^  Call: (7) _G749 is 2+ -2 ? creep
^  Exit: (7) 0 is 2+ -2 ? creep
   Call: (7) fib(1, _G747) ? creep
   Exit: (7) fib(1, 1) ? creep
   Call: (7) fib(0, _G747) ? creep
   Exit: (7) fib(0, 0) ? creep
^  Call: (7) _G671 is 1+0 ? creep
^  Exit: (7) 1 is 1+0 ? creep
   Exit: (6) fib(2, 1) ? creep
M = 1 ;
   Redo: (7) fib(0, _G747) ? creep
^  Call: (8) _G752 is 0+ -1 ? creep
^  Exit: (8) -1 is 0+ -1 ? creep
^  Call: (8) _G755 is 0+ -2 ? creep
^  Exit: (8) -2 is 0+ -2 ? creep
   Call: (8) fib(-1, _G753) ? creep
^  Call: (9) _G758 is -1+ -1 ? creep
^  Exit: (9) -2 is -1+ -1 ? creep
^  Call: (9) _G761 is -1+ -2 ? creep
^  Exit: (9) -3 is -1+ -2 ? creep
   Call: (9) fib(-2, _G759) ? creep
^  Call: (10) _G764 is -2+ -1 ? creep
^  Exit: (10) -3 is -2+ -1 ? creep
...
正如你所见,在发现第二个斐波那契是1(根据你的定义)之后,你要求第二个解决方案;因为您没有指定第三个子句只能在N>1时使用,所以prolog试图通过计算fib(-1)、fib(-2)、fib(-3)等来找到第二个解


要解决此问题,您必须在第三个子句中添加
N>1
或类似规则

您可能要解决的一个问题是不必要地重新计算斐波那契值。下面是对代码的一个小改动,以解决此缺陷:

:- dynamic db_fib/2.

init_fib :-
    assertz( db_fib(0, 0) ),
    assertz( db_fib(1, 1) ).

fib(N, NF) :-
    A is N - 1,
    B is N - 2,
    get_fib(A, AF),
    get_fib(B, BF),
    NF is AF + BF.

get_fib(A, F) :-
    db_fib(A, F),
    !.

get_fib(A, F) :-
    fib(A, F),
    assertz( db_fib(A, F) ).
例如,在SWI Prolog中,可以计算

?- init_fib, fib(1000,F).
非常快,没有烟囱排气

?- init_fib.
true.

?- fib(10,A).
A = 55.

?- fib(100,A).
A = 354224848179261915075.

?- fib(1000,A).
A = 43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875.

您的代码不是尾部递归的。正确的尾部递归结构意味着可以应用TRO(尾部递归优化)。这实质上是通过重用递归调用的现有堆栈框架,将递归转换为迭代。应用TRO后,每个递归调用都会在调用堆栈上推送一个新的堆栈帧。您应该像这样构造谓词(注意,我实际上没有测试这段代码,但它应该可以完成这项工作):

如果顺序是这样写的,那么最有可能的顺序(谁是第一个鸡还是第一个蛋):

fib(N, NF) :- 
  A is N - 1, 
  B is N - 2,
  fib(A, AF), 
  fib(B, BF),
  NF is AF + BF.
fib(1, 1).
fib(0, 0).

这个问题将得到解决。

您的程序没有终止的原因可以通过只考虑程序的一个片段(称为a)来最好地了解,该片段可以通过在程序中添加
目标来获得

fib(0, 0) :- false. fib(1, 1) :- false. fib(N, NF) :- A is N - 1, B is N - 2, fib(A, AF), false, fib(B, BF), NF is AF + BF. fib(0,0):-false。 小谎(1,1):-错。 fib(N,NF):- A是N-1, B是N-2, fib(A,AF),假, fib(B,BF), NF是AF+BF。 程序的所有部分对终止没有任何影响。它们可能会产生其他影响,比如当您的计划成功或失败时,但在终止时没有任何影响

要使程序终止,必须更改可见部分中的某些内容。显然,第一个参数无限制地减少

但是,故障片还意味着许多其他程序将有效地拥有相同的故障片。例如,考虑将事实放在最后(如@RicardoMojica所建议的)。这些事实可以通过
false
以同样的方式删除,从而产生相同的程序。因此:

更改条款顺序对(通用)终止没有影响


有限保修
所有这些声明仅适用于纯单调程序。不纯的非单调特征和副作用破坏了这些特性。

仍然使用
fib(1,0)
循环,但应该失败。 fib(0, 0) :- false. fib(1, 1) :- false. fib(N, NF) :- A is N - 1, B is N - 2, fib(A, AF), false, fib(B, BF), NF is AF + BF.