如果我想确保两个变量没有实例化到同一个术语,那么首选方法是什么?
假设我需要在图中找到有向边,并且节点不能有自己的边:
node(a, x, y). node(b, z, x). node(c, y, y).
(这里的边是 - > c,b - > a,但是不是 c - > c)
以下作品:
edge(A, B) :- node(A, _, X), node(B, X, _), A \== B.
这也有效[swi-prolog]:
edge(A, B) :- dif(A, B), node(A, _, X), node(B, X, _).
这显然不起作用(因为A和B都没有实例化?):
edge(A, B) :- A \== B, node(A, _, X), node(B, X, _).
我想第一个解决方案的问题在于,使用更复杂的node
谓词,在edge
失败之前可能会发生许多不必要的统一。另一方面,dif
位于一个库中,这表明它并不适用于这么简单的情况(虽然它具有我似乎正在寻找的确切功能)。
答案 0 :(得分:14)
仅出于优雅和教学原因,dif/2
在这里以及绝大多数其他案例中显然更为可取,因为正如您已经注意到“可能会发生许多不必要的统一”,也因为{与dif/2
相比,{1}}是一个纯粹的,很好的声明性谓词,可以在子句体的所有方向和任何地方使用,而不会改变程序的含义。 (\==)/2
也是SWI-Prolog中的自动加载谓词,这意味着您需要不明确导入任何库以使用它,并且dif/2
可用于任何内置谓词。
如果您使用dif/2
,您可以更轻松地了解您的代码。例如,在您的情况下,您从:
edge(A, B) :- node(A, _, X), node(B, X, _), dif(A, B).
然后,正如您所知dif/2
是完全纯粹的谓词,您知道您也可以将其写为:
edge(A, B) :- dif(A, B), node(A, _, X), node(B, X, _).
此外,由于您知道dif/2
始终终止,因此您知道此更改最多可以改进程序的终止属性。
与所有约束一样,dif/2
意味着使用。我强烈推荐它而不是不可交换的不纯的谓词。
如果您担心性能,这里只是一个小比较,只是在一个用例中将dif/2
与非声明性dif/2
进行比较,其中两个谓词可以互换使用:
?- N = 1_000_000, time((between(1,N,_),dif(a,b),false)). % 11,000,005 inferences, 0.352 CPU in 0.353 seconds (100% CPU, 31281029 Lips) ?- N = 1_000_000, time((between(1,N,_),a\==b,false)). %@ % 3,000,001 inferences, 0.107 CPU in 0.107 seconds (99% CPU, 28167437 Lips)
因此,使用(\==)/2
时,有时会带来性能优势。但是,使用这种低级谓词时也存在更严重的缺点:它更难理解,更容易出错,而且没有声明性。
因此,我建议您只使用(\==)/2
表示两个术语不同。
答案 1 :(得分:8)
查询是元解释的,开销可能超过dif(X,Y)
和X\==Y
的差异。您应该比较这两个谓词:
t1:-
1000=I,
time(t1(I)).
t1(I):-
dif(X,Y),
between(1,I,X),
between(1,I,Y),
false.
t2:-
1000=I,
time(t2(I)).
t2(I):-
between(1,I,X),
between(1,I,Y),
X\==Y,
false.
在B-Prolog上,我得到了以下结果:
| ?- cl(t)
Compiling::t.pl
compiled in 0 milliseconds
loading::t.out
yes
| ?- t1
CPU time 0.14 seconds.
no
| ?- t2
CPU time 0.078 seconds.
no
| ?- 1000=I,time(( dif(X,Y), between(1,I,X), between(1,I,Y), false )).
CPU time 0.234 seconds.
no
| ?- 1000=I,time(( between(1,I,X), between(1,I,Y), X \== Y, false )).
CPU time 0.218 seconds.
答案 2 :(得分:7)
首先,当两个参数都是基础时,dif/2
和(\==)/2
的含义相同,即变量空闲。因此,如果您可以确保参数可以被接地 - 或者更充分地实例化,以便进一步的实例化不会影响(\==)/2
的结果 - 那么它就没有区别。
在您的示例中,我们需要确定node/3
的答案始终包含第一个参数。在这种情况下,(\==)/2
程序没问题。在极少数情况下,它的效率可能低于dif/2
版本。想一想目标edge(X, X)
。
在许多情况下,(\==)/2
甚至(\=)/2
的效率要高得多。另一方面,与正确性相比,效率有多重要?
另一种看待这种情况的方法是将(\==)/2
和(\=)/2
视为两方的近似值:只有两者都同意,我们才能获得安全的最终结果。
历史上,dif/2
是最古老的内置谓词之一。它存在于第一个Prolog系统中,它有时被称为Prolog 0,以区别于下一个版本,它通常被认为是第一个Prolog - 马赛Prolog - Prolog 1. Prolog 1不再有dif/2
Prolog来到爱丁堡正是这种形状。此外,dif/2
不是ISO标准(当前)的一部分,因为它需要一些类似于coroutining的机制。许多(相当较老的)Prolog系统没有这样的机制。然而,即使在ISO Prolog中,人们也可以做得更好:
iso_dif(X, Y) :-
X == Y,
!,
fail.
iso_dif(X, Y) :-
X \= Y,
!.
iso_dif(X, Y) :-
throw(error(instantiation_error,iso_dif/2)).
(这是another, probably more efficient implementation)
请注意有问题的案例如何被一个停止整个计算的错误所覆盖。
支持dif/2
开箱即用的当前Prolog系统是B,SICStus,SWI,YAP。它位于IF,Ciao,XSB的库中。
另见this answer。
为了支持我对开销的主张,这是在同一台机器上的各种Prolog中的测试。在SWI中,存在10倍的开销,在B中,没有开销。正如@nfz所指出的,编译时数字略有不同。所以你的里程可能会有所不同。
SWI 6.3.4-55
?- 1000=I,time(( dif(X,Y), between(1,I,X), between(1,I,Y), false )). % 22,999,020 inferences, 5.162 CPU in 5.192 seconds (99% CPU, 4455477 Lips) false. ?- 1000=I,time(( between(1,I,X), between(1,I,Y), X \== Y, false )). % 2,000,001 inferences, 0.511 CPU in 0.521 seconds (98% CPU, 3912566 Lips) false.
B 7.8
| ?- 1000=I,time(( dif(X,Y), between(1,I,X), between(1,I,Y), false )). CPU time 0.364 seconds. no | ?- 1000=I,time(( between(1,I,X), between(1,I,Y), X \== Y, false )). CPU time 0.356 seconds. no
YAP
?- 1000=I,time(( dif(X,Y), between(1,I,X), between(1,I,Y), false )). % 2.528 CPU in 2.566 seconds ( 98% CPU) no ?- 1000=I,time(( between(1,I,X), between(1,I,Y), X \== Y, false )). % 0.929 CPU in 0.963 seconds ( 96% CPU) no