我正在尝试在Prolog中实现一个findall谓词(是的,我知道它是内置的,这是一个赋值)。
编写如下:
my_findall(N,P,Pred,L) :- Pred, not(new(N,P)), !, assert(new(N,P)), my_findall(N1,P1,Pred,L1), L=[N,P,L1], retract(new(N,P)).
my_findall(_,_,_, []).
由于某种原因,它只给我第一个解决方案并停在那里,好像第二次调用my_findall失败了。根据我的理解,回溯机制应该覆盖所有可能的选项,其中应包括调用Pred(N,P)的所有选项,因此即使第二次调用在第一次尝试时失败(为Pred尝试的第一个选项已经已断言),它应该在放弃并转到my_findall((,),_,[])之前先尝试所有其他选项。
如果这不是它的工作原理,有没有办法强制这种行为而不完全重写解决方案?
答案 0 :(得分:5)
您的Pred包含未绑定的变量。在第一次迭代中调用Pred时,这些变量将绑定到第一个可能的值。在递归步骤中,Pred已经绑定了变量,并且它们无法更改值。所以...这个解决方案不起作用。
来自SWI-Prolog的追踪(出于某些原因,我不得不将new / 2重命名为item / 2):
第一级(调用:my_findall(A,B,成员(p(A,B),[p(1,2),p(3,4)]),L)。)。
Call: (7) my_findall(_G819, _G820, member(p(_G819, _G820), [p(1, 2), p(3, 4)]), _G840) ? creep
Call: (8) lists:member(p(_G819, _G820), [p(1, 2), p(3, 4)]) ? creep
Exit: (8) lists:member(p(1, 2), [p(1, 2), p(3, 4)]) ? creep
我们得到p(A,B)= p(1,2)。此时A绑定为1,B绑定为2。
^ Call: (8) not(item(1, 2)) ? creep
Call: (9) item(1, 2) ? creep
Fail: (9) item(1, 2) ? creep
^ Exit: (8) not(item(1, 2)) ? creep
好的,数据库中没有项目(1,2)。
^ Call: (8) assert(item(1, 2)) ? creep
^ Exit: (8) assert(item(1, 2)) ? creep
现在第(1,2)项是真的。递归调用:
Call: (8) my_findall(_L215, _L216, member(p(1, 2), [p(1, 2), p(3, 4)]), _L199) ? creep
让我们得到另一个解决方案:Pred:
Call: (9) lists:member(p(1, 2), [p(1, 2), p(3, 4)]) ? creep
^^^^^^^
见下划线的部分?
要使这种技术起作用,你可能应该复制Pred,递归地将N和P改为新的变量。对于每次迭代,您必须“创建”新的N和P对。检查copy_term / 2(http://www.swi-prolog.org/pldoc/doc_for?object=copy_term%2f2)。