Как определить (и назвать) соответствующие предикаты сравнения безопасных терминов в ISO Prolog?
Стандартный порядок терминов (ISO/IEC 13211-1 7.2 Порядок терминов) определяется для всех терминов, включая переменные. Хотя для этого есть хорошее применение - подумайте о реализации setof/3
, это делает многие иначе понятные и логичные использования встроенных в 8.4 Term сравнений декларативным кошмаром с бесами (краткая форма для императивных конструкций) вокруг. 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
,ПРИМЕЧАНИЕ. - Встроенные предикаты, которые проверяют порядок терминов
определены в 8.4.
...
И, таким образом, все переменные меньше, чем a
, Но однажды X
создается экземпляр:
?- X @< a, X = a.
X = a.
результат становится недействительным.
Так что это проблема. Чтобы преодолеть это, можно либо использовать ограничения, либо придерживаться только основного поведения и, следовательно, произвести instantiation_error
,
7.12.2 Классификация ошибок
Ошибки классифицируются по форме
Error_term
:а) Должна быть ошибка установления, когда
аргумент или один из его компонентов является переменной, и
требуется конкретный аргумент или компонент. Она имеет
формаinstantiation_error
,
Таким образом, мы точно знаем, что результат хорошо определен, пока не возникает ошибка создания экземпляра.
За (\==)/2
уже есть dif/2
который использует ограничения или iso_dif/2
который производит чистую ошибку экземпляра.
iso_dif(X, Y) :-
X \== Y,
( X \= Y -> true
; throw(error(instantiation_error,iso_dif/2))
).
Так о чем мой вопрос: как определить (и назвать) соответствующие предикаты сравнения безопасных терминов в ISO Prolog? В идеале, без какого-либо явного обхода термина. Может быть, чтобы уточнить: выше iso_dif/2
не использует явный термин обход. И то и другое (\==)/2
а также (\=)/2
Обход термина внутри, но накладные расходы для этого чрезвычайно низки по сравнению с явным обходом с (=..)/2
или же functor/3, arg/3
,
8 ответов
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).
(Редактировать: принимая во внимание замечания Тудора Берариу: (i) отсутствует случай ошибки var/var, (ii) порядок по арности в первую очередь; более того, исправление (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
преуспевает. Мы хотим проверить, что отношение не зависит от некоторых неинициализированных переменных. Итак, я делаю соответствующие копии X2
а также Y2
из X
а также Y
где все переменные создаются:
- В
X2
, переменные объединяются с 1. - В
Y2
переменные объединяются с 0.
Итак, если отношение X2 @< Y2
все еще остается в силе, мы знаем, что мы не полагаемся на стандартный порядок терминов между переменными. В противном случае мы выдаем исключение, потому что это означает, что 1 @< 0
отношение, которое ранее не происходило, сделало связь неудачной.
Упущения
(на основе комментариев ОП)
lt(X+a,X+b)
должен быть успешным, но выдает ошибку.На первый взгляд можно подумать, что объединяющие переменные, которые встречаются в обоих терминах с одним и тем же значением, скажем,
val
, может исправить ситуацию. Тем не менее, могут быть и другие случаиX
в сравнении, где это приводит к ошибочному суждению.lt(X,3)
должен выдать ошибку, но успешно.Чтобы исправить это дело, нужно унифицировать
X
с чем-то больше 3. В общем случае,X
должен принимать значение, которое больше, чем любой другой возможный термин1. Помимо практических ограничений,@<
отношение не имеет максимума: составные термины больше, чем несоставные, и по определению составные термины могут быть сделаны произвольно большими.
Таким образом, этот подход не является окончательным, и я не думаю, что его можно легко исправить.
1: обратите внимание, что для любого данного термина, однако, мы могли бы найти локально максимальные и минимальные термины, которых было бы достаточно для цели вопроса.
Третья попытка! Разработано и протестировано с GNU Prolog 1.4.4.
Приложение "А": "так просто, как может"
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)
).
Некоторый общий код:
альфа_омега (альфа, омега):- Альфа это - (10.0 ^ 1000),% HACK! функтор (Омега, г, 255). % HACK! label_vars ([], _, _). label_vars ([Альфа |Vs], Альфа, Омега):- label_vars(Vs, Альфа, Омега). label_vars([Omega|Vs], Альфа, Омега):- label_vars(Vs, Альфа, Омега).
Это не совсем оригинальный ответ, так как он основан на ответе @coredump.
Есть один тип запросов lt/2
(эталонная реализация, выполняющая явный обход термина) не может правильно ответить:
| ?- lt(b(b), a(a,a)).
no
| ?- @<(b(b), a(a,a)).
yes
Причина в том, что стандартный порядок терминов учитывает арность до сравнения имен функторов.
Во-вторых, 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)
).
Предикат lt_args(Xs, Ys)
верно, когда есть пара соответствующих аргументов Xi
, 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),
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, "срок меньше чем"). Его основными свойствами являются:
- Явный обход рекурсивных терминов.
На основе пролог-сопутствующих объектов, в частности
when/2
,Сравнение может прогрессировать постепенно:
- "заморозить" всякий раз, когда создание экземпляра недостаточно
- "просыпаться" всякий раз, когда изменяются экземпляры наиболее значимых терминов
Текущий фронт сравнения представлен в виде явного (LIFO) стека.
- Текущее состояние напрямую передается по остаточным целям.
Следующий код был разработан и протестирован на /questions/tagged/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_ ([LR | LRs]): - Cond = (? = (L, R); nonvar (L), nonvar (R)), когда (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) -> throw ( ошибка ( instantiation_error, _)); float (V) -> T = t1_float (V); целое число (V) -> T = t2_integer (V); callable (V) -> T = t3_callable (A, F), функтор (V, F, A); throw(error(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]) -> [LR], args_args_paired(Ls, Rs).
Примеры запросов:
|? - safe_term_less_than (X, 3). Пролог:trig_nondif(Х,3,_О,_B), Пролог:trig_or([_ В,X] _ А, _О), пролог: когда (_A,(?=(X,3);nonvar(X),nonvar(3)), пользователь:i_lt_step_(X,3,[]))? да |?- safe_term_less_than(X, 3), X = 4. нет |?- safe_term_less_than(X, 3), X = 2. Х = 2?; нет |?- safe_term_less_than(X, a). Пролог:trig_nondif(X, A,_О,_B), Пролог:trig_or([_ В,X] _ А, _О), пролог: когда (_A,(?=(X,a);nonvar(X),nonvar(a)), пользователь: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.
терпит неудачу - так же, как и должно!
Какого черта! Я тоже попробую!
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
отличаются, то это единственные переменные, которые должны быть проверены на актуальность. Таким образом, это включает случай, когда нет общей переменной, но она является еще более общей, чем эта.
Этот ответ дополняет мой предыдущий, который представил safe_term_less_than/2
,
Что дальше? Безопасный вариант compare/3
- единодушно называется scompare/3
:
scompare (Ord, L, R): - i_scompare_ord ([LR], Ord). i_scompare_ord([], =). i_scompare_ord([LR|Ps], X):- когда ((?=(L,R);nonvar(L),nonvar(R)), i_one_step_scompare_ord(L,R,Ps,X)). i_one_step_scompare_ord(L, R, LR, 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
такие же, как определено ранее.