Контринтуитивное поведение min_member/2

min_member (- Min, + List)

Истинно, когда Min является наименьшим членом в стандартном порядке терминов. Ошибка, если List пуст.

?- min_member(3, [1,2,X]).
X = 3.

Объяснение, конечно, состоит в том, что переменные встречаются перед всеми другими терминами в стандартном порядке терминов, и используется унификация. Однако заявленное решение кажется каким-то неправильным.

Как это можно оправдать? Как интерпретировать это решение?

EDIT:

Один из способов предотвратить выполнение min_member/2 с помощью этого решения - изменить стандартную библиотеку (SWI-Prolog) реализацию следующим образом:

xmin_member(Min, [H|T]) :-
    xmin_member_(T, H, Min).

xmin_member_([], Min0, Min) :-
    (   var(Min0), nonvar(Min)
    ->  fail
    ;   Min = Min0
    ).
xmin_member_([H|T], Min0, Min) :-
    (   H @>= Min0 
    ->  xmin_member_(T, Min0, Min)
    ;   xmin_member_(T, H, Min)
    ).

Обоснование неудачи вместо того, чтобы бросить ошибку создания (то, что @mat предлагает в своем ответе, если я правильно понял), заключается в том, что это однозначный вопрос:

"Является ли 3 минимальным членом [1,2,X], когда X является свободной переменной?"

и ответ на это (по крайней мере, для меня) ясный "Нет", а не "я не могу сказать".

Это тот же класс поведения, что и sort/2:

?- sort([A,B,C], [3,1,2]).
A = 3,
B = 1,
C = 2.

И применяются те же самые трюки:

?- min_member(3, [1,2,A,B]).
A = 3.

?- var(B), min_member(3, [1,2,A,B]).
B = 3.

Ответ 1

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

?- X @< 2, X = 3.
X = 3.

Большинство предикатов, использующих аннотацию -Value для аргумента, говорят, что pred(Value) является тем же как pred(Var), Value = Var. Вот еще один пример:

?- sort([2,X], [3,2]).
X = 3.

Эти предикаты представляют только чистые отношения, если вход заземлен. Слишком много требовать, чтобы входные данные были заземлены, потому что они могут быть осмысленно использованы с переменными, если пользователь знает, что он/она не должен создавать экземпляр какого-либо из упорядоченных условий. В этом смысле я не согласен с @mat. Я согласен с тем, что ограничения, безусловно, могут повлиять на некоторые из этих отношений.

Ответ 2

Фактическим источником путаницы является общая проблема с общим кодом Пролога. Нет чистой, общепринятой классификации вида чистоты или примеси предиката Пролога. В руководстве, а также в стандарте, чистые и нечистые встроенные модули счастливо смешиваются. По этой причине вещи часто путаются и говорят о том, что должно быть так, а что нет, часто приводит к бесплодным дискуссиям.

Как это можно оправдать? Как интерпретировать это решение?

Сначала рассмотрите "объявление режима" или "индикатор режима":

min_member (-Min, + List)

В документации SWI это описывает способ использования этим предикатом программиста. Таким образом, первый аргумент должен быть необознаненным (и, вероятно, также неизолированным в пределах цели), второй аргумент должен быть создан в список какого-то рода. Для всех других целей вы сами по себе. Система предполагает, что вы можете проверить это для себя. Вы действительно в состоянии это сделать? У меня, со своей стороны, есть некоторые трудности с этим. ISO имеет другую систему, которая также возникает в DEC10.

Кроме того, реализация пытается быть "разумной" для неуказанных случаев. В частности, он пытается быть стойким в первом аргументе. Таким образом, минимум сначала вычисляется независимо от значения Min. Затем результирующее значение объединяется с Min. Эта надежность против злоупотреблений часто приходит к цене. В этом случае min_member/2 всегда должен посещать весь список. Неважно, полезно ли это или нет. Рассмотрим

?- length(L, 1000000), maplist(=(1),L), min_member(2, L).

Ясно, что 2 не является минимумом L. Это можно обнаружить, рассмотрев только первый элемент списка. Из-за общности определения необходимо посетить весь список.

Этот способ обработки унификации вывода аналогично обрабатывается в стандарте. Вы можете заметить эти случаи, когда декларативное описание (иначе) (которое является первым из встроенного) явно ссылается на унификацию, например

8.5.4 copy_term/2

8.5.4.1 Описание

copy_term(Term_1, Term_2) истинно, если if Term_2 объединяет с термином T, который является переименованной копией (7.1.6.2) of Term_1.

или

8.4.3 sort/2

8.4.3.1 Описание

sort(List, Sorted) истинно, если if Sorted объединяется с помощью отсортированный список List (7.1.6.5).

Вот те аргументы (в скобках) встроенных модулей, которые могут быть поняты только как выходные аргументы. Обратите внимание, что есть намного больше, которые эффективно являются выходными аргументами, но после некоторой операции не требуется процесс объединения. Подумайте о 8.5.2 arg/3 (3) или 8.2.1 (=)/2 (2) или (1).

8.5.4 1 copy_term/2 (2), 8.4.2 compare/3 (1), 8.4.3 sort/2 (2), 8.4.4 keysort/2 (2), 8.10.1 findall/3 (3), 8.10.2 bagof/3 (3), 8.10.3 setof/3 (3).

Так много для ваших прямых вопросов, есть еще несколько фундаментальных проблем:

Срок заказа

Исторически сложилось, что "стандартный" термин порядок 1 был определен, чтобы разрешить определение setof/3 и sort/2 около 1982. (До этого, как и в 1978 году, он не упоминался в руководство пользователя DEC10 manual.)

С 1982 года термин ордер часто использовался (erm, ab-) для реализации других заказов, в частности, потому что DEC10 не предлагал предикатов более высокого порядка напрямую. call/N должен был быть изобретен два года спустя (1984); но потребовалось еще несколько десятилетий, чтобы быть общепринятым. Именно по этой причине программисты Prolog имеют несколько небрежное отношение к сортировке. Часто они намереваются сортировать термины определенного типа, но предпочитают использовать sort/2 для этой цели - без дополнительной проверки ошибок. Еще одна причина для этого - отличная производительность sort/2 избиения различных "эффективных" библиотек в других языках программирования спустя десятилетия (я считаю, что STL тоже была ошибкой для этой цели). Также полная магия в коде - я помню одну переменную с именем Omniumgatherum - не приглашал копировать и модифицировать код.

У порядка порядка есть две проблемы: переменные (которые могут быть дополнительно созданы для аннулирования текущего порядка) и бесконечные термины. Оба обрабатываются в текущих реализациях без возникновения ошибки, но с результатами undefined. Тем не менее, программисты предполагают, что все будет работать. В идеале были бы предикаты сравнения, которые производят ошибки создания для неясных случаев как это предложение. И еще одна ошибка для несравнимых бесконечных терминов.

Оба SICStus и SWI имеют min_member/2, но только SICStus имеет min_member/3 с дополнительным аргументом для указания используемого порядка, Таким образом, цель

?- min_member(=<, M, Ms).

ведет себя больше к вашим ожиданиям, но только для чисел (плюс арифметические выражения).


Сноска:

1 Я цитирую стандарт в стандартном срочном порядке, поскольку это понятие существовало с 1982 года, тогда как стандарт был опубликован в 1995 году.

Ответ 3

Ясно, что min_member/2 не является истинным отношением:

?- min_member(X, [X,0]), X = 1.
X = 1.

но после простого обмена двумя целями (весьма желательной) коммутативностью конъюнкции получим:

?- X = 1, min_member(X, [X,0]).
false.

Это явно плохо, как вы правильно заметили.

Ограничения - это декларативное решение для таких задач. В случае целых чисел ограничения конечных доменов являются вполне декларативным решением для таких задач.

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

Ответ 4

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

?- min_member(X, [A, B]), A = 3, B = 2.
X = A, A = 3,
B = 2.

можно избежать, если некоторые условия могут быть отложены на тот момент, когда A и B получают экземпляр.

promise_relation(Rel_2, X, Y):-
    call(Rel_2, X, Y),
    when(ground(X), call(Rel_2, X, Y)),
    when(ground(Y), call(Rel_2, X, Y)).

min_member_1(Min, Lst):-
    member(Min, Lst),
    maplist(promise_relation(@=<, Min), Lst).

То, что я хочу от min_member_1(?Min, ?Lst), заключается в выражении отношения, в котором Min всегда будет ниже (в стандартном порядке терминов), чем любой из элементов в Lst.

?- min_member_1(X, L), L = [_,2,3,4], X = 1.
X = 1,
L = [1, 2, 3, 4] .

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

?- min_member_1(X, [A,B,C]), B is 3, C is 4, A is 1.
X = A, A = 1,
B = 3,
C = 4 ;
false.
?- min_member_1(X, [A,B,C]), A is 1, B is 3, C is 4.
false.

Но этого можно избежать, объединив все их в одну цель:

?- min_member_1(X, [A,B,C]), [A, B, C] = [1, 3, 4].
X = A, A = 1,
B = 3,
C = 4 ;
false.

Версии

Если сравнения предназначены только для инстанцируемых переменных, promise_relation/3 можно изменить, чтобы проверить отношение только тогда, когда обе переменные получают экземпляр:

promise_relation(Rel_2, X, Y):-
    when((ground(X), ground(Y)), call(Rel_2, X, Y)).

Простой тест:

?- L = [_, _, _, _], min_member_1(X, L), L = [3,4,1,2].
L = [3, 4, 1, 2],
X = 1 ;
false.

! Были сделаны изменения для улучшения исходного сообщения благодаря ложным комментариям и предложениям.

Ответ 5

Вот как реализуется min_member/2:

min_member(Min, [H|T]) :-
    min_member_(T, H, Min).

min_member_([], Min, Min).
min_member_([H|T], Min0, Min) :-
    (   H @>= Min0
    ->  min_member_(T, Min0, Min)
    ;   min_member_(T, H, Min)
    ).

Итак, кажется, что min_member/2 на самом деле пытается унифицировать Min (первый аргумент) с наименьшим элементом в List в стандартном порядке терминов.

Ответ 6

У меня есть наблюдение за вашей реализацией xmin_member. В этом запросе не получается:

?- xmin_member(1, [X, 2, 3]).
false.

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

ymin_member(Min, Lst):-
    member(Min, Lst),
    maplist(@=<(Min), Lst).

Конечно, это хуже с точки зрения эффективности, но оно работает в этом случае:

?- ymin_member(1, [X, 2, 3]).
X = 1 ;
false.

?- ymin_member(X, [X, 2, 3]).
true ;
X = 2 ;
false.