Используя\==/2 или dif/2

Если я хочу убедиться, что две переменные не создают экземпляр для одного и того же термина, каков предпочтительный способ сделать это?

Скажем, мне нужно найти направленные ребра в графе, а node не может иметь ребра для себя:

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 находится в библиотеке, которая предполагает, что он не предназначен для использования в таком простом случае (хотя у него есть точная функция, которую я, похоже, ищу).

Ответы

Ответ 1

Только для изящных и дидактических причин dif/2 здесь явно предпочтительнее и в подавляющем большинстве других случаев, поскольку, как вы уже отмечаете, "в противном случае может произойти много ненужных унификаций", а также потому, что dif/2 является чистым и красиво декларативным предикатом, который может использоваться во всех направлениях и в любом месте тела предложения без изменения значения программы, в отличие от (\==)/2. dif/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 с не декларативным (\==)/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 иногда возникают преимущества производительности. Однако при использовании такого низкоуровневого предиката существуют гораздо более серьезные недостатки: сложнее понять, подвергнуть ошибкам, а не декларативно.

Поэтому я рекомендую просто использовать dif/2, чтобы выразить, что два члена отличаются.

Ответ 2

Запросы метаинтерпретируются, и служебные данные могут перевесить различия 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.

Ответ 3

Прежде всего, dif/2 и (\==)/2 означают то же самое, когда оба аргумента заземлены, то есть переменная свободна. Поэтому, если вы можете гарантировать, что аргументы будут измельчены; или, скорее, достаточно инстанцируется таким образом, что дальнейшие экземпляры не повлияют на результат (\==)/2 — то это не имеет значения.

В вашем примере нам нужно будет точно знать, что ответы для node/3 всегда содержат первый аргумент. В этом случае программа (\==)/2 прекрасна. В редких случаях это может быть менее эффективно, чем версия dif/2. Подумайте о цели edge(X, X).

Во многих ситуациях (\==)/2 или даже (\=)/2 значительно эффективнее. С другой стороны, насколько важна эффективность по сравнению с правильностью?

Другой способ увидеть это - рассмотреть (\==)/2 и (\=)/2 как приближения с двух сторон: только если оба согласятся, у нас есть безопасный окончательный результат.

Исторически, dif/2 является одним из старейших встроенных предикатов. Он присутствовал в самой первой системе Prolog, которую иногда называют Prolog 0, чтобы отличить ее от следующей версии, которая часто воспринимается как первый Prolog — Marseille Prolog — Prolog 1. Prolog 1 больше не имел dif/2, и именно в этой форме Prolog пришел в Эдинбург. Кроме того, dif/2 не является частью стандарта ISO (в настоящее время), поскольку для этого требуется некоторый механизм, подобный сопроцессорам. И многие (более старые) системы 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)).

(Вот другая, возможно более эффективная реализация)

Обратите внимание, что проблемные случаи покрываются ошибкой, которая останавливает все вычисления.

Текущие системы Prolog, которые поддерживают dif/2 прямо из коробки, - это B, SICStus, SWI, YAP. Он находится в библиотеке IF, Ciao, XSB.

См. также этот ответ.


Чтобы поддержать мое требование о накладных расходах, вот тест в разных прологах на той же машине. В 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

ЯП

?- 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