标准术语顺序(ISO/IEC 13211-1 7.2 术语顺序(是针对所有术语(包括变量(定义的。虽然这有很好的用途——想想setof/3
的实现,这使得 8.4 术语比较中内置的许多干净和合乎逻辑的使用成为到处都是 imps(命令式结构的缩写(的声明性噩梦。8.4 术语比较功能:
8.4 术语比较
8.4.1 (@=<(/2, (==(/2, (==(/2, (@<(/2, (@>(/2,(@>=(/2.
8.4.2 比较/3.
8.4.3 排序/2.
8.4.4 键排序/2.
举个例子,请考虑:
?- X @< a.
true.
这成功了,因为
7.2 术语顺序
排序term_precedes (3.181( 定义是否或
不是术语X
术语先于术语Y
。如果
X
和Y
是相同的术语,则X
term_precedesY
Y
term_precedesX
都是假的。如果
X
和Y
具有不同的类型:X
term_precedesY
X
类型按以下顺序
在Y
类型之前:variable
先于floating point
先于integer
先于atom
先于compound
.NOTE — 测试术语
顺序的内置谓词在 8.4 中定义。
...
因此所有变量都小于 a
.但是一旦X
被实例化:
?- X @< a, X = a.
X = a.
结果无效。
这就是问题所在。为了克服这个问题,人们可以使用约束,或者只坚持核心行为,因此产生instantiation_error
。
7.12.2 错误分类
错误根据
Error_term
的形式分类:a( 当
参数或其组件之一是变量,并且
实例化参数或组件是必需的。它有
形式instantiation_error
.
通过这种方式,我们确信只要没有实例化错误发生,结果就得到了很好的定义。
对于(==)/2
,已经有使用约束的dif/2
或产生干净实例化错误的dif_si/2
(以前称为iso_dif/2
(。
dif_si(X, Y) :-
X == Y,
( X = Y -> true
; throw(error(instantiation_error,dif_si/2))
).
那么我的问题是什么:如何在ISO Prolog中定义(和命名(相应的安全术语比较谓词? 理想情况下,没有任何明确的术语遍历。也许澄清一下:上面dif_si/2
没有使用任何明确的术语遍历。(==)/2
和(=)/2
都在内部遍历该术语,但与具有 (=..)/2
或functor/3, arg/3
的显式遍历相比,此操作的开销极低。
iso_dif/2
比比较更容易实现:
- 内置
=
运算符可用 - 您现在确切地向
=
提供哪些参数
定义
根据您的评论,安全比较意味着如果两个子项中的变量都实例化,则顺序不会更改。如果我们命名比较 lt
,例如:
-
lt(a(X), b(Y))
:始终适用于所有 X 和 Y,因为a @< b
-
lt(a(X), a(Y))
:我们不确定:intanciation_error
-
lt(a(X), a(X))
:总是失败,因为 X @
如注释中所述,如果在并行遍历两个术语时,第一个(可能(判别术语对包含以下内容,则您希望抛出错误:
- 两个不相同的变量 (
lt(X,Y)
( - 一个变量和一个不变量(
lt(X,a)
或lt(10,Y)
(
但首先,让我们回顾一下您不想使用的可能方法:
定义显式术语遍历比较函数。我知道出于性能原因,您不希望这样做,但这仍然是最直接的方法。我建议无论如何都这样做,以便您有一个参考实现来与其他方法进行比较。
使用约束进行延迟比较:我不知道如何使用ISO Prolog进行比较,但是对于例如ECLiPSe,我会暂停对一组非实例化变量的实际比较(使用
term_variables/2
(,直到没有更多的变量。以前,我还建议使用 coroutine/0 谓词,但我忽略了它不会影响@<
运算符(仅<
个(的事实。这种方法不能解决与您描述的完全相同的问题,但它非常接近。一个优点是,如果给定变量的最终值满足比较,它不会抛出异常,而
lt
在事先不知道时会抛出异常。
显式术语遍历(参考实现(
这里是lt
的显式术语遍历方法的实现,@<
的安全版本。请查看它以检查这是否是您所期望的。我可能错过了一些案例。我不确定这是否符合ISO Prolog,但如果你愿意,也可以修复。
lt(X,Y) :- X == Y,!,
fail.
lt(X,Y) :- (var(X);var(Y)),!,
throw(error(instanciation_error)).
lt(X,Y) :- atomic(X),atomic(Y),!,
X @< Y.
lt([XH|XT],[YH|YT]) :- !,
(XH == YH ->
lt(XT,YT)
; lt(XH,YH)).
lt(X,Y) :-
functor(X,_,XA),
functor(Y,_,YA),
(XA == YA ->
X =.. XL,
Y =.. YL,
lt(XL,YL)
; XA < YA).
(编辑:考虑到Tudor Berariu的评论:(i(缺少var/var错误案例,(ii(首先按arity排序;此外,修复(i(允许我删除列表的subsumes_term
。谢谢。
隐式术语遍历(不起作用(
这是我在不解构术语的情况下达到相同效果的尝试。
every([],_).
every([X|L],X) :-
every(L,X).
lt(X,Y) :-
copy_term(X,X2),
copy_term(Y,Y2),
term_variables(X2,VX),
term_variables(Y2,VY),
every(VX,1),
every(VY,0),
(X @< Y ->
(X2 @< Y2 ->
true
; throw(error(instanciation_error)))
; (X2 @< Y2 ->
throw(error(instanciation_error))
; false)).
理由
假设X @< Y
成功了。我们要检查关系是否不依赖于一些未初始化的变量。因此,我生成了X
和Y
的X2
和Y2
的相应副本,其中所有变量都是实例化的:
- 在
X2
中,变量与1统一。 - 在
Y2
中,变量与0统一。
因此,如果X2 @< Y2
关系仍然成立,我们知道我们不依赖于变量之间的标准项排序。否则,我们将抛出异常,因为这意味着以前未发生的1 @< 0
关系使关系失败。
缺点
(根据OP的评论(
lt(X+a,X+b)
应该成功,但会产生错误。乍一看,人们可能会认为将两个术语中出现的变量统一为相同的值,例如
val
,可能会解决这种情况。但是,在比较的术语中,可能会发生其他X
,这会导致错误的判断。lt(X,3)
应该会产生错误,但会成功。为了解决这种情况,应该将
X
与大于 3 的东西统一起来。在一般情况下,X
应取大于其他任何可能项1 的值。撇开实际限制不谈,@<
关系没有最大值:复合项大于非复合项,根据定义,复合项可以任意大。
因此,这种方法不是决定性的,我认为它不容易纠正。
1:请注意,对于任何给定的项,我们可以找到局部最大值和最小项,这对于问题的目的就足够了。
第三次尝试!使用 GNU Prolog 1.4.4 开发和测试。
图表"A":"尽可能简单">
lt(X,Y) :-
X == Y,
( X = Y
-> alpha_omega(Alpha,Omega),
term_variables(X+Y,Vars), % A
+ + (label_vars(Vars,Alpha,Omega), X @< Y),
( + (label_vars(Vars,Alpha,Omega), X @> Y)
-> true
; throw(error(instantiation_error,lt/2))
)
; throw(error(instantiation_error,lt/2))
).
图表"B":"无需标记所有变量">
lt(X,Y) :-
X == Y,
( X = Y
-> alpha_omega(Alpha,Omega),
term_variables(X,Xvars), % B
term_variables(Y,Yvars), % B
vars_vars_needed(Xvars,Yvars,Vars), % B
+ + (label_vars(Vars,Alpha,Omega), X @< Y),
( + (label_vars(Vars,Alpha,Omega), X @> Y)
-> true
; throw(error(instantiation_error,lt/2))
)
; throw(error(instantiation_error,lt/2))
).
vars_vars_needed([], [], []).
vars_vars_needed([A|_], [], [A]).
vars_vars_needed([], [B|_], [B]).
vars_vars_needed([A|As],[B|Bs],[A|ABs]) :-
( A == B
-> ABs = [B]
; vars_vars_needed(As,Bs,ABs)
).
一些共享代码:
alpha_omega(阿尔法,欧米茄( :- 阿尔法是 -(10.0^1000(, % 黑客! 函子(欧米茄,z,255(. %黑客!label_vars([],_,_(。label_vars([阿尔法|Vs],Alpha,Omega( :- label_vars(Vs,Alpha,Omega(.label_vars([欧米茄|Vs],Alpha,Omega( :- label_vars(Vs,Alpha,Omega(.
一个完全原创的答案,因为它建立在@coredump的答案之上。
有一种类型的查询lt/2
(执行显式术语遍历的参考实现(无法正确回答:
| ?- lt(b(b), a(a,a)).
no
| ?- @<(b(b), a(a,a)).
yes
原因是术语的标准顺序在比较函子名称之前考虑了arity。
其次,在比较变量时,lt/2
并不总是会引发instatiation_error:
| ?- lt(a(X), a(Y)).
no
我在这里写了另一个参考显式实现的候选者:
lt(X,Y):- var(X), nonvar(Y), !, throw(error(instantiation_error)).
lt(X,Y):- nonvar(X), var(Y), !, throw(error(instantiation_error)).
lt(X,Y):-
var(X),
var(Y),
( X == Y -> throw(error(instatiation_error)) ; !, false).
lt(X,Y):-
functor(X, XFunc, XArity),
functor(Y, YFunc, YArity),
(
XArity < YArity, !
;
(
XArity == YArity, !,
(
XFunc @< YFunc, !
;
XFunc == YFunc,
X =.. [_|XArgs],
Y =.. [_|YArgs],
lt_args(XArgs, YArgs)
)
)
).
lt_args([X1|OtherX], [Y1|OtherY]):-
(
lt(X1, Y1), !
;
X1 == Y1,
lt_args(OtherX, OtherY)
).
当有一对对应的参数Xi
时,谓词lt_args(Xs, Ys)
为真,Yi
使得所有前一对的lt(Xi, Yi)
和Xj == Yj
Xj
,Yj
(例如lt_args([a,X,a(X),b|_], [a,X,a(X),c|_])
为真(。
一些示例查询:
| ?- lt(a(X,Y,c(c),_Z1), a(X,Y,b(b,b),_Z2)).
yes
| ?- lt(a(X,_Y1,c(c),_Z1), a(X,_Y2,b(b,b),_Z2)).
uncaught exception: error(instatiation_error)
到底是什么!我也试一试!
lt(X,Y) :-
X == Y,
( X = Y
-> term_variables(X,Xvars),
term_variables(Y,Yvars),
list_vars_excluded(Xvars,Yvars,XonlyVars),
list_vars_excluded(Yvars,Xvars,YonlyVars),
_ = s(T_alpha),
functor(T_omega,zzzzzzzz,255), % HACK!
copy_term(t(X,Y,XonlyVars,YonlyVars),t(X1,Y1,X1onlyVars,Y1onlyVars)),
copy_term(t(X,Y,XonlyVars,YonlyVars),t(X2,Y2,X2onlyVars,Y2onlyVars)),
maplist(=(T_alpha),X1onlyVars), maplist(=(T_omega),Y1onlyVars),
maplist(=(T_omega),X2onlyVars), maplist(=(T_alpha),Y2onlyVars),
% do T_alpha and T_omega have an impact on the order?
( compare(Cmp,X1,Y1),
compare(Cmp,X2,Y2)
-> Cmp = (<) % no: demand that X @< Y holds
; throw(error(instantiation_error,lt/2))
)
; throw(error(instantiation_error,lt/2))
).
一些更辅助的东西:
listHasMember_identicalTo([X|Xs],Y) :-
( X == Y
-> true
; listHasMember_identicalTo(Xs,Y)
).
list_vars_excluded([],_,[]).
list_vars_excluded([X|Xs],Vs,Zs) :-
( listHasMember_identicalTo(Vs,X)
-> Zs = Zs0
; Zs = [X|Zs0]
),
list_vars_excluded(Xs,Vs,Zs0).
让我们做一些测试(使用GNU Prolog 1.4.4(:
?- lt(a(X,Y,c(c(,Z1(, a(X,Y,b(b,b(,Z2((.是的?- lt(a(X,Y,b(b,b(,Z1(, a(X,Y,c(c(,Z2((.不?- lt(a(X,Y1,c(c(,Z1(, a(X,Y2,b(b,b(,Z2((.未捕获的异常:错误(instantiation_error,LT/2(?- lt(a(X,Y1,b(b,b(,Z1(, a(X,Y2,c(c(,Z2((.未捕获的异常:错误(instantiation_error,LT/2(?- lt(b(b(, a(a,a((.是的?- lt(a(X(, a(Y((.未捕获的异常:错误(instantiation_error,LT/2(?- lt(X, 3(.未捕获的异常:错误(instantiation_error,LT/2(?- lt(X+a, X+b(.是的?- lt(X+a, Y+b(.未捕获的异常:错误(instantiation_error,LT/2(?- lt(a(X(, b(Y((.是的?- lt(a(X(, a(Y((.未捕获的异常:错误(instantiation_error,LT/2(?- lt(a(X(, a(X((.不
编辑 2015-05-06
更改了lt/2
的实现,以使用T_alpha
和T_omega
,而不是两个新变量。
-
lt(X,Y)
制作了两份X
(X1
和X2
(和两份Y
(Y1
和Y2
(。 X
和Y
的共享变量也由X1
和Y1
共享,以及由X2
和Y2
共享。-
T_alpha
排在所有其他术语(X1
、X2
、Y1
、Y2
(之前,即标准顺序。 -
T_omega
排在标准顺序中的所有其他术语之后。 - 在复制的术语中,
X
但不Y
(反之亦然(的变量与T_alpha
/T_omega
统一。- 如果这对术语排序有影响,我们还无法决定排序。
- 如果没有,我们就完成了。
现在,@false给出的反例有效:
?- lt(X+1,1+2).
uncaught exception: error(instantiation_error,lt/2)
?- X=2, lt(X+1,1+2).
no
以下是我认为可能是一种工作方法的草图。考虑目标lt(X, Y)
和term_variables(X, XVars), term_variables(Y, YVars)
。
该定义的目的是确定进一步的实例化是否会更改术语顺序 (7.2(。因此,我们可能希望直接找出责任变量。由于term_variables/2
以与术语顺序相关的相同方式遍历术语,因此以下情况成立:
如果存在更改术语顺序的实例化,则必须实例化以见证更改的变量位于列表前缀 XCs
、YCs
分别为 XVars
和 YVars
,并且
-
XCs
、YCs
、XVars
和YVars
相同,或者 -
XCs
和YCs
在最后一个元素之前是相同的,或者 -
XCs
和YCs
直到末尾是相同的,其中一个列表具有另一个元素,而另一个列表与其相应的变量列表XVars
或YVars
相同。
作为一个有趣的特例,如果XVars
和YVars
中的第一个元素不同,那么这些是唯一要测试相关性的变量。因此,这包括没有公共变量的情况,但它比这更通用。
下一个!这应该比我之前的尝试做得更好:
lt(X,Y) :-
X == Y,
( X = Y
-> term_variables(X,Xvars),
term_variables(Y,Yvars),
T_alpha is -(10.0^1000), % HACK!
functor(T_omega,z,255), % HACK!
copy_term(t(X,Y,Xvars,Yvars),t(X1,Y1,X1vars,Y1vars)),
copy_term(t(X,Y,Xvars,Yvars),t(X2,Y2,X2vars,Y2vars)),
copy_term(t(X,Y,Xvars,Yvars),t(X3,Y3,X3vars,Y3vars)),
copy_term(t(X,Y,Xvars,Yvars),t(X4,Y4,X4vars,Y4vars)),
maplist(=(T_alpha),X1vars), maplist(maybe_unify(T_omega),Y1vars),
maplist(=(T_omega),X2vars), maplist(maybe_unify(T_alpha),Y2vars),
maplist(=(T_omega),Y3vars), maplist(maybe_unify(T_alpha),X3vars),
maplist(=(T_alpha),Y4vars), maplist(maybe_unify(T_omega),X4vars),
% do T_alpha and T_omega have an impact on the order?
( compare(Cmp,X1,Y1),
compare(Cmp,X2,Y2),
compare(Cmp,X3,Y3),
compare(Cmp,X4,Y4),
-> Cmp = (<) % no: demand that X @< Y holds
; throw(error(instantiation_error,lt/2))
)
; throw(error(instantiation_error,lt/2))
).
辅助maybe_unify/2
处理X
和Y
中出现的变量:
maybe_unify(K,X) :-
( var(X)
-> X = K
; true
).
使用 GNU-Prolog 1.4.4 进行检查:
?- lt(a(X,Y,c(c),Z1), a(X,Y,b(b,b),Z2)).
yes
?- lt(a(X,Y,b(b,b),Z1), a(X,Y,c(c),Z2)).
no
?- lt(a(X,Y1,c(c),Z1), a(X,Y2,b(b,b),Z2)).
uncaught exception: error(instantiation_error,lt/2)
?- lt(a(X,Y1,b(b,b),Z1), a(X,Y2,c(c),Z2)).
uncaught exception: error(instantiation_error,lt/2)
?- lt(b(b), a(a,a)).
yes
?- lt(a(X), a(Y)).
uncaught exception: error(instantiation_error,lt/2)
?- lt(X, 3).
uncaught exception: error(instantiation_error,lt/2)
?- lt(X+a, X+b).
yes
?- lt(X+a, Y+b).
uncaught exception: error(instantiation_error,lt/2)
?- lt(a(X), b(Y)).
yes
?- lt(a(X), a(Y)).
uncaught exception: error(instantiation_error,lt/2)
?- lt(a(X), a(X)).
no
?- lt(X+1,1+2).
uncaught exception: error(instantiation_error,lt/2)
?- lt(X+X+2,X+1+3). % NEW
uncaught exception: error(instantiation_error,lt/2)
在这个答案中,我们提出了谓词safe_term_less_than/2
,这是iso-prolog内置谓词(@<)/2
的单调类似物(§8.4.1,"项小于"(。其主要特性是:
- 递归项的显式遍历。
-
基于prolog-coroutining设施,特别是
when/2
。-
比较可能会逐渐进行:
- 实例化不足时"冻结">
- 每当最重要术语的实例化发生变化时"唤醒">
-
比较的当前前线表示为显式 (LIFO( 堆栈。
- 当前状态直接围绕剩余目标传递。
-
以下代码已在 sicstus-prolog 版本 4.3.2 上开发和测试:
safe_term_less_than(L, R) :- % exported predicate
i_less_than_([L-R]).
上述safe_term_less_than/2
定义基于以下辅助谓词:
i_less_than_([L-R|LRs]( :- Cond = (?=(L,R( ; nonvar(L(,nonvar(R((, when(Cond, i_lt_step_(L,R,LRs((.i_lt_step_(L, R, LRs( :- ( L == R -> i_less_than_(LRs( ; term_itype(L, L_type(, term_itype(R, R_type(, 比较(Ord, L_type, R_type(, ord_lt_step_(Ord, L, R, LRs( ).term_itype(V, T( :- ( var(V( -> 抛出(错误(instantiation_error,_(( ; 浮点数(V( -> T = t1_float(V( ; 整数(V( -> T = t2_integer(V( ; 可调用(V( -> T = t3_callable(A,F(, 函子(V, F, A( ; 抛出(错误(system_error,_(( ).ord_lt_step_(<, _, _, _(.ord_lt_step_(=, L, R, LRs( :- ( 化合物(L( -> L =..[_|Ls], R =..[_|Rs], 短语(args_args_paired(Ls,Rs(, LRs0, LRs(, i_less_than_(LRs0( ; i_less_than_(LRs( ).args_args_paired([], []( --> []。args_args_paired([L|Ls], [R|Rs]( --> [L-R], args_args_paired(Ls, Rs(.
示例查询:
| ?- safe_term_less_than(X, 3(.序:trig_nondif(X,3,_A,_B(,prolog:trig_or([_B,X],_A,_A(,prolog:when(_A,(?=(X,3(;nonvar(X(,nonvar(3((,user:i_lt_step_(X,3,[](( ?是的| ?- safe_term_less_than(X, 3(, X = 4.不| ?- safe_term_less_than(X, 3(, X = 2.X = 2 ? ;不| ?- safe_term_less_than(X, a(.prolog:trig_nondif(X,a,_A,_B(,prolog:trig_or([_B,X],_A,_A(,prolog:when(_A,(?=(X,a(;nonvar(X(,nonvar(a((,user:i_lt_step_(X,a,[](( ? ;不| ?- safe_term_less_than(X, a(, X = a.不| ?- safe_term_less_than(X+2, Y+1(, X = Y.不
与之前的答案相比,我们观察到:
- 剩余目标的"文本量"似乎有点"臃肿"。
- 查询
?- safe_term_less_than(X+2, Y+1), X = Y.
失败 - 就像它应该的那样!
这个答案是我之前提出的safe_term_less_than/2
答案的后续。
下一步是什么?compare/3
的一个安全变体 - 难以想象地称为scompare/3
:
比较(Ord, L, R( :- i_scompare_ord([L-R], Ord(.i_scompare_ord([], =(.i_scompare_ord([L-R|Ps], X( :- when((?=(L,R(;nonvar(L(,nonvar(R((, i_one_step_scompare_ord(L,R,Ps,X((.i_one_step_scompare_ord(L, R, LRs, Ord( :- ( L == R -> scompare_ord(LRs, Ord( ; term_itype(L, L_type(, term_itype(R, R_type(, 比较(Rel, L_type, R_type(, ( Rel \== (=( -> Ord = rel ; 化合物(L( -> L =..[_|Ls], R =..[_|Rs], 短语(args_args_paired(Ls,Rs(, LRs0, LRs(, i_scompare_ord(LRs0, Ord( ; i_scompare_ord(LRs , Ord( ) ).
谓词term_itype/2
和args_args_paired//2
与前面定义的谓词相同。