Prolog中列表处理的典型代码示例是append
:
append([], Ys, Ys).
append([X | Xs], Ys, [X | Zs]) :- append(Xs, Ys, Zs).
我的问题是这个程序是否是尾递归的。我想不是我在函数式语言方面的经验。但是,我觉得判断Prolog程序更加困难。我们似乎必须考虑统一。
答案 0 :(得分:8)
是的,您的(因此Prolog“标准”版本)append/3
是尾递归的。您可以轻松地看到这一点,因为最终目标是调用append/3
本身。请注意,函数式语言中append
的典型实现是 not 尾递归,因为最终调用是一个等效于Lisp中cons
的操作,例如对应于:
lisp_append([], Ys, Ys).
lisp_append([X|Xs], Ys, Zs) :-
lisp_append(Xs, Ys, Zs0),
Zs = [X|Zs0].
示例查询,产生本地堆栈溢出,因为无法应用尾调用优化:
?- length(Ls, 10_000_000), lisp_append(Ls, [], _).
ERROR: Out of local stack
而append/3
的自然Prolog版本有效:
?- length(Ls, 10_000_000), append(Ls, [], _).
Ls = [_G8, _G11, _G14, _G17, _G20, _G23, _G26, _G29, _G32|...].
请注意,由于统一的强大功能允许您在尾调用之前提取部分结果的描述,因此更多谓词在Prolog中比在函数语言中自然是尾递归的。 +1是个好问题。
答案 1 :(得分:0)
不,你的代码不是尾递归的。 尾递归意味着在递归的底部,您可以直接得到您要求的答案。
如果您跟踪代码,例如append([1,2,3],[a,b,c],Out)
,则获取:
Call:append([1, 2, 3], [a, b, c], _G4702)
Call:append([2, 3], [a, b, c], _G4756)
Call:append([3], [a, b, c], _G4759)
Call:append([], [a, b, c], _G4762)
Exit:append([], [a, b, c], [a, b, c])
Exit:append([3], [a, b, c], [3, a, b, c])
Exit:append([2, 3], [a, b, c], [2, 3, a, b, c])
Exit:append([1, 2, 3], [a, b, c], [1, 2, 3, a, b, c])
变量(_G4762,_G4759,_G4756
)的值最多传递到_G4702
,_G4702
就是答案。
我们可能有一个追尾的尾递归版本:
ap_tail_r([H|T],B,Ac,Out):-
ap_tail_r(T,B,[H|Ac],Out).
ap_tail_r([],B,[H|Ac],Out):-
ap_tail_r([],[H|B],Ac,Out).
ap_tail_r([],Out,[],Out).
再次追踪ap_tail_r([1,2,3],[a,b,c],[],Out)
:
Call:ap_tail_r([1, 2, 3], [a, b, c], [], _G4786)
Call:ap_tail_r([2, 3], [a, b, c], [1], _G4786)
Call:ap_tail_r([3], [a, b, c], [2, 1], _G4786)
Call:ap_tail_r([], [a, b, c], [3, 2, 1], _G4786)
Call:ap_tail_r([], [3, a, b, c], [2, 1], _G4786)
Call:ap_tail_r([], [2, 3, a, b, c], [1], _G4786)
Call:ap_tail_r([], [1, 2, 3, a, b, c], [], _G4786)
Exit:ap_tail_r([], [1, 2, 3, a, b, c], [], [1, 2, 3, a, b, c])
Exit:ap_tail_r([], [2, 3, a, b, c], [1], [1, 2, 3, a, b, c])
Exit:ap_tail_r([], [3, a, b, c], [2, 1], [1, 2, 3, a, b, c])
Exit:ap_tail_r([], [a, b, c], [3, 2, 1], [1, 2, 3, a, b, c])
Exit:ap_tail_r([3], [a, b, c], [2, 1], [1, 2, 3, a, b, c])
Exit:ap_tail_r([2, 3], [a, b, c], [1], [1, 2, 3, a, b, c])
Exit:ap_tail_r([1, 2, 3], [a, b, c], [], [1, 2, 3, a, b, c])
我们保留的唯一变量是_G4786
,这是我们首先要寻找的答案。
尾递归代码到底是做什么的:a。颠倒第一部分,b。将倒置的第一部分按头部顺序放到第二部分,c。当保留的第一部分为空时,更新的第二部分是附加结果。