为什么我们使用'!'在prolog

时间:2014-11-14 10:15:26

标签: prolog prolog-dif prolog-cut meta-predicate

这是我想要了解的代码。

co(X) :- co(X,[],L).
co([],A,A):- write(A).
co([X|Xs], A, L) :- p(X-Z,A,R), !, Z1 is Z+1, co(Xs, [X-Z1|R], L). 
co([X|Xs], A, L) :- co(Xs, [X-1|A], L). 

p(X-Y,[X-Y|R],R):- !.
p(X,[H|Y], [H|Z]) :- p(X,Y,Z).

'!'有什么用?和上面代码中的谓词p(,,)。或者任何人都可以在上面的代码的每一步添加注释,以便我能够理解。感谢。

2 个答案:

答案 0 :(得分:3)

初学者倾向于使用!/0因为他们不知道其负面后果。

这是因为在初学者中流行的大多数Prolog教科书非常糟糕,并且经常包含有关!/0的错误和误导性信息。

@false有excellent answer何时使用!/0。总结:

相反,专注于关于什么保持的声明性描述,并尝试使用纯粹和单调的方法(如约束,干净的表示,...)来使描述变得优雅和通用。

答案 1 :(得分:3)

您的计划中有很多事情需要解决。削减甚至不是主要问题。请bring me the broom

清理界面

您接下来的精确界面是什么?目前co(Xs)的目的是产生副作用。否则它对于给定列表可能成功或失败。但不仅仅是那个。然而,这种副作用根本不需要 - 并且对于大多数情况来说并不是一种有用的方法,因为这样的程序实际上是不可用的并且无视任何逻辑推理。你需要留下一个洞让一些结果潜伏在关系之外。添加另一个参数并移除write/1中的目标co/3

co(Xs, D) :-
   co(Xs, [], D).

现在,您可以单独使用顶级shell测试程序。您不需要任何线束或沙箱来检查"输出"。它就在那里,很容易在一个单独的论证中。

清理程序结构

接下来是co/3本身。在这里,最好的方法是通过分离一些问题来澄清意图,并使这些额外的论点更具意图揭示。 D代表词典。另一个好名称是KVs意义列表(复数s)的键值对。请注意不同状态的编号方式:它们以D0D1,...开头,最后有D。通过这种方式,如果您开始编写规则,则可以将D0,D置于脑中而不知道该规则中需要多少个状态。

co([], D,D).
co([X|Xs], D0,D) :-
   nn(X, D0,D1),
   co(Xs, D1,D).

nn(K, D0,D) :-
   p(K-V0,D0,D1), !,
   V is V0+1,
   D = [X-V|D1].
nn(K, D0,D) :-
   D = [K-1|D0].

p(X-Y,[X-Y|R],R):- !.
p(X,[H|Y], [H|Z]) :- p(X,Y,Z).

co/3现在更清楚地揭示了它的意图。它以某种方式将列表的元素与某些状态联系起来,即更新"对于每个元素。有一句话:这是左撇子。甚至有一个谓词:foldl/4。因此,我们可以将co/3定义为:

co(Xs, D0,D) :-
   foldl(nn, Xs, D0,D).

或者更好地完全摆脱co/3

co(Xs, D) :-
   foldl(nn, Xs, [], D).

foldl(_C_3, [], S,S).
foldl(C_3, [X|Xs], S0,S) :-
   call(C_3, X, S0,S1),
   foldl(C_3, Xs, S1,S).

请注意,到目前为止,我甚至没有触及你的任何削减,这些现在是他们的最后时刻......

卸妆多余的削减

p/3中的切入不起任何作用。无论如何,在目标p/3之后立即进行切割。然后,X-Y中不需要p/3,您可以安全地将其替换为另一个变量。简而言之,p/3现在是Prolog序言中的谓词select/3

select(E, [E|Xs], Xs).
select(E, [X|Xs], [X|Ys]) :-
   select(E, Xs, Ys).

nn(K, D0,D) :-
   select(K-V0, D0,D1), !,
   V is V0+1,
   D = [K-V|D1].
nn(K, D0,D) :-
   D = [K-1|D0].

无法轻易删除这一个剩余的剪辑:它可以保护替代子句在K-V中不会出现D时使用。但是,还有更好的方法来表达这一点。

(\+)/1

替换剪辑
nn(K, D0,D) :-
   select(K-V0, D0,D1),
   V is V0+1,
   D = [K-V|D1].
nn(K, D0,D) :-
   \+select(K-_, D0,_),
   D = [K-1|D0].

现在,每条规则都规定了自己想要的东西。这意味着,我们现在可以自由地改变这些规则的顺序。称之为迷信,但我更喜欢:

nn(K, D0,D) :-
   \+select(K-_, D0,_),
   D = [K-1|D0].
nn(K, D0,D) :-
   select(K-V0, D0,D1),
   V is V0+1,
   D = [K-V|D1].

使用dif/2

进行净化

为了使这成为一种真正的关系,我们需要摆脱这种否定。而不是说,没有解决方案,我们可以要求所有密钥(密钥是Key-Value中的第一个参数)与K不同。

nokey(_K, []).
nokey(K, [Kx-|KVs]) :-
   dif(K, Kx),
   nokey(K, KVs).

nn(K, D,[K-1|D]) :-
   nokey(K, D).
nn(K, D0,[K-V|D]) :-
   select(K-V0, D0,D),
   V is V0+1.

lambdas的帮助下,nokey(K, D)变为maplist(K+\(Kx-_)^dif(Kx,K), D)

总结一下,我们现在有:

co(Xs, D) :-
   foldl(nn, Xs, [], D).

nn(K, D,[K-1|D]) :-
   maplist(K+\(Kx-_)^dif(Kx,K), D).
nn(K, D0,[K-V|D]) :-
   select(K-V0, D0,D),
   V is V0+1.

那么这个关系是什么:第一个参数是一个列表,第二个参数是一个键值列表,每个元素和列表中出现的次数。