Реификация термина равенство / неравенство

Программы на чистом Prolog, которые четко различают равенство и неравенство терминов, страдают от неэффективности выполнения; даже когда все относящиеся к делу термины заземлены.

Недавний пример SO - это этот ответ. Все ответы и все ошибки верны в этом определении. Учитывать:

?- Es = [E1,E2], occurrences(E, Es, Fs).
Es = Fs, Fs = [E, E],
E1 = E2, E2 = E ;
Es = [E, E2],
E1 = E,
Fs = [E],
dif(E, E2) ;
Es = [E1, E],
E2 = E,
Fs = [E],
dif(E, E1) ;
Es = [E1, E2],
Fs = [],
dif(E, E1),
dif(E, E2).

Хотя программа безупречна с декларативной точки зрения, ее прямое выполнение в существующих системах, таких как B, SICStus, SWI, YAP, излишне неэффективно. Для следующей цели точка выбора остается открытой для каждого элемента списка.

?- occurrences(a,[a,a,a,a,a],M).
M = [a, a, a, a, a] ;
false.

Это можно увидеть, используя достаточно большой список a следующим образом. Возможно, вам придется адаптировать I так, чтобы список все еще можно было представить; в SWI это означало бы, что

1mo ​​I должен быть достаточно маленьким, чтобы предотвратить ошибку ресурса для глобального стека, как показано ниже:

?- 24=I,N is 2^I,length(L,N), maplist(=(a),L).
ERROR: Out of global stack

2должен ли I быть достаточно большим, чтобы вызвать ошибку ресурса для локального стека:

?- 22=I,N is 2^I,length(L,N), maplist(=(a),L), ( Length=ok ; occurrences(a,L,M) ).
I = 22,
N = 4194304,
L = [a, a, a, a, a, a, a, a, a|...],
Length = ok ;
ERROR: Out of local stack

Чтобы преодолеть эту проблему и при этом сохранить прекрасные декларативные свойства, необходим некоторый предикат сравнения.


Как следует определить этот предикат сравнения?

Вот такое возможное определение:

equality_reified(X, X, true).
equality_reified(X, Y, false) :-
   dif(X, Y).

Изменить: возможно, порядок аргументов должен быть изменен на противоположный, аналогично встроенному в ISO compare/3 (ссылка только на черновик).

Его эффективная реализация сначала обрабатывает быстрые детерминированные случаи:

equality_reified(X, Y, R) :- X == Y, !, R = true.
equality_reified(X, Y, R) :- ?=(X, Y), !, R = false. % syntactically different
equality_reified(X, Y, R) :- X \= Y, !, R = false. % semantically different
equality_reified(X, X, true).
equality_reified(X, Y, false) :-
   dif(X, Y).

Изменить: мне не ясно, является ли X \= Y подходящим охранником при наличии ограничений. Без ограничений ?=(X, Y) или X \= Y одинаковы.


Пример

Как предлагает @ user1638891, вот пример того, как можно использовать такой примитив. Исходный код mats был:

occurrences_mats(_, [], []).
occurrences_mats(X, [X|Ls], [X|Rest]) :-
   occurrences_mats(X, Ls, Rest).
occurrences_mats(X, [L|Ls], Rest) :-
   dif(X, L),
   occurrences_mats(X, Ls, Rest).

Это можно переписать примерно так:

occurrences(_, [], []).
occurrences(E, [X|Xs], Ys0) :-
   reified_equality(Bool, E, X),
   ( Bool == true -> Ys0 = [X|Ys] ; Ys0 = Ys ),
   % ( Bool = true, Ys0 = [X|Ys] ; Bool = true, Ys0 = Ys ),
   occurrences(E, Xs, Ys).

reified_equality(R, X, Y) :- X == Y, !, R = true.
reified_equality(R, X, Y) :- ?=(X, Y), !, R = false.
reified_equality(true, X, X).
reified_equality(false, X, Y) :-
   dif(X, Y).

Обратите внимание, что индексирование второго аргумента SWI активируется только после, когда вы вводите запрос типа occurrences(_,[],_). Кроме того, SWI нуждается в немонотонном по своей сути if-then-else, поскольку он не индексирует (;)/2 дизъюнкцию. SICStus делает это, но имеет только индексирование первого аргумента. Таким образом, остается одна (1) точка выбора открытой (в конце с []).


person false    schedule 01.12.2012    source источник
comment
Это действительно интересный вопрос. Не могли бы вы добавить код из упомянутого вопроса SO или еще лучше добавить еще более очевидный случай?   -  person NotAUser    schedule 11.12.2012
comment
@ user1638891: выполните поиск по пролог-диф - и, пожалуйста, поменяйте теги, если вы найдете другие примеры, которые я пропустил.   -  person false    schedule 11.12.2012
comment
@false, @mat, @repeat Все реализации occurrences/3, которые были представлены к этой дате, кроме моей вчерашней, входят в бесконечный цикл, когда у запроса есть первые два аргумента свободными, а третий - это список с разными основными элементами, правильное поведение будучи, конечно, потерпеть неудачу.   -  person migfilg    schedule 19.04.2015
comment
@repeat Добро пожаловать. И спасибо за то, что восприняли мое предупреждение так конструктивно, как и предполагалось.   -  person migfilg    schedule 20.04.2015


Ответы (6)


Во-первых, имя должно быть более декларативным, например equality_truth/2.

person mat    schedule 02.12.2012
comment
Я имею в виду, что вместо equality_reified/2 его следует называть более декларативно, например equality_truth/2 или, может быть, даже equality_true/2 (поскольку false как второй аргумент вряд ли можно назвать истиной). Reified подразумевает, что кто-то сделал что-то, что является более императивным, чем декларативным. Мы должны выбрать имя с оттенком, который что-то содержит. - person mat; 04.12.2012

Следующий код основан на if_/3 и (=)/3 (также известный как equal_truth/3), как реализовано с помощью @false в Prolog union для AUBUC:

=(X, Y, R) :- X == Y,    !, R = true.
=(X, Y, R) :- ?=(X, Y),  !, R = false. % syntactically different
=(X, Y, R) :- X \= Y,    !, R = false. % semantically different
=(X, Y, R) :- R == true, !, X = Y.
=(X, X, true).
=(X, Y, false) :-
   dif(X, Y).

if_(C_1, Then_0, Else_0) :-
   call(C_1, Truth),
   functor(Truth,_,0),  % safety check
   ( Truth == true -> Then_0 ; Truth == false, Else_0 ).

По сравнению с occurrences/3, вспомогательный occurrences_aux/3 использует другой порядок аргументов, который передает список Es в качестве первого аргумента, что может разрешить индексацию по первому аргументу:

occurrences_aux([], _, []).
occurrences_aux([X|Xs], E, Ys0) :-
   if_(E = X, Ys0 = [X|Ys], Ys0 = Ys),
   occurrences_aux(Xs, E, Ys).

Как указывает @migfilg, цель Fs=[1,2], occurrences_aux(Es,E,Fs) должна потерпеть неудачу, поскольку она логически неверна: occurrences_aux(_,E,Fs) утверждает, что все элементы в Fs равны E. Однако сам по себе occurrences_aux/3 не завершается в подобных случаях.

Мы можем использовать вспомогательный предикат allEqual_to__lazy/2 для улучшения поведения завершения:

allEqual_to__lazy(Xs,E) :-
   freeze(Xs, allEqual_to__lazy_aux(Xs,E)).

allEqual_to__lazy_aux([],_).
allEqual_to__lazy_aux([E|Es],E) :-
   allEqual_to__lazy(Es,E).

Установив все вспомогательные предикаты, давайте определим occurrences/3:

occurrences(E,Es,Fs) :-
   allEqual_to__lazy(Fs,E),    % enforce redundant equality constraint lazily
   occurrences_aux(Es,E,Fs).   % flip args to enable first argument indexing

У меня есть вопросы:

?- occurrences(E,Es,Fs).       % first, the most general query
Es = Fs, Fs = []        ;
Es = Fs, Fs = [E]       ;
Es = Fs, Fs = [E,E]     ;
Es = Fs, Fs = [E,E,E]   ;
Es = Fs, Fs = [E,E,E,E] ...    % will never terminate universally, but ...
                               % that's ok: solution set size is infinite

?- Fs = [1,2], occurrences(E,Es,Fs).
false.                         % terminates thanks to allEqual_to__lazy/2

?- occurrences(E,[1,2,3,1,2,3,1],Fs).
Fs = [1,1,1],     E=1                     ;
Fs = [2,2],                 E=2           ;
Fs = [3,3],                           E=3 ;
Fs = [],      dif(E,1), dif(E,2), dif(E,3).

?- occurrences(1,[1,2,3,1,2,3,1],Fs).
Fs = [1,1,1].                  % succeeds deterministically

?- Es = [E1,E2], occurrences(E,Es,Fs).
Es = [E,  E], Fs = [E,E],     E1=E ,     E2=E  ;
Es = [E, E2], Fs = [E],       E1=E , dif(E2,E) ;
Es = [E1, E], Fs = [E],   dif(E1,E),     E2=E  ;
Es = [E1,E2], Fs = [],    dif(E1,E), dif(E2,E).

?- occurrences(1,[E1,1,2,1,E2],Fs).
    E1=1 ,     E2=1 , Fs = [1,1,1,1] ;
    E1=1 , dif(E2,1), Fs = [1,1,1]   ;
dif(E1,1),     E2=1 , Fs = [1,1,1]   ;
dif(E1,1), dif(E2,1), Fs = [1,1].

Изменить 2015-04-27

Еще несколько запросов для проверки, завершается ли occurrences/3 универсал в определенных случаях:

?-           occurrences(1,L,[1,2]).
false. 
?- L = [_|_],occurrences(1,L,[1,2]).
false.
?- L = [X|X],occurrences(1,L,[1,2]).
false.
?- L = [L|L],occurrences(1,L,[1,2]).
false.
person repeat    schedule 17.04.2015
comment
По сравнению с кодом, на который ссылается ЗП, меняется порядок аргументов, который следует четко указать в ответе. Таким образом, эта реализация использует стратегию, описанную в моих предыдущих ответах, с использованием индексации 2-го аргумента исходного предиката, чтобы избежать некоторых точек выбора. Не могли бы вы указать мне, где находятся определения if_/3 и =/3? - person migfilg; 18.04.2015
comment
Извините, я пропустил вашу вторую строчку; Возможно, вам стоило отдать должное идее ... Спасибо за ссылку. Что касается =/3, я не вижу, где вы его используете: он находится внутри вашей копии if_/3? - person migfilg; 18.04.2015
comment
Узнал как используется =/3. - person migfilg; 18.04.2015

Кажется, лучше всего вызывать этот предикат с теми же аргументами (=)/3. Таким образом, условия типа if_/3 теперь гораздо более читабельны. И если использовать суффикс _t вместо _truth:

memberd_t(_X, [], false).
memberd_t(X, [Y|Ys], Truth) :-
   if_( X = Y, Truth=true, memberd_t(X, Ys, Truth) ).

Что раньше было:

memberd_truth(_X, [], false).
memberd_truth(X, [Y|Ys], Truth) :-
   if_( equal_truth(X, Y), Truth=true, memberd_truth(X, Ys, Truth) ).
person false    schedule 13.04.2015

ОБНОВЛЕНИЕ: этот ответ был заменен моим от 18 апреля. Я не предлагаю удалять его из-за комментариев ниже.

Мой предыдущий ответ был неправильным. Следующий работает против тестового примера в вопросе, и реализация имеет желаемую особенность, позволяющую избежать лишних точек выбора. Я предполагаю, что верхний режим предиката будет?, + ,? хотя другие режимы могут быть легко реализованы.

Всего в программе 4 предложения: выполняется посещение списка во втором аргументе, и для каждого члена есть две возможности: он либо объединяется с 1-м аргументом верхнего предиката, либо отличается от него, и в этом случае применяется ограничение dif:

occurrences(X, L, Os) :- occs(L, X, Os).

occs([],_,[]).
occs([X|R], X, [X|ROs]) :- occs(R, X, ROs).
occs([X|R], Y, ROs) :- dif(Y, X), occs(R, Y, ROs).

Примеры запусков с использованием YAP:

?- occurrences(1,[E1,1,2,1,E2],Fs).
E1 = E2 = 1,
Fs = [1,1,1,1] ? ;
E1 = 1,
Fs = [1,1,1],
dif(1,E2) ? ;
E2 = 1,
Fs = [1,1,1],
dif(1,E1) ? ;
Fs = [1,1],
dif(1,E1),
dif(1,E2) ? ;
no  

?- occurrences(E,[E1,E2],Fs).
E = E1 = E2,
Fs = [E,E] ? ;
E = E1,
Fs = [E],
dif(E,E2) ? ;
E = E2,
Fs = [E],
dif(E,E1) ? ;
Fs = [],
dif(E,E1),
dif(E,E2) ? ;
no
person migfilg    schedule 15.04.2015

Вот еще более короткая логически чистая реализация occurrences/3.

Мы строим его на основе мета-предиката tfilter/3, обобщенный предикат равенства термина (=)/3 и сопрограмма allEqual_to__lazy/2 (определенная в моем предыдущем ответе на этот вопрос):

occurrences(E,Xs,Es) :-
   allEqual_to__lazy(Es,E),
   tfilter(=(E),Xs,Es).

Готово! Чтобы облегчить сравнение, мы повторно запускаем точно такие же запросы, которые я использовал в своем предыдущем ответе:

?- Fs = [1,2], occurrences(E,Es,Fs).
false.

?- occurrences(E,[1,2,3,1,2,3,1],Fs).
Fs = [1,1,1],     E=1                     ;
Fs = [2,2],                 E=2           ;
Fs = [3,3],                           E=3 ;
Fs = [],      dif(E,1), dif(E,2), dif(E,3).

?- occurrences(1,[1,2,3,1,2,3,1],Fs).
Fs = [1,1,1].

?- Es = [E1,E2], occurrences(E,Es,Fs).
Es = [E, E ], Fs = [E,E],     E1=E ,     E2=E  ;
Es = [E, E2], Fs = [E],       E1=E , dif(E2,E) ;
Es = [E1,E ], Fs = [E],   dif(E1,E),     E2=E  ;
Es = [E1,E2], Fs = [],    dif(E1,E), dif(E2,E).

?- occurrences(1,[E1,1,2,1,E2],Fs).
    E1=1 ,     E2=1 , Fs = [1,1,1,1] ;
    E1=1 , dif(E2,1), Fs = [1,1,1]   ;
dif(E1,1),     E2=1 , Fs = [1,1,1]   ;
dif(E1,1), dif(E2,1), Fs = [1,1].

?- occurrences(1,L,[1,2]).
false.

?- L = [_|_],occurrences(1,L,[1,2]).
false.

?- L = [X|X],occurrences(1,L,[1,2]).
false.

?- L = [L|L],occurrences(1,L,[1,2]).
false.

Наконец, самый общий вопрос:

?- occurrences(E,Es,Fs).
Es = Fs, Fs = []      ;
Es = Fs, Fs = [E]     ;
Es = Fs, Fs = [E,E]   ;
Es = Fs, Fs = [E,E,E] % ... and so on ad infinitum ...

Мы получаем одинаковые ответы.

person repeat    schedule 16.06.2015

Реализация occurrences/3 ниже основана на моих предыдущих ответах, а именно за счет использования механизма индексации предложений по 1-му аргументу, чтобы избежать некоторых точек выбора, и решает все проблемы, которые были подняты.

Более того, он справляется с проблемой во всех представленных реализациях до сих пор, включая ту, о которой идет речь в вопросе, а именно, что все они входят в бесконечный цикл, когда у запроса есть 2 первых аргумента свободными, а 3-й - список с разными базовыми элементами. Разумеется, правильное поведение - потерпеть неудачу.

Использование предиката сравнения

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

Реализация

В этом порядке рассматриваются три исключительных случая: если 2-й аргумент заземлен, то он посещается рекурсивно; в противном случае, если 3-й аргумент является заземленным, он проверяется и затем посещается рекурсивно; в противном случае для 2-го и 3-го аргументов создаются подходящие списки.

occurrences(X, L, Os) :-
  ( nonvar(L) -> occs(L, X, Os) ;
    ( nonvar(Os) -> check(Os, X), glist(Os, X, L) ; v_occs(L, X, Os) ) ).

Второй аргумент «посещение земли» имеет три случая, когда список не пуст: если его заголовок и X выше являются наземными и унифицируемыми, X находится в заголовке результирующего списка вхождений и другой альтернативы нет; в противном случае есть две альтернативы, когда X отличается от головы или объединяется с ней:

occs([],_,[]).
occs([X|R], Y, ROs) :-
  ( X==Y -> ROs=[X|Rr] ; foccs(X, Y, ROs, Rr) ), occs(R, Y, Rr).

foccs(X, Y, ROs, ROs) :- dif(X, Y).
foccs(X, X, [X|ROs], ROs).

Проверка основного третьего аргумента состоит в том, чтобы убедиться, что все его члены объединены с X. В принципе, эту проверку может выполнить glist/3, но таким образом можно избежать неиспользованных точек выбора.

check([], _).
check([X|R], X) :- check(R, X).

Посещение третьего аргумента земли со свободным вторым аргументом должно завершаться добавлением переменных, отличных от X, в сгенерированный список. На каждом шаге рекурсии есть две альтернативы: текущий заголовок сгенерированного списка - это текущий заголовок посещенного списка, который должен быть унифицированным с X или являться свободной переменной, отличной от X. Это чисто теоретическое описание, потому что на самом деле существует бесконечное количество решений, и третье предложение никогда не будет достигнуто, если заголовок списка является переменной. Поэтому третий пункт ниже закомментирован, чтобы избежать ненужных точек выбора.

glist([], X, L) :- gdlist(L,X).
glist([X|R], X, [X|Rr]) :- glist(R, X, Rr).
%% glist([X|R], Y, [Y|Rr]) :- dif(X, Y), glist([X|R], Y, Rr).

gdlist([], _).
gdlist([Y|R], X) :- dif(X, Y), gdlist(R, X).

Наконец, случай, когда все аргументы свободны, рассматривается аналогично предыдущему случаю и имеет аналогичную проблему с некоторыми шаблонами решений, которые на практике не генерируются:

v_occs([], _, []).
v_occs([X|R], X, [X|ROs]) :- v_occs(R, X, ROs).
%% v_occs([X|R], Y, ROs) :- dif(Y, X), v_occs(R, Y, ROs). % never used

Образцы тестов

?- occurrences(1,[E1,1,2,1,E2],Fs).
Fs = [1,1],
dif(E1,1),
dif(E2,1) ? ;
E2 = 1,
Fs = [1,1,1],
dif(E1,1) ? ;
E1 = 1,
Fs = [1,1,1],
dif(E2,1) ? ;
E1 = E2 = 1,
Fs = [1,1,1,1] ? ;
no

?- occurrences(1,L,[1,2]).
no

?- occurrences(1,L,[1,E,1]).
E = 1,
L = [1,1,1] ? ;
E = 1,
L = [1,1,1,_A],
dif(1,_A) ? ;
E = 1,
L = [1,1,1,_A,_B],
dif(1,_A),
dif(1,_B) ? ;
   ...
person migfilg    schedule 18.04.2015
comment
Я аплодирую тому, что occurrences(1,L,[1,2]). завершается (и правильно не работает), однако почему тогда цикл специализации L = [_|_], occurrences(1,L,[1,2]).? Проблема действительно в анализе случая. Есть еще стилистический аспект: вы смешиваете логический и процедурный аспекты. Некоторые другие подходы пытаются сконцентрировать нечистый код в некоторых обычно применимых чистых предикатах (if_/3, (=)/3) - так, чтобы фактический код оставался чистым. - person false; 19.04.2015
comment
Готов поспорить (у меня нет времени проверить это), что все реализации будут зацикливаться, если указанные аргументы списка не являются свободными или правильно закрытыми списками, что правильно, потому что list без какой-либо квалификации в Prolog означает закрытый список. Поэтому неудивительно, что указанная вами специализация вызовет цикл. Что касается стилистического аспекта, он открыт для обсуждения, тем более, когда ваш вопрос ставит проблему неэффективности на уровне реализации. На мой взгляд, чистота не означает декларативность, и когда на карту поставлена ​​эффективность, я предпочитаю писать программу на Прологе, основанную на том, что такое Пролог. - person migfilg; 20.04.2015
comment
Принято считать, что завершение также является монотонным, чего нельзя сказать о вашем случае. То есть обычно предполагается, что если Q завершается, то также L = [_|_], Q завершается. - person false; 20.04.2015
comment
Очень интересно. Тогда, возможно, вы захотите попробовать другие реализации, представленные здесь, с этим запросом L=[X|X], Q, который, похоже, вы ожидаете завершить во всех из них, кроме моего. Поскольку эта дискуссия ни к чему не приведет, я просто ухожу здесь: удачного дня. - person migfilg; 22.04.2015
comment
Я не получил вашего комментария. Для всех других реализаций верно следующее: если Q завершается, то L = [_|_], Q прекращается. - person false; 22.04.2015
comment
Я предполагал, что вы поймете мою точку зрения: если аргументы могут быть любыми, тогда описание предиката неверно, поскольку в нем говорится, что последние 2 являются списками (= правильно закрытые списки). Могу вас заверить, что иначе я бы избегал случая, который вы упомянули в моей программе. Но если можно использовать какой-либо термин, чем я предлагал вам протестировать с циклическим термином, и, возможно, вы поймете, что для избавления от него потребуется больше нечистого кода в других реализациях. И со своей стороны это все, что я могу сказать. - person migfilg; 23.04.2015