
Глава 19 Метаинтерпретаторы
Метапрограммы обращаются с другими программами как с данными; они выполняют их анализ, преобразование и моделирование. Легкость разработки метапрограмм, или метапрограммирования, на Прологе обусловлена эквивалентностью программ и данных - и те и другие являются термами Пролога. Действительно, метапрограммирование не является для Пролога чем-то необычным. Несколько примеров программ в предыдущих главах являются метапрограммами: редактор в программе 12.5,действие оболочки в программе 12.6,модели автомата в разд. 14,3и трансляция грамматических правил в предложения Пролога, реализованная в программе 16.2.
В данной главе рассматриваются метапрограммы особого класса - метаинтерпретаторы. В первом разделе обсуждаются принципы построения метаинтерпретаторов, В других разделах главы показано, насколько широка область их применения.
Применения метаинтерпретаторов для построения оболочек экспертных систем разниваются во втором разделе. В третьем разделе обсуждаются алгоритмы отладки программ и их реализации.
19 1. Простые метаинтерпретаторы
Метаинтерпретатордля некоторого языка - это интерпретатор для языка, написанный на том же самом языке. Некоторые языки программирования позволяют легко разрабатывать метаинтерпретаторы, что является важным свойством таких языков. Оно делает возможным построение интегрированной среды программирования и обеспечивает доступ к вычислительным средствам языка. Поскольку метаинтерпретатор - это программа на языке Пролог, то приведем реляционную схему. Отношениерешить(Цель)истинно, еслиЦельистинна в отношении программы, подлежащей интерпретации. Везде в этом разделе для обозначения метаинтерпретатора используется предикатрешить/1.
решить (Цель)
Цельвыводится из программы на чистом Прологе, определенной предложениемпредложение /2.
решить(истина).
решить(А,В)) решить(A),решить(B).
решить(А) предложение(А,В),решить(В).
Программа 19.1.Метаинтерпретатор для чистого Пролога.
В простейшем метаинтерпретаторе, который может быть написан на Прологе, используются возможности метапеременной, а именно правило вида
решить (А) А.
Полезность использования метапеременных демонстрировалась программой 12.6и программой 12.7,в которых они служили основой для описания оболочки и средств регистрации, реализованных на Прологе.
Более интересный метаинтерпретатор имитирует вычислительную модель логических программ. Редукция цели для программы на чистом Прологе может быть описана тремя предложениями, составляющими программу 19.1.В данной главе рассматриваются этот базовый метаинтерпретатор и его расширения.
Интерпретатору соответствует следующее декларативное толкование. Константа истинаявляется истинной. Конъюнкция(А. В)истинна, если истинна цельАи истинна цельВ.ЦельАистинна, если в интерпретируемой программе существует предложениеА В,такое, что цельВистинна.
Теперь дадим процедурное толкование этим трем предложениям программы 19.1,Фактрешитьустанавливает, что пустая цель, представленная в Прологе атомомистина,достижима. Следующее предложение относится к конъюнктивным целям. Оно читается так: «Для достижения конъюнкции целей (А, В) необходимо достичь целиАи целиВ».Общий случай редукции цели покрывается последним, предложением программы. Чтобы доказать некоторую цель, из программы выбирается предложение, заголовок которого унифицируется с целью, а затем рекурсивно применяется тело предложения.
Процедурное толкование предложений Пролога необходимо, чтобы показать, что метаинтерпретатор, представленный программой 19.1,действительно отражает возможности Пролога при реализации абстрактной вычислительной модели логического программирования. Такими возможностями, например, являются выбор для редукции крайней левой цели и использование последовательного поиска с возвратом при недетерминированном выборе предложения для редукции цели. Порядок целей в теле предложениярешить,содержащего конъюнктивные цели, грантирует, что самая,левая цель в конъюнкции решается первой. Последовательный поиск и возврат осуществляются при доказательстве целипредложение согласно принципам выполнения Пролог - программ.
Постоянная работа интерпретатора обеспечивается третьим предложением программы 19.1.При вызове предложенияпредложениевыполняется унификация с заголовками предложении, имеющихся в программе. Это предложение ответственно также за получение различных решений при возвратах. Кроме того, возвраты происходят и в конъюнктивном правиле (возвраты от целиВк целиА).
Поучителен разбор протокола работы метаинтерпретатора, представленного программой 19.1,при доказательстве некоторой цели. На рис, 19.1приведен протокол метаинтерпретатора при ответе на вопросрешить (member (X,[a,b,c]))в отношении программы 3.12.
решать(member(Х,[а,b,с]))
предложение(member(Х,[а,b,с]),В) {X =а,В == true}
решить (истина)
истина Результат: Х =и
решить(истина)
предложение (истина,Т) f
предложение(membег(Х,[а,b,с]),В) {В = member(X,[b,c])}
решить(member(X,[b,c]))
предложение(membеr(Х,[b,с]),В1) {X = b,B1 = true}
решить (истина)
истина Выход: Х = b
;
решить(истина)
предложение(истина, Т) f
предложение(member(Х,[b,с]),В1) {В] = member(X,[c])}
решить(member(Х,[с]))
предложение (member(X,[c]),B2) {X =с,В2 == true}
решить(истина)
истина Выход: Х = с
;
решить (истина)
предложение (истина. Т) f
предложение(member(X,[с],В2)) {В2 = member(X,[ ])}
решить(member(Х,[ ]))
предложение (member(X,[ ],B3) f
Других решений нет
Рис. 19.1.Протокол работы метаинтерпретатора.
Метаинтерпретаторы можно различать по степени их детализации, т.е. по размеру фрагментов вычисления, которые оказываются доступны программисту. Метаинтерпретатор, состоящий из одного предложения, слишком тривиален и поэтому имеет весьма ограниченную область применения. Можно, хотя и нелегко, написать метаинтерпретатор, который будет моделировать процедуру унификации и механизм возврата. Степень детализации такого метаинтерпретатора очень высока, но стремиться к ней, как правило, бессмысленно, поскольку потеря эффективности при этом не окупается дополнительными приложениями. Метаинтерпретатор в программе 19.1на уровне редукции цели имеет степень детализации, подходящую для широкой области приложений.
Простым примером использования метаинтерпретатора является построение дерева доказательства в процессе решения определенной цели. Этот метод схож с методом конструирования дерева разбора для грамматик, рассматриваемым в программе 16.3.Кроме того, здесь используются структурированные аргументы, применение которых в логических программах рассматривалось в разд. 2.2.Дерево доказательства полезно для средств объяснения в экспертных системах. Этот вопрос будет обсуждаться в следующем разделе.
solve (Goal,Tree)
Tree-дереводоказательства цели Goal,задаваемой программой, определенной предикатом clause/2.
solve(true.true). solve((A,B),(ProofA,ProofB))
solve(A,ProorA),solve(B,ProofB).
solve(A,(A Proof))
clause(A,B), solve(B,Proof).
Программа 19.2.Построение дерева доказательства.
Основным отношением метаинтерпретатора является отношение solve (Goal,Tree), где Tree-дереводоказательства для решения цели Goal.Дерево доказательства представляется структурой Goal Proof,где Proof -конъюнкция ветвей (подцелей) доказываемой цели Goal.Программа 19.2,реализующая предикат solve/2,является простым расширением программы 19,1. Три ее предложения точно соответствуют трем предложениям метаинтерпретатора для чистого Пролога.
Факт solveутверждает, что пустая цель истинна и имеет тривиальное дерево доказательства, представляемое атомом true.Во втором предложении утверждается, что дерево доказательства конъюнктивной цели (A,В)представляет собой конъюнкцию деревьев доказательства целейАиВ.Последнее предложение solve строит дерево доказательстваА Proofдля цели А, в котором Proofстроится рекурсивно при решении тела предложения, используемого для редукции целиА.
Рассмотрим пример использования программы 19.2для чиcто логической программы 1.2.Вопрос solve(сын(лот, аран).Proof)?имеет решение
Proof= (сын (лom, аран)
((отец (арии, лот) - true),
(мужчина (лот) true))).
Вопрос solve (сын (X, apан),Proof)?имеет решениеХ =лоти то же самое значение для Proof.
Для того чтобы обрабатывать программы, в которых используются средства, выходящие за рамки чистого Пролога, метаинтерпретатор, представленный программой 19.1,должен быть расширен.
Различные системные предикаты не определяются предложениями программы и поэтому требуют отдельной обработки. Простейший способ обращения к этим системным предикатам состоит в непосредственном их вызове с использованием метапеременных. Необходима таблица, устанавливающая, какие предикаты являются системными. Будем считать, что она состоит из фактов вида system (Predicate) для каждого системного предиката. Часть такой таблицы представлена на рис. 19-2. Предложения метаинтерпретатора, оперирующие с системными предикатами, имеют вид
solve(A) system (A), A.
system(A :=В). system(A < В).
system (read (X)). system (write(X)).
system(integer(X)). system(functor(T,F,N)).
system(clause(A,B)). system(system(X)).
Рис. 19.2.фрагмент таблицы системных предикатов.
Дополнительное предложение solveделает действие системных предикатов невидимым для интерпретатора. Этот способ можно распространить при необходимости на несистемные предикаты. Наоборот, существуют некоторые системные предикаты, которые должны быть видимыми. Примерами являются предикаты для отрицания и программирования второго порядка. Для каждого из них в метаинтерпретаторе целесообразно иметь специальные предложения, как, например:
solve(not A) not solve(A).
solve(set_of(X, Goal, Xs))
set_of(X,soive(Goal,Xs).
Проблемой в этом метаинтерпрераторе является корректное, моделирование отсечения. Наивным было бы рассматривать отсечение как системный предикат, поскольку это означает добавление в метаинтерпретатор предложения
solve(!) !.
которое не приводит к требуемому эффекту. Отсечение в таком предложении скорее гарантирует переход к текущему предложению solve,нежели воздействие на дерево поиска. Другими словами, область действия отсечения слишком локальна.
Более искусное решение связано с использованием такого системного предиката, как отсечение с учетом предыстории. Такое отсечение реализовано в языках Wisdom-Прологи Waterloo-Пролог,но отсутствует в языке Edinburgh-Пролог. Синтаксически отсечение с учетом предыстории описывается как!(Предок),гдеПредок-ссылка на предка текущей цели. ЕслиПредок -положительное целое число, напримерn, то в отсечении идет речь обп-ом предке текущей цели. Счет предков ведется вверх от текущей цели, так что первый предок-это працель, второй предок-прапрацельи т.д. ЕслиПредокне является целочисленным термом, то рассматривается первый предок, унифицируемый с термомПредок.В любом случае все цели, родственные рассматриваемой, отсекаются от дерева поиска, как если бы непосредственно к предковой цели было применено отсечение.
Чтобы корректно использовать отсечения, учитывающие предысторию, необходимо ввести предикат, отличный от solve.Здесь используется предикатreduce(Goal).Корректная область действия отсечения обеспечивается использованием в метаинтерпретаторе для отсечения предшествующейредуцированнойцели отсечения, учитывающего предысторию.
Все рассмотренные выше усовершенствования метаинтерпретатора включены в программу 19.3 - метаинтерпретатор для Пролога.
solve (Goal)
Goal-цель, выводимая из Пролог-программы, определенной предложением clause/2.
solve(true).
solve((A,B)) solve(A), solve(B).
solve(!) !(reduce(A)).
solve(notA) not solve(A).
solve(set_of(X,Goal,Xs)) set _ of(X,solve(Goal),Xs).
solve(A) system(A),A,
solve(A) reduce(A).
reduce(A) clause(A,B), solve(B).
Программа 19.3.Метаинтерпретатор для полного Пролога.
Метаинтерпретатор в программе 19.3можно сделать еще более эффективным путем добавления отсечений. Выбор соответствующего предложения из набора Предложений sol:eдетерминирован. Как только будет идентифицировано правильное предложение, оно может быть использовано в процессе вычислений.
Метаинтерпретатор для построения дерева доказательства, представленный на чистом Прологе программой 19.2.также может быть расширен добавлением некоторых предложений. В частности, системные цели могут обрабатываться с помощью предложения
so1ve(A,(A true)) system(A).A.
Деревом доказательства для системной цели АбудетА true.
В качестве примера рассмотрим усовершенствованный метаинтерпретатор, обеспечивающий трассировку вычисления, как в разд. 6.1-Будут приведены две версии метаинтерпретатора. Интерпретатор, представленный программой 19.4, обрабатывает только успешные ветви вычислений в чистом Прологе и не отображает безуспешные вершины в дереве поиска. Он способен, в частности, генерировать протокол вычислений вопроса append([Xs,Ys,[a,b.c]),представленный на рис. 6.2. Второй метаинтерпретатор (программа 19.5)обрабатывает системные предикаты и, что более важно, отображает безуспешные вершины в дереве поиска. Он способен генерировать протоколы, представленные на рис. 6.1и 6.3,для вопросовсын(Х, аран) и quicksort([2,l,3],Xs)соответственно.
trace (Goal)
Goal-цель,выводимая из программы на чистом Прологе, определенной предложением clause/2.
Программа выполняет трассировку доказательства с использованием побочных эффектов.
trace(Goal)
trace(Goal,0).
trace(true,Depth).
trace((A,B),Depth)
trace(A,Depth), trace(B,Depth).
trace(A,Depth)
clause(A,B),
display(A,Depth),
Depth1: = Depth + 1,
trace(B,Depth1).
display(A,Depth)
tab(Depth) write(A), nl.
Программа 19.4.Трассировщик для чистого Пролога.
Основной предикат рассматриваемой программы -trace (Goal, Depth),гдеGoal - цель, решаемая на некоторой глубине Depth.Предполагается, что начальная глубина равна 0,Три предложения программы 19.4соответствуют трем предложениям программы 19.1.Первые два предложения устанавливают, что пустая цель решается на любой глубине, а глубина решения каждого элемента конъюнктивной цели одинакова. Третье предложение сопоставляет цель с заголовком некоторого предложения трассируемой программы, отображает цель, увеличивает глубину и решает тело предложения программы на новой глубине.
Предикат displa\ (Goul, Depth),который служит для отображения цели Goalна глубине Depth,является некоторым интерфейсом, обеспечивающим печать трассируемой цели. Глубина в дереве доказательства отражена глубиной отступа при печати. В определении предиката displayиспользуется предикат tab (Depth),определяющий размер отступа, который пропорционален глубине в дереве поиска.
trace (Goal)
Goal - цель,выводимая из Пролог-программы, определенной предложением clause/2. Программа выполняет трассировку доказательства с использованием побочных эффектов.
trace(Goal) trace{Goal,0).
trace(true,Depth) !.
trace((A,B),Depth)
!, trace(A,Depth), trace(B,Depth).
trace(A,Depth)
system(A), A, !,display(A,Depth), nl.
trace(A, Depth)
clause(A,B),
display(A, Depth), nl,
Depth1: = Depth +1,
trace(B,Depthl).
trace(A,Depth)
not clause(A.B), display(A.Depth), tab(8), write(f), nl, fail.
display(A,Depth)
Spacing: == 3 * Depth, tab(Spacing),write(A).
Программа 19.5.Трассировщик для Пролога.
Существует некоторая хитрость в определении порядка целей в предложении
trace(Goal, Depth)
clause(A,B),
display (A, Depth),
Display!: = Depth + 1,
trace (В, Depth!).
Цель displayрасположена между целями clauseи trace.Это гарантирует, что цельdisplayбудет выполняться каждый раз, когда происходит возврат к цели clause.Если же поменять местами цели clauseи display,то при трассировке будет отображен только начальный вызов целиА.
Использование программы 19.4для вопроса trace(append(Xs,Ys,a,b,c]))по отношению к программе 3.15для предиката appendприводит к протоколу, представленному на рис. 6.1.Выходные сообщения и точки с запятой для указания альтернативных решений обеспечиваются самой Пролог - системой. Существует лишь одно отличие от протокола на рис. 6.2:унификации здесь уже выполнены.
Программа 19.4может быть расширена с целью трассировки безуспешных целей. Для вывода на печать безуспешных целей следует положиться на операционное поведение Пролога, главное, в части порядка предложений. К первым двум предложениям добавим отсечения, а в конце программы поместим дополнительно следующее предложение:
trace(A, Depth)
not clause(A, B),display(A,Depth),
tab(10),write(f),nl,fail.
Отметим, что предикат displayмодифицирован для того, чтобы вывод не начинался с новой строки и сообщения о безуспешных целях были согласованы с результатами трассировки, представленными в гл. 6.В предложении, вызывающем предикатdisplay,необходима дополнительная команда nl. Системные цели обрабатываются предложением
trace(A, Depth) system(A),A,!,display(A,Depth).
Для того чтобы последнее правило действовало корректно, когда решение цели оказывается безуспешным, предикат displayвызывается после успешного решения пели. Требуемое поведение обеспечивается введением отсечения. С учетом описанных модификаций получена программа 19.5,которая способна выдать протоколы, представленные на рис. 6.3и 19.1.
Упражнения к раэд. 19.1
1.Напишите метаинтерпретатор для подсчета числа вызовов процедуры в некоторой программе.
2.Расширьте программу 19.5.чтобы она обрабатывала программы на полном Прологе аналогично программе 19.3.
3.Напишите интерактивный трассировщик, который обращается к пользователю прежде, чем выполнить редукцию очередной цели.
4.Расширьте программу 19.3так, чтобы она интерпретировала отсечение с учетом предыстории.