Предположим следующую программу:

nat(0).
nat(s(N)) :- nat(N).

/* 0+b=b */
plus(0,B,B) :- nat(B).
/* (a+1)+b = c iff a+(b+1)=c */
plus(s(A),B,C) :- plus(A,s(B),C).

Он отлично подходит для сложения двух чисел, но когда я пытаюсь выполнить запрос следующего вида:

plus(Z,Z,s(0)).

Он переходит к поиску возможных значений для Z еще долго после того, как должно быть очевидно, что решения нет (т.е. Z>s(0))

Я знаком с оператором cut (!), и моя интуиция подсказывает, что решение имеет к нему какое-то отношение, я просто не знаю, как его использовать в этом случае.

7
yurib 23 Мар 2014 в 21:26
1
Спасибо за тег, не знал, что у концепции есть официальное название.
 – 
yurib
24 Мар 2014 в 03:09

3 ответа

Лучший ответ

!/0 постоянно не является хорошим решением таких проблем. Обычно это приводит к потере действительных решений для более общих запросов.

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

?- use_module(library(clpfd)).
true.

?- Z + Z #= 0.
Z = 0.

?- Z + Z #= 0, Z #> 0.
false.

РЕДАКТИРОВАТЬ : возможное решение без CLP (FD) по запросу:

plus(0, Y, Y).
plus(s(X), Y, s(Z)) :- plus(X, Y, Z).
4
mat 24 Мар 2014 в 03:18
Я, вероятно, должен был упомянуть об этом явно, но я ищу решение без использования каких-либо библиотек и такое, которое использует арифметику преемника.
 – 
yurib
24 Мар 2014 в 03:01

Итак, вам нужно понять точные свойства завершения конкретной программы. Пролог здесь сильно отличается от других языков программирования, поскольку имеет относительно сложный механизм выполнения. В частности, возврат с возвратом тесно переплетается с «нормальным разрешением», а затем сочетается с унификацией.

Самый простой способ понять проблему исходной программы - рассмотреть следующий фрагмент отказа (см. Ссылку для других Примеры):

plus(0,B,B) :- false, nat(B).
plus(s(A),B,C) :- plus(A,s(B),C), false.

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

Рассмотрим третий аргумент: передается просто переменная C. Его ценность (в этом фрагменте) никого не интересует. Таким образом, третий аргумент никак не повлияет на расторжение договора!

Хуже того, второй аргумент - это просто свободная переменная B в заголовке, таким образом, опять же, аргумент никогда не повлияет на завершение этого фрагмента или нет.

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

plus(A,B,C)terminates_if b(A),b(B);b(A),b(C).

Кажется, лучше всего переформулировать вашу программу. Предоставляя вам следующее условие завершения:

plus(A,B,C)terminates_if b(A),b(B);b(C).

В Prolog мы обычно хотим еще больше обобщать программы, принимая также некоторые неожиданные решения и улучшая условие завершения до еще лучше:

plus(A,B,C)terminates_if b(A);b(C).

Однако эта последняя версия теперь допускает решения, которые не всегда приемлемы. Например. plus(0, nonnumber, nonnumnber) теперь выполняется успешно, тогда как вы можете захотеть, чтобы он завершился неудачей.

Конечно, вы также можете поэкспериментировать с сокращениями, но имейте в виду, что использование cut чрезвычайно подвержено ошибкам. По крайней мере, вы должны комбинировать это с соответствующими тестами, которые часто исключают «повышение эффективности».

3
Community 23 Май 2017 в 15:33

Я понял, что подхожу к неправильному пути, мне нужен был не разрез (!), а другой набор правил, которые находят X и Y путем "разрушения" Z, пока он не достигнет 0, поскольку X и Y увеличиваются только при уменьшении Z, им никогда не будут присвоены невозможные значения:

plus(0,0,0).
plus(s(A),B,s(C)) :- plus(A,B,C).
plus(0,s(B),s(C)) :- plus(0,B,C).
1
yurib 24 Мар 2014 в 03:08
Вы правы, поместите это как ответ, и я приму его.
 – 
yurib
24 Мар 2014 в 03:14