swi-prolog не останавливается после первого истины с дизъюнкцией (или)

Я (абсолютный новичок в Прологе) пытаюсь понять смысл пролога. Вот пример Санты в swi-prolog:

gives(santa,leonard,book).
gives(santa,adrian,game).
gives(santa,adrian,smartmax).

likes(leonard,lego).
likes(adrian,lego).
likes(adrian,book).

age(leonard,6).
age(adrian,4).

jealous(C1,C2) :- aggregate_all(count, gives(santa,C1,X), N1), 
  aggregate_all(count, gives(santa,C2,X), N2),
  N1 < N2,
  true.

jealous(C1,C2) :- findall(G, 
    (owns(C2,G), 
     likes(C1,G), 
     not(owns(C1,G))
    ), 
    Gifts),
  length(Gifts,NGifts),
  NGifts > 0,
  true.

Это работает как ожидалось:

?- jealous(adrian,leonard).
true.

Однако, когда я меняю порядок двух jealous-предикатов в коде:

?- jealous(adrian,leonard).
true ;
false.

Это довольно странно: я думал, что предложения jealous будут вести себя как «или»: всякий раз, когда один из результатов верен, дальнейшей обработки не должно быть. Поэтому мне интересно: как сделать так, чтобы оно действительно действовало как «или»: если какое-либо из jealous-правил истинно, оно должно возвращать true, во всех остальных случаях это false. Я не хочу «следующих результатов». Мне нужен только 1 результат: true или false.

Что я делаю неправильно? (Наверное, кое-что, так что просветите меня, пожалуйста :)).

Спасибо.


person Kurt Sys    schedule 04.12.2019    source источник
comment
В вашей реализации есть точка выбора. Это означает, что у Пролога есть другие варианты для изучения после первого успеха (правда). Ваша ; запись указывает Prolog вернуться к точке выбора и проверить, есть ли другие решения. Больше он не находит, поэтому отвечает ложью. Это нормальное поведение Пролога.   -  person lurker    schedule 05.12.2019
comment
Что ж, эта точка выбора зависит от порядка пунктов, которые я определяю ... Я не понимаю, почему Prolog возвращается в некоторых случаях, а не в других. Однако, похоже, я нашел решение: stackoverflow.com/questions/25346189/prolog-disjunction.   -  person Kurt Sys    schedule 05.12.2019
comment
Это еще один пример: https://stackoverflow.com/questions/42209223/prolog-true-false-output-of-member-predicate   -  person lurker    schedule 05.12.2019
comment
да, правда ... Я понимаю ;, я не могу понять, почему Prolog хочет продолжить, когда обнаруживает true. Но, видимо, это нормальное поведение: p.   -  person Kurt Sys    schedule 05.12.2019
comment
Есть такая вещь, как написание детерминированного предиката в Прологе, не оставляющего точки выбора. Это может быть очень сложно сделать без потери общности (например, если вы используете сокращение).   -  person lurker    schedule 05.12.2019
comment
Висячие точки выбора безвредны, но немного неэстетичны и, кажется, доставляют немало огорчений новым пользователям Prolog. Вместо того, чтобы беспокоиться об этом, вам, вероятно, следует просто игнорировать их, поскольку, как указывает @lurker, вмешательства, которые вы пытаетесь предпринять в первые несколько дней использования Prolog, могут причинить вам вред, который вы заметите гораздо позже (проблемы обратной правильности, например). Помните, что недетерминизм - это преимущество Prolog; вам нужны генерируемые предикаты, и вы обычно будете следовать за ними с помощью предикатов, которые фильтруют.   -  person Daniel Lyons    schedule 05.12.2019


Ответы (1)


При таком порядке предложений поиск Prolog пытается сначала сопоставить первое предложение:

jealous(C1,C2) :- aggregate_all(count, gives(santa,C1,X), N1), 
  aggregate_all(count, gives(santa,C2,X), N2),
  N1 < N2.
  % the final true is unnecessary

в этом предложении считается, что у Адриана 2 дара, а у Леонарда 1, поэтому N1 ‹N2, здесь он не работает. Prolog выполняет возврат, все предложение не выполняется, и алгоритм пытается выполнить второе, которое завершается успешно:

?- jealous(adrian,leonard).
true.

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

Если пункты поменяны местами, сначала проверяется успешный вариант, и дается разрешение:

?- jealous(adrian,leonard).
true

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

;
false.

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

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

NGifts > 0

ясно, что если известно о NGifts, существует только одно возможное решение (неважно какое), и возврат в этот момент не вернется с альтернативным доказательством. Таким образом, этот оператор исключен из стека выбора, поскольку он детерминирован - его единственное решение было изучено.

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

Если вам нужно контролировать это поведение и не нужно выявлять случаи, когда маленькие мальчики завидуют друг другу вдвойне, вы можете заставить Prolog «вырезать» точки выбора:

jealous(C1,C2) :-
  findall(G, 
    (owns(C2,G), 
     likes(C1,G), 
     not(owns(C1,G))
    ), 
    Gifts),
  length(Gifts,NGifts),
  NGifts > 0,
  !. % cut: if this clause gets so far, later clauses do not need to be explored

jealous(C1,C2) :-
  aggregate_all(count, gives(santa,C1,X), N1), 
  aggregate_all(count, gives(santa,C2,X), N2),
  N1 < N2.

Это делает поведение обеих программ идентичным в этом примере. Я оставляю обсуждение «будьте осторожны с сокращениями» для комментариев и других вопросов.

person boisvert    schedule 05.12.2019
comment
Хорошее объяснение! Соблюдая осторожность с сокращениями, я получаю это, поскольку оно сильно зависит от порядка пунктов. Было бы разумнее использовать в запросах слово «один раз»? - person Kurt Sys; 05.12.2019
comment
@KurtSys проблема (как с вырезанием, так и с однократным) в том, что он влияет на работу Prolog как средства доказательства теорем. Логическое программирование (как поле) должно позволить вам думать о проблеме, которую нужно решить независимо от выполнения алгоритма. Если вам нужен «один раз» или сокращение, тогда ваша программа зависит от порядка выполнения, и поэтому это не формулировка вашей проблемы, а программа для ее решения. - person boisvert; 05.12.2019
comment
Хорошо, я понимаю ... (было неправильно, что порядок важен). Предложения jealous - это не постановка задачи, а программа, решающая проблему. Я вижу. Итак, куда мне их поместить, если их не должно быть в пунктах? (Или как мне лучше написать «постановку проблемы»? - person Kurt Sys; 05.12.2019
comment
@KurtSys ищет способы упростить код, переместить сложный код, чтобы изолировать некрасивые вычисления в отдельных правилах или предложениях, и назвать правила в соответствии с тем, для чего вы их собираетесь. На самом деле, вашему первому предложению не нужны все подсчеты. Все, что вам нужно, это владеет / нравится / не владеет. Если вы получили несколько доказательств ревности, подумайте еще раз: каждое доказательство - это повод для ревности, добавленное к подсчету, который измеряет, насколько сильна ревность. Так что NGifts - это своего рода показатель зависти. - person boisvert; 05.12.2019
comment
теперь имеет для меня смысл. Пользователь сам выбирает, что делать со всеми «истинными» доказательствами (или, если кто-то добавляет метрику, что-то делать со всеми метриками). Однако возникает другой вопрос: можно ли связать все доказательства в один список? Пока что N1<N2 и owns/likes/not owns возвращают истину или ложь, но Пролог не «собирает» их в список или около того, верно? Использование findall (и подобных ему) кажется трудным, поскольку нет переменных. findall(_, jealous(adrian, lennert), J). дает что-то вроде J = [_3114]. О, это означает: 1 true значение? - person Kurt Sys; 06.12.2019
comment
... исправили, добавив к каждому пункту «меру ревности». Мне очень нравится эта идея / решение. И я «извлек» фактическую программу (как отвечать на конкретные вопросы, например, «насколько ревнивы X к Y») в другой файл. Хороший. Спасибо! - person Kurt Sys; 06.12.2019