- •Глава 7 экспертные консультации
- •7.1. Системы экспертных консультаций
- •Два определения понятия "экспертная система"
- •Составные части системы экспертных консультаций
- •Интерфейс с пользователем
- •Стратегия решения задач
- •7.2, Запоминание пути вывода Процедура "вып"
- •Как работает процедура "вып"
- •Доказательство факта
- •Доказательство правила
- •Показ цепочки выводов
- •Ограничения программы "вып"
- •7.3. Видимый пролог
- •Переход от программы "вып" к программе "вид"
- •7.4. Интерпретатор, обнаруживающий циклы Процедуры, которые зацикливаются.
- •Робастный решатель задач
- •Программа "иоц" как предикат метаязыка
- •7.5. Заключение: стиль программирования на прологе Суть компьютерной программы
- •Два правила хорошего стиля программирования на Прологе. Декларативная граница
- •Примеры декларативной границы
- •Другие примеры
- •Пролог как эффективный вычислительный формализм
- •Библиографические заметки
- •Упражнения
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)» потерпит неудачу и рекурсия в "иоц" будет прекращена. Заметьте, что точно в этом месте (когда после отыскания трех ответов пользователь просит найти еще один ответ) обычный интерпретатор Пролога вошел бы в бесконечный цикл.