как остановить prolog от бесконечного изучения невозможных решений?

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

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))

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

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).

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

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

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

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

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

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

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

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 чрезвычайно подвержено ошибкам. По крайней мере, вы должны объединить его с соответствующими тестами, которые часто сводят на нет "повышение эффективности".


я понял, что подхожу к нему не с той стороны, что мне нужно было не порез(!) но другой набор правил, которые находят 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).