Используя\==/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