我最近一直在做一些Prolog。我读过Prolog的艺术书。他们在那里有一个Nim游戏实现。所以我把它重写为SWI-Prolog,除了本地堆栈错误之外,一切似乎都很好。 经过调试后,我发现它似乎在程序的这一部分永远循环:
nim_sum([N|Ns],Bs,Sum):-
binary(N,Ds), nim_add(Ds,Bs,Bs1), nim_sum(Ns,Bs1,Sum).
nim_sum([],Sum,Sum).
nim_add(Bs,[],Bs).
nim_add([],Bs,Bs).
nim_add([B|Bs],[C|Cs],[D|Ds]):-
D is (B+C) mod 2, nim_add(Bs,Cs,Ds).
有人遇到过这种问题吗?对于某些替代实施的任何建议?
答案 0 :(得分:2)
为避免“堆栈外”问题,通常需要以“最后调用优化”或“尾递归”形式编写递归谓词。
这里似乎应该颠倒 nim_sum / 3 的两个子句(首先放置“fact”子句,这是终止条件)。然后,调用 nim_sum / 3 会在具有正文的子句中自行创建,而不会打开任何回溯点(假设二进制/ 2 和 nim_add / 3 是确定性的。)
尝试交换 nim_sum 的这两个子句,并告诉我们它是如何工作的。
已添加:在进一步思考 nim_add / 3 之后,我怀疑Prolog引擎可能无法检测到它是确定性的,即仅以单向成功。这是削减工作!运营商。最简单的解决方案是在 nim_sum / 3 调用自身的位置前面添加一个剪切,这样在递归调用时肯定没有打开回溯点。然而,这更像是Prolog的“精神”:
nim_sum([],Sum,Sum).
nim_sum([N|Ns],Bs,Sum):-
binary(N,Ds),
nim_add(Ds,Bs,Bs1),
nim_sum(Ns,Bs1,Sum).
nim_add(Bs,[],Bs) :- !.
nim_add([],Bs,Bs) :- !.
nim_add([B|Bs],[C|Cs],[D|Ds]):-
D is (B+C) mod 2,
nim_add(Bs,Cs,Ds).
这又假设二进制/ 2 是确定性的,可能是将整数(非负?)转换为0和1的最低有效位列表。