Как пролог выполняется через рекурсивные запросы с использованием succ?
Может ли кто-нибудь объяснить мне, почему этот запрос пролога работает так, как он работает. Определение таково:
add(0,Y,Y).
add(succ(X),Y,succ(Z)):- add(X,Y,Z).
Учитывая это:
?- add(succ(succ(succ(0))), succ(succ(0)), R).
Вот след запроса:
Call: (6) add(succ(succ(succ(0))), succ(succ(0)), R)
Call: (7) add(succ(succ(0)), succ(succ(0)), _G648)
Call: (8) add(succ(0), succ(succ(0)), _G650)
Call: (9) add(0, succ(succ(0)), _G652)
Exit: (9) add(0, succ(succ(0)), succ(succ(0)))
Exit: (8) add(succ(0), succ(succ(0)), succ(succ(succ(0))))
Exit: (7) add(succ(succ(0)), succ(succ(0)),
succ(succ(succ(succ(0)))))
Exit: (6) add(succ(succ(succ(0))), succ(succ(0)),
succ(succ(succ(succ(succ(0))))))
Часть, которая смутила меня больше всего в этом учебнике, была тем фактом, что в первом аргументе succ разделен, и он рекурсирует. Хотя при рекурсии R получает succ... Но как?! Кроме того, откуда берется ноль на первом выходе (9)? Я новичок в прологе, и я пытаюсь понять язык для домашнего задания. Любая помощь очень ценится.
Примечание: для всех, кто заинтересован, ссылка на этот учебник http://www.learnprolognow.org/lpnpage.php?pagetype=html&pageid=lpn-htmlse9
3 ответов:
"откуда берется ноль на первом выходе (9)?"
ВызовОбъединен с первым предложением, которое говорит, что если первый аргумент
addравен нулю, то второй и третий являются одинаковыми. В этой конкретной ситуации переменная_G652становитсяsucc(succ(0))."хотя при рекурсии R получает succ... Но как?!"
Это является результатом применения второго пункта. В этом пункте говорится (грубо), что вы сначала отделитеsuccот первого аргумента, затем вызовитеaddрекурсивно и, наконец, добавьте еще один "слой"succк третьему аргументу, возвращающемуся из этого рекурсивного вызова.Предикат
addесть не что иное, как прямая реализация сложения в арифметике Пеано: http://en.wikipedia.org/wiki/Peano_axioms#Addition
Вы видите,
callиexit- этоverbsдействия, которые интерпретатор предпринимает, пытаясь решить поставленный Вами запрос. Затем след показывает детали фактически выполненной работы и позволяет вам просмотреть ее в исторической перспективе.Когда Пролог должен выбрать правило (a
call), он используетname, которое вы ему даете (так называемыйfunctor), и пытается кunifyкаждому аргументу в голове правила. Тогда мы обычно говорим, что пролог рассматривает такжеarity, то есть число аргументов, для выбора.
Unificationпопытки "сделать равными" два члена, и заслуживающие внимания результаты так называемыеbindingsпеременных. Вы уже знаете, что переменные-это имена, начинающиеся сUppercase. Такие имена идентифицируют неопределенные значения в правилах, т. е. являютсяplaceholdersдля Аргументов. Чтобы избежать путаницы, когда Пролог показывает трассировку, переменные переименовываются, чтобы мы могли их идентифицировать, потому что соответствующая деталь-этоidentitiesили привязки, установленные во время доказательства.Тогда вы видите такие
_G648символы в следах. Они остаются дляеще не установленных аргументов названной цели, а именноRиZ. R уникален (встречается только в вызове верхнего уровня), поэтому этот пролог любезно сохраняет удобное для пользователя имя, но Z приходит из программы, потенциально встречается несколько раз, а затем переименовывается.Чтобы ответить на этот запрос
?- add(succ(succ(succ(0))), succ(succ(0)), R).Пролог Первые попытки соответствия
add(0,Y,Y).И потерпеть неудачу, потому что succ(succ(succ(0)) нельзя сделать равным 0. Затем попытки
add(succ(X),Y,succ(Z)) :- add(X,Y,Z).Таким образом должны быть решены следующие привязки (слева термины вызывающего):
Вы можете видеть, почему X становитсяsucc(succ(succ(0))) = succ(X) succ(succ(0)) = Y R = Zsucc(succ(0)), и у нас есть новая цель доказать, то есть тело правилаadd(X,Y,Z)С только что установленными связями:Add (succ(succ(0)), succ(succ (0)), _G648)
И так далее... пока X не станет
0и цель не совпадет
add(0,Y,Y).Тогда Y становится succ(succ (0)), и следует также дать значение
Zв вызове правило.HTH
(Надеюсь) более разборчивый след с большим количеством аннотаций:
Как видите, четыре выхода избыточны, окончательный ответ уже известен в точке(6) Call: add(succ(succ(succ(0))), succ(succ(0)), R) % R = succ(_G648) (7) Call: add(succ(succ(0)), succ(succ(0)), _G648) % _G648 = succ(_G650) (8) Call: add(succ(0), succ(succ(0)), _G650) % _G650 = succ(_G652) (9) Call: add(0, succ(succ(0)), _G652) % _G652 = succ(succ(0)) (9) Exit: add(0, succ(succ(0)), succ(succ(0))) (8) Exit: add(succ(0), succ(succ(0)), succ(succ(succ(0)))) (7) Exit: add(succ(succ(0)), succ(succ(0)), succ(succ(succ(succ(0))))) (6) Exit: add(succ(succ(succ(0))), succ(succ(0)), succ(succ(succ(succ(succ(0))))))(9) Exit; в этой точке достаточно только одного выхода:% R = succ(_G648) % _G648 = succ(_G650) % _G650 = succ(_G652) % _G652 = succ(succ(0)) % thus, % R = succ( succ( succ( succ(succ(0)) )))Это действительно то, что происходит при оптимизации хвостового вызова, поскольку определение предиката является действительно хвостовым рекурсивным, а результат под
Rстроится сверху вниз, причем" дыра " постепенно заполняется инстанциацией логических переменных. Таким образом,succ( _ )не добавляется после рекурсивного вызова, но устанавливается перед им. В этом также заключается суть оптимизации хвостовой рекурсии по модулю cons.
Comments