
- •Методическое пособие
- •Архангельск,
- •«К 2011 году большинство программ будут писать компьютеры…».
- •История и перспективы развития языка Пролог
- •2. Краткая инструкция по работе в среде pie32
- •3. Механизм вывода в системе Пролог. Примеры Термы и объекты
- •Запросы к базе данных
- •Унификация
- •Правила
- •Рекурсивные процедуры
- •Базы знаний
- •Решение логических задач
- •Арифметические выражения
- •Примеры программ
- •Часть II. Указания к выполнению контрольной работы
- •Введение
- •Генеалогическое дерево
- •Перевод генеалогического дерева в базу фактов
- •Составление правил искомых родственных связей в базе знаний
- •Лабораторная работа №3
- •Список литературы
Правила
Кроме фактов программы на языке Пролог могут содержать правила, позволяющие получать дополнительные знания о том мире, который описывает программа. Правило задает новый предикат через определенные ранее.
Правило состоит из головы (предиката) и тела (последовательности предикатов, разделенных запятыми). Голова и тело разделены знаком :- и, подобно каждой фразе Пролога, правило должно заканчиваться точкой. Запятая в теле правила означает конъюнкцию (логическое И).
Знак :- есть схематическая запись стрелки (<-) и показывает, что из правой части следует левая. Этот знак читается как "если". Интуитивный смысл правила состоит в том, что цель, являющаяся головой, будет истинной, если интерпретатор Пролога сможет показать, что все выражения (подцели) в теле правила являются истинными.
Пример: Правило, определяющее отношение ребенок/2 через отношение отец/2, запишется следующим образом:
ребенок(X, Y) :- отец(Y, X).
Это означает, что если человек Y является для человека X отцом, то X является ребенком Y. Здесь X и Y - переменные. Напомним, что запись ребенок/2 показывает, что предикат ребенок является функцией от двух аргументов.
Пример: Определим отношение мать/2 через отношения родитель/2 и женщина/1: матерью X для человека Y является его родитель женского рода.
мать(X, Y) :- родитель(X, Y), женщина(X).
Предикаты отличаются друг от друга не только именем, но и количеством аргументов. Так можно определить отношение мать/1 следующим образом:
мать(X) :- родитель(X, _), женщина(X).
Так как нам в данном предикате не важно, чьим родителем является данная женщина, то мы использовали анонимную переменную. Теперь запросы могут выглядеть так:
?- мать(X, Y).
X=анна
Y=юлия
Yes
?- мать(X).
X=анна
Yes
Пример: Определим отношение дедушка/2:
дедушка(X, Y) :- отец(X, Z), отец(Z, Y).
дедушка(X, Y) :- отец(X, Z), мать(Z, Y).
Эти правила утверждают, что дедушкой X для человека Y является отец человека Z, который в свою очередь является отцом или матерью человека Y.
Рекурсивные процедуры
Рекурсия в большинстве языков программирования - это такой способ организации обработки данных, при котором программа (процедура) вызывает сама себя непосредственно, либо с помощью другой программы (процедуры). Используя рекурсию как прием программирования мы должны быть уверены, что рекурсивная процедура будет завершена.
В Прологе рекурсия встречается, когда предикат содержит цель, которая ссылается на саму себя. В рекурсивном правиле более сложные входные аргументы должны выражаться через менее сложные.
На примере уже имеющейся у нас базы данных объясним преимущества использования рекурсии и особенности рекурсивных правил. Пусть имеются следующие факты:
больше(слон, лошадь).
больше(лошадь, осел).
больше(осел, собака).
больше(осел, обезьяна).
Выполним запрос к базе данных
?- больше(осел, собака).
Yes
Цель больше(осел, собака) была достигнута потому, что этот факт был сообщен Прологу при загрузке базы. Теперь проверим, больше ли обезьяна слона?
?- больше(обезьяна, слон).
No
Нет, не больше. Мы получили такой ответ, какой и ожидали: соответствующий запрос, а именно больше(обезьяна, слон) не подтвердился. Но, что случится, если мы зададим вопрос по-другому?
?- больше(слон, обезьяна).
No
Таким образом, слоны не больше, чем обезьяны. Полученный результат совершенно не согласуется с нашими представлениями о мире, но если посмотреть на базу данных, то легко заметить, что в ней действительно ничего не сказано об отношениях между слонами и обезьянами. Однако, мы знаем, что слоны больше, чем лошади, которые в свою очередь больше, чем ослы, которые больше обезьян, поэтому слоны также должны быть больше, чем обезьяны.
Правильная интерпретация отрицательного ответа, данного Прологом, такова: информации, сообщенной системе, недостаточно для доказательства того, что слон больше обезьяны. Если мы захотим получить положительный ответ на запрос вида больше(слон, обезьяна), то мы должны обеспечить более точное описание мира. Одним из возможных способов решения этой проблемы является добавление отсутствующих фактов, например,
больше(слон, обезьяна).
Для нашего маленького примера это означает добавление еще 5 фактов. Однако гораздо лучшим решением будет добавление в программу нового отношения, которое мы назовем больше_2. Животное X больше, чем животное Y, если это определено как факт (первое правило) или существует животное Z, для которого определен факт, что животное X больше, чем животное Z и может быть показано, что животное Z больше, чем животное Y (второе правило). На Прологе это запишется так:
больше_2(X, Y) :- больше(X, Y).
больше_2(X, Y) :- больше(X, Z), больше(Z, Y).
Если в цепочке участвуют не три, а большее число объектов, то придется добавить новые правила:
больше_2(X, Y) :- больше(X, Z1), больше(Z1, Z2),
больше(Z2, Y).
больше_2(X, Y) :- больше(X, Z1), больше(Z1, Z2),
больше(Z2, Z3), больше(Z3, Y).
Эта программа длинна и работать будет далеко не всегда. Она сможет просматривать базу данных только до определенной глубины, задаваемой максимальным количеством подцелей в правилах.
Поэтому воспользуемся более корректной и элегантной формулировкой. Ключевая идея здесь - определить отношение больше_2 с помощью его самого. Теперь второе (и последнее!) правило выглядит так:
больше_2(X, Y) :- больше(X, Z), больше_2(Z, Y).
Таким образом, итоговая программа будет иметь вид
больше_2(X, Y) :- больше(X, Y).
больше_2(X, Y) :- больше(X, Z), больше_2(Z, Y).
Обратите внимание на порядок подцелей во втором правиле: если их поменять местами, то в большинстве реализаций языка Пролог выполнение запроса к такой базе знаний приведет к сообщению об ошибке, аналогичному следующему:
ERROR: Out of local stack
Если теперь в запросе использовать предикат больше_2 вместо больше, то программа будет работать так, как и предполагалось:
?- больше_2(слон, обезьяна).
Yes
Интерпретатор всегда просматривает базу данных сверху вниз и слева направо. Поэтому он анализирует сначала первую фразу процедуры больше_2 и пытается унифицировать каждый аргумент запроса с соответствующим аргументом этой фразы. Это происходит при помощи сравнения запроса с началом правила больше_2(X, Y) (т.е. с его головой). После этого двум переменным присваиваются значения: X = слон и Y = обезьяна.
После конкретизации переменной некоторым термом это значение "закрепляется" за всеми случаями использования этой переменной в правиле. После унификации запроса с заголовком фразы интерпретатор переходит к обработке целей, содержащихся в теле этой фразы.
В данном случае Пролог не может найти в базе данных факта больше(слон, обезьяна) и переходит к рассмотрению второго правила. Оно гласит, что для того, чтобы получить ответ на вопрос больше_2(X,Y) (с фиксированными значениями переменных, то есть больше_2(слон, обезьяна)), Пролог должен ответить на два подвопроса больше(X, Z) и больше_2(Z, Y), опять же с соответствующими значениями переменных. Процесс просмотра базы знаний с самого начала повторяется до тех пор, пока факты, составляющие цепочку между слон и обезьяна, не будут найдены, а запрос успешно обработан.
Любая рекурсивная процедура должна включать, по крайней мере, по одной из ниже перечисленных компонент.
Нерекурсивную фразу, определяющую правило, применяемое в момент прекращения рекурсии.
Рекурсивное правило, первая подцель которого вырабатывает новые значения аргументов, а вторая - рекурсивная подцель- использует эти значения.