40

Pure Prolog programs that distinguish between the equality and inequality of terms in a clean manner suffer from execution inefficiencies ; even when all terms of relevance are ground.

A recent example on SO is this answer. All answers and all failures are correct in this definition. Consider:

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

While the program is flawless from a declarative viewpoint, its direct execution on current systems like B, SICStus, SWI, YAP is unnecessarily inefficient. For the following goal, a choicepoint is left open for each element of the list.

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

This can be observed by using a sufficiently large list of as as follows. You might need to adapt the I such that the list can still be represented ; in SWI this would mean that

1mo the I must be small enough to prevent a resource error for the global stack like the following:

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

2do the I must be large enough to provoke a resource error for the local stack:

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

To overcome this problem and still retain the nice declarative properties some comparison predicate is needed.


How should this comparison predicate be defined?

Here is such a possible definition:

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

Edit: Maybe the argument order should be reversed similar to the ISO built-in compare/3 (link links to draft only).

An efficient implementation of it would handle the fast determinate cases first:

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).

Edit: it is not clear to me whether or not X \= Y is a suitable guard in the presence of constraints. Without constraints, ?=(X, Y) or X \= Y are the same.


Example

As suggested by @user1638891, here is an example how one might use such a primitive. The original code by mats was:

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).

Which can be rewritten to something like:

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).

Please note that SWI's second-argument indexing is only activated, after you enter a query like occurrences(_,[],_). Also, SWI need the inherently nonmonotonic if-then-else, since it does not index on (;)/2 – disjunction. SICStus does so, but has only first argument indexing. So it leaves one (1) choice-point open (at the end with []).

4

6 回答 6

9

一方面,名称应该更具声明性,例如equality_truth/2.

于 2012-12-02T01:06:32.287 回答
8

以下代码基于if_/3and (=)/3(aka equal_truth/3),由 @false 在Prolog union for 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.
于 2015-04-17T20:22:16.313 回答
5

似乎最好用相同的参数调用这个谓词(=)/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) ).
于 2015-04-13T20:45:40.797 回答
4

更新:这个答案已被我的 4 月 18 日取代。由于以下评论,我不建议将其删除。

我之前的回答是错误的。以下是针对问题中的测试用例运行的,并且该实现具有避免多余选择点的所需功能。我假设顶级谓词模式是 ?,+,? 尽管可以轻松实现其他模式。

该程序总共有 4 个子句:访问第二个参数中的列表,并且对于每个成员有两种可能性:它要么与顶部谓词的第一个参数统一,要么与它不同,在这种情况下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
于 2015-04-15T16:58:23.843 回答
4

这是一个更短的逻辑纯实现occurrences/3.

我们将它建立在 tfilter/3、具体化术语相等 predicate(=)/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 ...

我们得到相同的答案。

于 2015-06-16T01:31:53.417 回答
2

下面的实现occurrences/3是基于我之前的回答,即利用第一个参数的子句索引机制来避免一些选择点,并解决所有提出的问题。

此外,它还解决了迄今为止所有提交的实现中的一个问题,包括问题中提到的一个问题,即当查询有 2 个第一个参数免费且第三个具有不同基本元素的列表时,它们都进入无限循环。当然,正确的行为是失败。

使用比较谓词

我认为,为了避免未使用的选择点并保持良好的实现声明性,不需要像问题中提出的那样使用比较谓词,但我同意这可能是一个品味或倾向的问题。

执行

按此顺序考虑三种排他情况:如果第二个参数是接地的,则递归访问它;否则,如果第三个参数是接地的,则对其进行检查,然后递归访问;否则会为第二个和第三个参数生成合适的列表。

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

当列表不为空时,访问ground 2nd 参数有三种情况:如果它的头部和X上面都是ground 并且unifiableX是在结果列表的头部并且没有其他选择;否则有两种与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。这是仅理论上的描述,因为实际上存在无限数量的解决方案,并且当列表头是变量时,永远不会到达第 3 个子句。因此,下面的第三个子句被注释掉,以避免不可用的选择点。

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) ? ;
   ...
于 2015-04-18T14:49:14.733 回答