portray(присоединить(А,В,С)):- writе('присоединить('), write(A), write(','), write(B), write(','), write('‹foo›)').
Каждый раз когда встретится цель X, содержащая предикат присоединить, приведенное утверждение будет обеспечивать успешное согласование цели portray(X), и вывод трассировочной информации будет полностью возложен на данное утверждение. В случае цели, содержащей любой другой предикат, цель port-гау(Х) не согласуется с базой данных и потому сведения об X будут выданы с помощью предиката write(X). Если бы приведенное выше утверждение присутствовало в базе данных, то соответствующая часть приведенного выше протокола трассировки выглядела бы следующим образом:
?- присоединить ([a],[b],X).
CALL присоединить([а],[b],‹fоо›)
CALL присоединить ([],[b],‹foo›)
EXIT присоединить([],[b],‹fоо›)
EXIT присоединить([a],[b],‹foo›)
X = [a,b];
REDO присоединить([a],[b],‹foo›)
REDO присоединить([],[b],‹foо›)
FAIL присоединить([],[b],‹foo›)
FAIL присоединить ([a],[b],‹foo›) нет
Т
еперь рассмотрим управляемую трассировку. Если вы задали управляемую трассировку для событий некоторого типа, то Пролог спросит у вас, что нужно сделать после того, как наступит событие заданного типа. На терминале это может выглядеть примерно так:
?- присоединить ([а],[b],Х).
CALL присоединить([а],[b],_43)?
После вывода литеры '?' программа останавливается. Теперь от вас требуется ответ – команда, которой вы зададите одно из возможных действий. Если затребованное вами действие означает продолжение обычного выполнения программы, то она продолжит выполнение до тех пор, пока трассировка не дойдет до следующего управляемого события для прослеживаемого предиката. И опять вам будет задан вопрос вида:
CALL присоединить([],[b],_103)?
Одной из команд может быть выдача списка допустимых команд на терминал. Рассмотрим некоторые из них.
Выдача информации о цели
Первая группа команд предназначена для выдачи информации о цели в различных форматах. Как мы уже знаем, стандартным средством выдачи сведений о цели является предикат print, в рамках которого с помощью определяемого пользователем предиката portray можно выводить нужные сведения в нужном формате. Однако у пользователя могут возникнуть сомнения в правильности утверждений, определяющих portray, или он может пожелать увидеть цель в обычной форме. Поэтому Пролог предоставляет команду, дающую вам возможность вывести сведения о текущей цело с помощью предиката write или display. В этом случае программа не продолжает выполняться, а пользователя просят задать еще одну команду, которая укажет как следует продолжать выполнение программы. Как правило, этот диалог имеет следующий вид:
?- присоединить([a],[b],X).
CALL присоединить([а],[b],‹fоо›)? write
CALL присоединить([а],[b],_103)?
Обычно в качестве альтернативного способа вывода сведений о цели используют write. Предикат display может понадобиться в том случае, когда цель содержит много операторов, и вы забыли приоритеты их выполнения. В этом случае display поможет вам однозначно определить вложенность функторов.
Выдача информации о предшественниках
Предшественниками данной цели называются те цели, согласованность которых зависит от согласованности данной цели. На наших диаграммах с прямоугольниками это те цели, прямоугольники которых включают данную цель. Так, каждая цель имеет предшественника, который в свою очередь является одной из целей исходного вопроса – той целью, согласованию которой помогает текущая цель. Аналогично, когда речь идет о правиле, каждая цель, порожденная телом правила, имеет в качестве предшественника ту цель, которая сопоставлена с заголовком правила. Рассмотрим некоторые примеры предшественников. Обратимся к следующей простой программе обращения списка (которая рассматривалась в разд. 7.5):
обр([],[]).
oбp([H|T],L):- oбp(T,Z), присоединить(Z,[H],L).
присоединить([],X,Х).
присоединить([А|В],С,[А|D]):- присоединить(В,С,D).
Пусть мы задали исходный вопрос:
?- oбp([a,b,c,d],X). (A)
Тогда, вследствие второго утверждения, возникают две подцели, каждая из которых в качестве своего непосредственного предшественника имеет цель, составляющую содержание исходного вопроса. Вот эти подцели:
oбp([b,c,d],Z) (B)
присоединить(Z,[a],X) (C)
Поскольку второе утверждение будет снова использовано при согласовании (B), снова возникают две подцели:
oбp([c,d],Z1) (D)
присоединить(Z1,[a],Z) (E)
Их предшественниками являются цели (A) и (B). Заметим, что цель (C) не является их предшественником, поскольку от них непосредственно зависит только согласованность (B), от которой, в свою очередь, зависит согласованность (А). Цели (D) и (E) никак не влияют на согласованность (C). Когда процесс согласования исходного вопроса заходит уже достаточно далеко, возникает цель вида:
присоединить([с],[b],Y)
На этом этапе текущая цель и ее предшественники могут быть представлены в следующем виде:
oбp([a,b,c,d],_46) (цель A)
oбp([b,c,d],[d|_50]) (цель B)
присоединить([d,с],[b],[d|_51])
присоединить([с],[b],_52)
Прежде чем читать дальше, вам следует убедиться в том, что вы понимаете, почему это предшественники данной цели, а также почему у нее нет никаких других предшественников. С приведенным здесь изображением предшественников связана одна особенность, которая может проявиться и в вашей Пролог-системе. Существуют два способа выдачи информации о предшественнике на печать – при первом способе информация соответствует состоянию предшественника при первой попытке согласовать его, при втором – текущему состоянию, с теми значениями переменных, которые они получили в результате конкретизации. Здесь у нас принят второй способ. Когда выполнение впервые дошло до цели (B), второй аргумент предиката обр не был конкретизирован. Тем не менее, в списке предшественников этот аргумент показан как имеющий значение. Это объясняется тем, что теперь переменная, которая задана в этой позиции, оказалась конкретизированной, а именно, теперь мы выяснили, что для [b, с, d] первым элементом обращенного списка является d.
Глядя на предшественников текущей цели можно получить ясное представление о том, что происходит с программой и почему она делает то, что наблюдается. Одной из команд, которые пользователю разрешается вводить при наступлении управляемого события для некоторой цели может быть выдача сведений о каких-либо из ее предшественников. Таким образом, если вы чувствуете, что ваша программа тратит где-то много времени и подозреваете, что это может быть результатом зацикливания, то верная стратегия состоит в том, чтобы прервать выполнение, включить полную трассировку, а затем посмотреть на предшественников, чтобы понять, где вы находитесь.
Изменение уровня трассировки
Другой набор команд, которыми можно воспользоваться при наступлении управляемого события, связан с изменением желаемого объема трассировки. Некоторые из более грубых управляющих воздействий состоят в следующем.
• Удаление всех контрольных точек. Это имеет тот же эффект, что и вызов цели nodebug (см. разд. 6.13).
• Отключение полной трассировки. Это имеет тот же эффект, что и вызов цели notrace (см. разд. 6.13).
• Включение полной трассировки. Это имеет тот же эффект, что и вызов цели trace (см. разд. 6.13).
При задании любой из этих команд ваша программа продолжит затем выполнение в указанном режиме до тех пор, пока не дойдет до цели, которую вы намеревались прослеживать. В некоторых версиях Пролога могут быть предусмотрены более тонкие средства управления трассировкой. Эти средства помогут вам быстро пройти через те участки выполнения программы, которые не представляют интереса с тем, чтобы сосредоточиться на участках, где, вероятно, имеются ошибки. Здесь возможны следующие команды:
• «creep» (ползти): Продолжить выполнение программы с полной трассировкой до тех пор, пока не поступит новое указание (при наступлении следующего управляемого события).
• «skip» (пропустить): Продолжить выполнение программы без выдачи какой-бы то ни было трассировочной информации до тех пор, пока не наступит какое-либо событие, относящееся к текущей цели.
• «leap» (перескочить); Продолжить выполнение программы без выдачи трассировочной информации до тех пор, пока либо не будет достигнута контрольная точка, либо не наступит событие, относящееся к текущей цели.