使用\ == / 2或dif / 2

时间:2012-12-07 05:08:49

标签: prolog prolog-dif logical-purity

如果我想确保两个变量没有实例化到同一个术语,那么首选方法是什么?

假设我需要在图中找到有向边,并且节点不能有自己的边:

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位于一个库中,这表明它并不适用于这么简单的情况(虽然它具有我似乎正在寻找的确切功能)。

3 个答案:

答案 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