7.4. Интерпретатор, обнаруживающий циклы Процедуры, которые зацикливаются.

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

Приведем, например, текст процедуры "нпредок" из разд. 1.5:

нпредок (А, Б) :- % (1)

родитель (А, Б).

нпредок (А, Б):- % (2)

нпредок (А, В),

родитель (В, Б)

Процедура "нпредок" сходна с процедурой "предок", которая работает нормально. Отличие между двумя процедурами заключается лишь в том, что порядок расположения подцелей в правиле (2) процедуры "нпредок" является леворекурсивным, а в процедуре "предок" — праворекурсивным. Поскольку рекурсивный вызов процедуры "нпредок" выполняется раньше, чем подцель "родитель", переменная В не конкретизирована в точке вызова процедуры "нпредок".

Когда интерпретатор Пролога попытается выполнить запрос к процедуре "нпредок", то вначале он найдет все правильные ответы, а затем продолжит рекурсию вплоть до исчерпания памяти.

Робастный решатель задач

Процедура "иоц" — это версия программы "вып", способная надежно выполнять запросы к леворекурсивным программам, подобным процедуре "нпредок". Метод, который применяется для придания программе "иоц" свойств робастного решателя задач, аналогичен методу, использованному при составлении программы "тснр_путешествовать3" из разд. 4.2. Программа "иоц" регистрирует в списке заголовки всех фраз, которые она встречает при обработке запроса. Если один и тот же заголовок фразы встретится четыре раза подряд, то "иоц" потерпит неудачу. Это свойство предохраняет программу "иоц" от попадания в бесконечные рекурсивные циклы. Для того чтобы продемонстрировать возможности программы "иоц" рассмотрим запрос к процедуре "нпредок". Предположим, что существует нижеследующая база данных "родитель":

родитель (жб, лц).

родитель (жб, гг).

родитель (гг, вм).

Интерпретатор Пролога в бесконечной рекурсии после отыскания трех (позитивных) ответов:

| ?- нпредок (жб, Z).

Z = лц ;

Z=гг ;

Z = вм ;

предупреждение: исчерпана стековая память.

Но процедура "иоц" сможет надежно выполнить такой запрос:

| ? - иоц (нпредок (жб, X) ) .

нпредок (жб, лц)

введите символ h для просмотра пути доказательства или символ ; для

получения другого ответа

h

нпредок (жб, лц)?

родитель (жб, лц) содержится в программе.

нпредок (жб, л ц)

введите символ h для просмотра пути доказательства или символ • для

получения другого ответа

;

нпредок (жб, гг)

введите символ h для просмотра пути доказательства или символ ; для

получения другого ответа

h

нпредок (жб, гг)?

родитель (жб, гг) содержится в программе.

нпредок (жб, гг)

введите символ h для просмотра пути доказательства или символ • для

получения другого ответа

нпредок (жб, вм)

введите символ h для просмотра пути доказательства или символ ; для

получения другого ответа

h

нпредок (жб, вм)?

нпредок (жб, гг)?

родитель (жб, гг) содержится в программе.

родитель (гг, вм) содержится в программе.

нпредок (жб, вм)

введите символ h для просмотра пути доказательства или символ ; для

получения другого ответа

;

Heт

Алгоритм работы программы "иоц"

Реализация программы ''иоц" очень близка к реализации программы "вып". В процедуре "иоц0" (эквивалент процедуры "вып0"') имеется новый по сравнению с "вып0" первый аргумент — список подцелей, встретившихся к настоящему моменту при выполнении запроса. В третьем правиле процедуры "иоц0" появилась новая подцель «not (унифицируемый (Q1, След, 3))». Переменная «След» - это список подцелей, а переменная <<Q1)> это текущая подцель. Процедура "унифицируемый" проверяет, можно ли унифицировать Q1 с гремя элементами списка «След» , расположенными подряд. Если можно, что процедура "иоцО" терпит неудачу и возвращается назад для продолжения поиска. Число "три" здесь выбрано произвольно. Если уменьшить это число до двух, то программа "иоц" не сможет находить ответы в некоторых леворекурсивных задачах. Если это число увеличить до четырех, то программа "иоц" сможет отыскивать дополнительные ответы в определенных редко встречающихся ситуациях, но это потребует значительно больших затрат машинного времени и оперативной памяти.

% интерпретатор, обнаруживающий циклы. иоц (Q) : --

иоц0([ ], Q,Путь),

спросить_у_пользователя (Q, Путь).

спросить_у_пользователя (Q, Путь) : -

write (Q), nl,

write ('вветиме символ h для просмотра пути доказательства'),

write ('или символ ; для ) nl,

write ('получения другого ответа' ),

nl,

get0(A), get0(_), % ввод символа + перевод строки

А = 104, % 104 - это ascii - код' h'

отобразить_путь ( , Путь),

спросить_у_пользователя (Q, Путь),

!.

% исходный случай для факта

% + +

% След Запрос Путь

иоц0(_, true, есть_в_программе) :-!.

% вторая фраза: составной запрос

иоцО (След, (Q1, Q2), (Р1, Р2)) : -

!,

иоц0(След,Q1.Р1),

иоц0(След,Q2,Р2).

% третья фраза: простой запрос

иоц0(След,Q1,п(Q1,Р1)) :-

% неудача, если три элемента подряд списка След

% унифицируются с Q1:

not (унифицируемый ( Q1, След, 3)),

clause (Q1, Тело).

иоц0([Q1 | След], Тело, Р).

% успех, если первый аргумент унифицируется с тремя подряд иду-

% щими элементами списка, заданного вторым аргументом.

унифицируемый (_, _, 0) : - !.

унифицируемый (A, [A ! R], N) :-

NNisN-1,

!, унифицируемый (A, R, NN).

Программа "иоц" работает во многом аналогично программе "вып". Отличие заключается в способе, которым список «След» (первый аргумент процедуры "иоц0") набирает элементы по мере того, как глубина рекурсивного вызова процедуры становится все большей. При выполнении запроса

| ?- иоц (нпредок (жб,Х)).

будут выполняться нижеследующие вызовы процедуры "иоц0", демонстрирующие увеличение размера списка, являющегося первым аргументом:

| ?- иоц0([ ], нпредок (жб,Х).Р1).

нпредок (жб, лц) % первый ответ, выдаваемый "иоц"

нпредок (жб, гг) % второй ответ, выдаваемый "иоц"

| ?- иоцО ([нпредок (жб, X) ], нпредок (жб, С1). Р2).

нпредок (жб, вм) % третий ответ, выдаваемый "иоц"

| ?- иоцО ([нпредок (жб, С 1), нпредок (жб.Х)],

нпредок (жб, С2), Р3).

| ?- иоц0 ( [нпредок (жб, С2), нпредок (жб, С1),

нпредок (жб, X) ], нпредок (жб, СЗ), Р4).

| ?— not (унифицируемый (нпредок (жб, СЗ) ,

[нпредок (жб,С2);

нпредок (жб, С 1),

нпредок (жб, X)],

3)).

нет

При выполнении запроса список «След» (первый аргумент процедуры "иоц0") продолжает расти до тех пор, пока в нем не станет насчитываться три элемента «нпредок (жб,_)», которые являются унифицируемыми друг с другом. При попытке добавления четвертого такого же элемента («нпредок(жб, СЗ)») подцель «not (унифицируемый (Q1, След, 3)» потерпит неудачу и рекурсия в "иоц" будет прекращена. Заметьте, что точно в этом месте (когда после отыскания трех ответов пользователь просит найти еще один ответ) обычный интерпретатор Пролога вошел бы в бесконечный цикл.

Соседние файлы в папке Гл.6,7,Прилож.,Допол