пролог-бесконечное правило

у меня есть следующие правила

% Signature: natural_number(N)/1
% Purpose: N is a natural number.
natural_number(0).
natural_number(s(X)) :-
   natural_number(X).

ackermann(0, N, s(N)). % rule 1
ackermann(s(M),0,Result):-
   ackermann(M,s(0),Result). % rule 2
ackermann(s(M),s(N),Result):-
   ackermann(M,Result1,Result),
   ackermann(s(M),N,Result1). % rule 3

запрос: ackermann (M,N,s(s(0))).

теперь, как я понял, в третьем расчете мы получили бесконечный поиск (ветвь отказа). Я проверяю его, и я получил конечный поиск (ветвь отказа).

я объясню: в первом случае мы получили замену M=0, N=s (0) (Правило 1 - успех!). Во-вторых, мы получили замену M=s (0),N=0 (Правило 2 - успех!). Но что теперь? Я пытаюсь соответствовать M=s(s (0)) N=0, но он получил конечная поиск - филиала провал. Почему компилятор не пишет мне "fail".

спасибо.

2 ответов


было немного трудно понять, что именно Том спрашивает Здесь. Возможно, есть ожидание, что сказуемое natural_number/1 как-то влияет на исполнение ackermann / 3. Не будет. Последний предикат является чисто рекурсивным и не делает подцелей, которые зависят от natural_number/1.

когда три показанных предложения определены для ackermann / 3, в гол:

?- ackermann(M,N,s(s(0))).

заставляет SWI-Prolog находить (с обратным отслеживанием) два решения, о которых сообщает том, а затем переходить в бесконечную рекурсию (что приводит к ошибке "из стека"). Мы можем быть уверены, что эта бесконечная рекурсия включает третье предложение, данное для ackermann / 3 (Правило 3 в комментариях Тома в коде), потому что в его отсутствие мы получаем только два признанных решения, а затем явный сбой:

M = 0,
N = s(0) ;
M = s(0),
N = 0 ;
false.

мне кажется, Том просит объяснить, почему изменение представленный запрос к тому, который устанавливает M = s(s(0)) и N = 0, производя конечный поиск (который находит одно решение, а затем терпит неудачу при обратном отслеживании), согласуется с бесконечной рекурсией, произведенной предыдущим запросом. Мое подозрение здесь заключается в том, что существует недопонимание того, что механизм Prolog пытается отследить (для исходного запроса), поэтому я собираюсь подробно остановиться на этом. Надеюсь, это прояснит дело для Тома, но посмотрим, так ли это. По общему признанию, мое лечение многословно, но механизм выполнения пролога (объединение и разрешение подцелей) заслуживает изучения.

[добавлено: предикат имеет очевидную связь со знаменитым функция Аккермана это полностью вычислимо, но не примитивно рекурсивно. Эта функция известна быстрым ростом, поэтому нам нужно быть осторожными в утверждении бесконечной рекурсии, потому что очень большая, но конечная рекурсия также вероятный. Однако третье предложение помещает два рекурсивных вызова в порядке, противоположном тому, что я бы сделал, и этот разворот, похоже, играет решающую роль в бесконечной рекурсии, которую мы находим при прохождении кода ниже.]

когда цель верхнего уровня ackermann(M,N,s(s(0))) отправляется, SWI-Prolog пробует предложения (факты или правила), определенные для ackermann / 3 пока не найдет тот, чья "голова" объединяется с данным запросом. Движок Prolog не должен далеко выглядеть как первое предложение, это факт:

ackermann(0, N, s(N)).

объединит, свяжет M = 0 и N = s(0) как уже было описано как первый успех.

если требуется отследить, например, путем ввода пользователем запятой, Prolog engine проверяет, есть ли альтернативный способ удовлетворить это первое предложение. Нет. Затем механизм Prolog переходит к попытке выполнить следующие условия для ackermann / 3 в данной порядок.

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

ackermann(s(M),0,Result) :- ackermann(M,s(0),Result).

объединение запроса и главы этого правила дает привязки M = s(0), N = 0 С точки зрения переменные, используемые в запросе. С точки зрения переменных, используемых в правиле как указано выше, M = 0 и Result = s(s(0)). Обратите внимание, что унификация соответствует терминам по их внешнему виду в качестве вызывающих аргументов и не рассматривает имена переменных, повторно используемые через границу запроса/правила как означающие идентификатор.

поскольку этот пункт является правилом (имея тело, а также голову), объединение-это только первый шаг в попытке добиться успеха с ним. Движок Prolog теперь пытается выполнить одну подцель, которая появляется в теле этого правила:

ackermann(0,s(0),s(s(0))).

обратите внимание, что эта подцель происходит от замены" локальных " переменных, используемых в правиле, значениями об объединении,M = 0 и Result = s(s(0)). Движок Prolog теперь вызывает предикат ackermann / 3 рекурсивно, чтобы увидеть, может ли эта подцель быть удовлетворена.

может, как первый пункт (факт) для ackermann / 3 объединяет очевидным образом (действительно, по существу так же, как и раньше, в отношении переменных, используемых в предложении). И таким образом (после этого рекурсивного вызова) мы получаем второе решение, следующее за внешним вызовом ( запрос верхнего уровня).

если пользователь просит движок Prolog вернуться еще раз, он снова проверяет, является ли текущее предложение (второе для ackermann / 3) может быть удовлетворен альтернативным способом. Это невозможно, и поэтому поиск продолжается, переходя к третьему (и последнему) предложению для предиката ackermann / 3:

ackermann(s(M),s(N),Result) :-
    ackermann(M,Result1,Result),
    ackermann(s(M),N,Result1).

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

   query     head
     M       s(M)
     N       s(N)
   s(s(0))  Result

учитывая, что переменные, имеющие то же имя в запросе, что и переменные в правиле, не ограничивают унификацию, эта тройка терминов может быть унифицирована. Запрос M будет глава s(M), это составной термин, включающий функтор s применяется к некоторой пока неизвестной переменной M появляется в голове. То же самое для запросов N. Единственный" основной " термин до сих пор является переменным Result появление в голове (и теле) правила, которое было связано с s(s(0)) из запроса.

теперь третье предложение является правилом, поэтому механизм пролога должен продолжать, пытаясь удовлетворить подцели, появляющиеся в теле этого правила. Если подставить значения от объединения головы в тело, то первая подцель удовлетворит есть:

ackermann(M,Result1,s(s(0))).

позвольте мне отметить, что я использовал здесь "локальные" переменные предложения, за исключением того, что Я заменил Result ценностью, к которой он был привязан в объединении. Теперь обратите внимание, что помимо замены N исходного запроса верхнего уровня по имени переменной Result1, мы просто спрашиваем то же самое, что и исходный запрос в эту подцель. Конечно, это большая подсказка, мы может ввести бесконечный рекурсия.

однако необходимо немного больше обсуждения, чтобы понять, почему мы не получаем никаких дальнейших решений! Это потому, что первый успех этой первой подцели (как и было найдено ранее) потребует M = 0 и Result1 = s(0), и механизм пролога должен затем приступить к попытке второй подцели предложения:

ackermann(s(0),N,s(0)).

к сожалению, эта новая подцель не объединяется с первым предложением (фактом) для ackermann / 3. Он тут объединить с главой второго предложения следующим образом:

   subgoal     head
     s(0)      s(M)
      N         0
     s(0)     Result

но это приводит к подзаголовку (из тела второго предложения):

ackermann(0,s(0),s(0)).

это не объединяется с главой ни первого, ни второго предложения. Он также не объединяется с главой Третьего предложения (который требует, чтобы первый аргумент имел форму s(_)). Итак, мы достигли точки неудачи в дереве поиска. Движок Prolog теперь отступает, чтобы увидеть, может ли первая подцель тела третьего предложения быть удовлетворена альтернативным способом. Как мы знаем, это может быть (поскольку эта подцель в основном такая же, как и исходный запрос).

теперь M = s(0) и Result1 = 0 этого второго решения приводит к этому для второй подцели тела третьего предложения:

ackermann(s(s(0)),N,0).

хотя это не объединяется с первым предложением (фактом) сказуемого, оно объединяется с главой второго статья:

   subgoal     head
   s(s(0))     s(M)
      N         0
      0       Result

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

ackermann(s(s(0)),s(0),0).

мы легко видим, что это не может объединиться с главой первого или второго предложения для ackermann / 3. Его можно объединить с главой Третьего предложения:

  sub-subgoal  head(3rd clause)
    s(s(0))       s(M)
      s(0)        s(N)
       0         Result

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

ackermann(s(0),Result1,0).

это не объединяется с первым предложением (фактом), но объединяется с главой второго предложения, связывающего M = 0, Result1 = 0 и Result = 0, производя (по знакомой логике) под-под-под-подцель:

ackermann(0,0,0).

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

  sub-sub-subgoal  head(3rd clause)
       s(0)             s(M)
      Result1           s(N)
         0             Result

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

ackermann(0,Result1,0).

но это не будет объединяться с главой любого из трех пунктов. Поиск решения для Под-под-подзадачи выше заканчивается неудачей. Движок Prolog отслеживает весь путь до где он впервые попытался удовлетворить вторую подцель третьего предложения, вызванного исходным запросом верхнего уровня, поскольку это теперь не удалось. Другими словами, он попытался удовлетворить его первыми двумя решениями первой подцели третьего предложения, которое, как вы помните, по сути было таким же, за исключением изменения имен переменных, как и исходный запрос:

ackermann(M,Result1,s(s(0))).

выше мы видели, что решения для этой подцели, дублирующие исходный запрос, из первое и второе предложения ackermann / 3, не позволяйте второй подцели тела третьего предложения преуспеть. Поэтому механизм Prolog пытается найти решения, удовлетворяющие третьему предложению. Но ясно, что теперь это переходит в бесконечную рекурсию, поскольку это третье предложение объединится в своей голове,но тело третьего предложения повторит точно такой же поиск, который мы только что преследовали. Таким образом, движок Prolog теперь попадает в тело третьего предложения бесконечно.


позвольте мне перефразировать ваш вопрос: запрос ackermann(M,N,s(s(0))). находит два решения, а затем петли. В идеале, он завершится после этих двух решений, так как нет другого N и M стоимость которого составляет s(s(0)).

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

вместо этого, вы можете попробовать следующее: вставить целей false в любом месте вашей программы. Если результирующая программа не завершается, то и исходная программа не завершается.

в вашем случае:

ackermann(0, N, s(N)) :- false.
ackermann(s(M),0,Result):- false,
   ackermann(M,s(0),Result).
ackermann(s(M),s(N),Result):-
   ackermann(M,Result1,Result), false,
   ackermann(s(M),N,Result1).

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

ackermann(s(M),s(N),Result):-ackermann(M,Result1,Result), false.

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

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

к сожалению, такого рода рассуждения только работает в случае расторжения договора. Для прекращения все сложнее. Лучше всего попробовать такой инструмент, как cTI который выводит условия завершения и пытается доказать их оптимальность. Я уже вошел в вашу программу, поэтому попробуйте изменить if и увидеть эффекты!

если мы на нем: Этот небольшой фрагмент также говорит нам, что второй аргумент не влияет на завершение1. Это означает, что запросы типа ackermann(s(s(0)),s(s(0)),R). будет и не прекращать. Обмен цели, чтобы увидеть разницу...


1 если быть точным, термин, который не объединяет с s(_) повлияет на прекращение. Подумайте о 0. Но любой s(0), s(s(0)), ... не повлияет на прекращение.