- •У.Клоксин, к.Меллиш программирование на языке пролог Для программистов и пользователей эвм. Предисловие редакторов перевода
- •Предисловие ко второму изданию
- •Предисловие к первому изданию
- •Глава 1 введение
- •1.1. Факты
- •1.2. Вопросы
- •1.3. Переменные
- •1.4. Конъюнкции
- •1.5. Правила
- •1.6. Заключение и упражнения
- •Глава 2 более детальное описание
- •2.1. Синтаксические правила
- •2.1.1. Константы
- •2.1.2. Переменные
- •2.1.3. Структуры
- •2.2. Литеры
- •2.3. Операторы
- •2.4. Равенство и установление соответствия
- •2.5. Арифметика
- •2.6. Общая схема согласования целевых утверждений
- •2.6.1. Успешное доказательство конъюнкции целевых утверждений
- •2.6.2. Рассмотрение целевых утверждений при использовании механизма возврата
- •2.6.3. Установление соответствия
- •Глава 3. Использование структур данных
- •3.1. Структуры и деревья
- •3.2. Списки
- •3.3. Принадлежность элементов списку
- •3.4. Пример: преобразование предложений
- •3.5. Пример: упорядочение по алфавиту
- •3.6. Использование предиката присоединить и спецификация деталей
- •Глава 4. Возврат и отсечение
- •4.1. Порождение множественных решений
- •4.2. Отсечение
- •4.3. Общие случаи использования отсечения
- •4.3.1. Подтверждение правильности выбора правила
- •4.3.2. Комбинация «отсечение-fail»
- •4.4. Проблемы, связанные с использованием отсечения
- •Глава 5 ввод и вывод
- •5.1. Ввод и вывод термов
- •5.1.1. Вывод термов
- •5.1.2. Ввод термов
- •5.2. Ввод и вывод литер
- •5.2.1. Вывод литер
- •5.2.2. Ввод литер
- •5.3. Ввод предложений
- •5.4. Чтение файлов и запись в файлы
- •5.4.1. Запись в файлы
- •5.4.2. Чтение файлов
- •5.4.3. Ввод программ
- •5.5. Объявление операторов
- •Глава 6. Встроенные предикаты
- •6.1. Ввод новых утверждений
- •Списковая форма записи
- •6.2. Выполнение и невыполнение целевого утверждения
- •6.3. Классификация термов
- •6.4. Работа с утверждениями как с термами
- •6.5. Создание структур и работа с компонентами структур
- •6.6. Воздействие на процесс возврата
- •Отсечение
- •6.7. Формирование составных целевых утверждений
- •Конъюнкция целей
- •Дизъюнкция целей
- •6.8. Равенство
- •6.9. Ввод и вывод данных
- •6.10. Обработка файлов
- •6.11. Вычисление арифметических выражений
- •6.12. Сравнение чисел
- •6.13. Наблюдение за выполнением программы на Прологе
- •Глава 7. Еще несколько примеров программ
- •7.1. Словарь в виде упорядоченного дерева
- •7.2. Поиск в лабиринте
- •7.3. Ханойские башни
- •7.4. Справочник комплектующих деталей
- •7.5. Обработка списков
- •7.6. Представление и обработка множеств
- •7.7. Сортировка
- •7.8. Использование базы данных: random, генатом, найтивсе
- •Генератор случайных чисел (random)
- •Генератор имен (генатом)
- •Генератор списков структур (найтивсе)
- •7.9. Поиск по графу
- •7.10. Просеивай Двойки, Просеивай Тройки
- •7.11. Символьное дифференцирование
- •7.12. Отображение структур и преобразование деревьев
- •7.13. Применение предикатов clause и retract
- •Глава 8. Отладка пролог-программ
- •8.1. Расположение текстов программ
- •8.2. Типичные ошибки
- •8.3. Модель трассировки
- •8.4. Трассировка и контрольные точки
- •Выдача информации о цели
- •Выдача информации о предшественниках
- •Изменение уровня трассировки
- •Вмешательство в процесс согласования цели
- •Другие команды
- •Заключение
- •8.5. Фиксация ошибок
- •Глава 9. Использование грамматических правил в прологе
- •9.1. Проблема синтаксического анализа
- •9.2. Описание синтаксического анализа на языке Пролог
- •9.3. Запись грамматических правил в Прологе
- •9.4. Присоединение дополнительных аргументов
- •9.5. Введение дополнительных условий
- •9.6. Заключение
- •Глава 10. Пролог и математическая логика
- •10.1. Краткое введение в исчисление предикатов
- •10.2. Приведение формул к стандартной форме
- •Этап 1 - исключение импликаций и зквивалентностей
- •Этап 2 - перенос отрицания внутрь формулы
- •Этап 3 - сколемизация
- •Этап 4 - вынесение кванторов общности в начало формулы
- •Этап 5 - использование дистрибутивных законов для & и #
- •Этап 6 - выделение множества дизъюнктов
- •10.3. Форма записи дизъюнктов
- •10.4. Принцип резолюций и доказательство теорем
- •10.5. Хорновские дизъюнкты
- •10.6. Пролог
- •10.7. Пролог и логическое программирование
- •Глава 11. Программные проекты на прологе
- •11.1. Простые проекты
- •11.2. Более сложные проекты
- •Приложение а. Ответы к некоторым упражнениям
- •Приложение в. Программа приведения формул исчисления предикатов к стандартной форме
- •Этап 1 - исключение импликаций
- •Этап 2 - перенос отрицания внутрь формулы
- •Этап 3 - сколемизация
- •Этап 4 - вынесение кванторов общности в начало формулы
- •Этап 5 - использование дистрибутивных законов для. & и #
- •Этап 6 - выделение множества дизъюнктов
- •Печать утверждений
- •Приложение с. Различные версии языка пролог
- •Синтаксис
- •Различные ограничения
- •Возможности окружения
- •Компиляция
- •Специальные встроенные предикаты
- •Средства отладки
- •Приложение d. Пролог для эвм dec system-10
- •Пример сеанса работы
- •Синтаксис
- •Различные ограничения
- •Возможности окружения
- •Компиляция
- •Различия во встроенных предикатах
- •Дополнительные встроенные предикаты
- •Средства отладки
- •Литература
- •Приложение е. Микро-пролог
- •Пример сеанса работы
- •Синтаксис
- •Различные ограничения
- •Возможности окружения
- •Специальные встроенные предикаты
- •Средства отладки
- •Литература
- •Приложение f. Система мпролог[19]
- •Пример сеанса работы
- •Синтаксис
- •Модульность
- •Компоненты системы мПролог
- •Различные ограничения
- •Дополнительные встроенные предикаты
- •Средства отладки
- •Литература
- •Примечания
8.3. Модель трассировки
Метод, используемый Прологом при попытках согласовать цели с базой данных, можно рассматривать с разных точек зрения. Мы уже познакомились с одной моделью описания этого метода, которую можно представить в виде «цепочки доказательств», проходящей через прямоугольники, изображающие цели. Теперь мы познакомимся с другой моделью описания работы Пролога, которая применяется во многих средствах отладки программ, таких как трассировка.Существованием этой модели мы обязаны главным образом нашему коллеге Лоренсу Бэрду. Вам следует изучить эту модель, прежде чем вы приступите к использованию средств отладки Пролога.
При использовании трассировки Пролог-система выводит на печать информацию о последовательности отработки целей, чтобы показать, какой стадии выполнения достигла программа. При этом, для того чтобы разобраться в том, что происходит, важно понять, когда и почему определенные цели выводятся на печать. В обычных языках программирования особый интерес представляют моменты входа в подпрограммы и выхода из них. Однако Пролог допускает написание недетерминированных программ, а это вносит сложности, связанные с механизмом возврата. Дело не ограничивается последовательностью входов в утверждения и выходов из них. Механизм возврата может неожиданно вновь запустить выполнение каких-либо утверждений, чтобы построить альтернативное решение. Кроме того предикат отсечения 'Г указывает для каких целей нельзя искать другие решения. Наибольшие трудности, с которыми приходится сталкиваться начинающим программистам, связаны именно с пониманием принципов работы механизма возврата: что на самом деле происходит, когда попытка согласования цели завершается неудачей и система неожиданно начинает возвратный ход. Мы надеемся, что этот процесс достаточно подробно описан в предыдущих главах. Впрочем, в предыдущих главах рассматривалась не только последовательность обработки целей, но также и то, как происходит конкретизация переменных, как цели сопоставляются с заголовками утверждений из базы данных, и, наконец, как согласуются с базой данных подцели. В модели трассировки выполнение Пролог-программ описывается в терминах четырех видов происходящих событий: CALL(ВЫЗОВ), ЕХIT(ВЫХОД), REDО(ПЕРЕДЕЛКА), FAIL(HEУДАЧА).
CALL
Событие CALL фиксирует начало попытки Пролога согласовать цель с базой данных. На наших диаграммах это обозначено стрелкой, входящей в прямоугольник сверху.
EXIT
Событие EXIT фиксирует момент, когда некоторая цель только что согласована с базой данных. На наших диаграммах это обозначается стрелкой, выходящей снизу из прямоугольника.
REDO
Событие REDO фиксирует момент, когда система возвращается к цели, пытаясь повторно согласовать ее с базой данных. На наших диаграммах это обозначается стрелкой, которая возвращается в прямоугольник снизу.
FAIL
Событие FAIL фиксирует момент, когда попытка согласовать цель с базой данных заканчивается неудачно. На наших диаграммах это соответствует выходу стрелки вверх из прямоугольника.
Средства отладки сообщают нам о моментах возникновения событий этих четырех видов в ходе выполнения наших программ. Эти события будут происходить со всеми целями, которые Пролог рассматривает во время выполнения программы. Чтобы можно было различать, к каким целям относятся происходящие события, каждой цели присваивается уникальный целочисленный идентификатор, который называется его номером обращения.
Обратимся к примеру. Рассмотрим следующее определение предиката потомок:
потомок(Х,Y):- отпрыск(Х,Y).
потомок(X,Z):- отпрыск(Х,Y), потомок(Y,Z).
Этот фрагмент программы находит прямых потомков некоторого лица по заданным в базе данных фактам отпрыск, например:
отпрыск(авраам,измаил).
отпрыск(авраам,исаак).
отпрыск(исаак,исав).
. . .
Первое утверждение программы указывает, что Y является потомком X если Y есть отпрыск X, а второе утверждение указывает, что Z является потомком X если Y есть отпрыск X и если Z является потомком Y. Рассмотрим вопрос:
?- потомок(авраам,Ответ), fail.
и проследим за ходом выполнения программы чтобы увидеть, когда возникают разные события указанных видов. Важно, чтобы вы попытались проследить за процессом трассировки, мысленно воссоздавая поведение цепочки доказательств, которая входит в прямоугольники, обозначающие цели и выходит из них. Время от времени мы будем представлять текущее состояние в виде диаграмм.
В заданном вопросе после первой цели следует fail. Это сделано для того, чтобы инициировать механизм возврата и тем самым, получить все возможные решения для цели потомок. Таким образом, в целом вопрос никак не может иметь положительного ответа. Однако цель нашей трассировки состоит в том, чтобы наглядно представить себе ход выполнения программы, вызванный несогласуемостью второй цели (fail).
В начале мы имеем прямоугольники, обозначающие две цели, в которые пока не входила стрелка, представляющая цепочку доказательств (см. рис. 8.1). Первое событие состоит в ВЫЗОВе (CALL) цели потомок. Это - обращение номер 1.
Риc. 8.1
(1) CALL: потомок(авраам,Ответ)
(2) CALL: отпрыск(авраам,Ответ)
Мы сопоставили с целью первое утверждение процедуры потомок и это привело к ВЫЗОВ цели отпрыск. В результате возникла ситуация, где стрелка движется вниз (см. рис. 8.2). Мы продолжаем:
Рис. 8.2.
(2) EXIT: отпрыск (авраам,измаил)
Сразу успешно согласуется первое утверждение и следует ВЫХОД (EXIT) из цели.
(1) EXIT: потомок(авраам,измаил)
Таким образом, мы согласовали первое утверждение процедуры потомок.
(3) CALL: fail
(3) FAIL: fail
(1) REDO: потомок(авраам,измаил)
Затем мы пытаемся согласовать fail, и, как и следовало ожидать, эта попытка завершается НЕУДАЧЕЙ (FAIL). Стрелка возвращается из прямоугольника fail назад выше в прямоугольник потомок. Изображение этой ситуации приведено на рис. 8.3. Стрелка движется вверх. Продолжаем:
Рис. 8.3.
(2) REDO: отпрыск(авраам,измаил)
(2) EXIT: отпрыск(авраам,исаак)
Для цели отпрыск выбрано альтернативное утверждение. Поэтому стрелка может снова выйти вниз из этого прямоугольника.
(1) EXIT: потомок(авраам,исаак)
(4) CALL: fail
(4) FAIL: fail
(1) REDO: потомок(авраам,исаак)
И снова fail заставляет нас отвергнуть это решение и начать возвратный ход. Заметим, что это было совершенно новое обращение к fail (мы вошли в него заново «сверху»).
(2) REDO: отпрыск(авраам,исаак)
(2) FAIL: отпрыск(авраам,Ответ)
На этот раз мы не можем предложить другое сопоставление для цели отпрыск, и потому продолжаем возвратный ход, стрелка отступает вверх, покидая прямоугольник отпрыск.
(5) CALL: отпрыск(авраам,Y)
Здесь произошло следующее: мы выбрали второе утверждение процедуры потомок и выполнили совершенно новое обращение к отпрыск, соответствующее первой подцели (рис. 8.4). Стрелка теперь снова движется вниз. Продолжаем:
Рис. 8.4.
(5) EXIT: отпрыск(авраам,измаил)
(6) CALL: потомок(измаил,Ответ)
Это дает решение, с которым мы теперь уже рекурсивно вызываем потомок. Следует новое обращение к потомок.
(7) CALL: отпрыск(измаил,Ответ)
(7) FAIL: отпрыск(измаил,Ответ)
(8) CALL: отпрыск(измаил, Y2)
(8) FAIL: отпрыск(измаил,Y2)
(6) FAIL: потомок(измаил,Ответ)
У Измаила нет детей (в данном примере) поэтому в обоих утверждениях процедуры потомок подцель отпрыск завершается неудачей, что приводит к неудаче всей цели потомок.
(5) REDO: отпрыск(авраам,измаил)
Мы возвращаемся назад для выбора новой альтернативы.
(5) EXIT: отпрыск(авраам.исаак)
(9) CALL: потомок(исаак,Ответ)
(10) CALL: отпрыск(исаак,Ответ)
(10) EXIT: отпрыск(исаак,исав)
Запускаем новое обращение к потомок и попытка согласовать подцель отпрыск завершается удачно (рис. 8.5). Продолжаем:
(9) EXIT: потомок(исаак,исав)
(1) EXIT: потомок(авраам,исав)
(11) CALL: fail
(11) FAIL: fail
(1) REDO: потомок(исаак,исав)
(9) REDO: потомок(исаак,исав)
Это дает окончательное решение исходного вопроса, однако fail вновь вынуждает включиться механизм возврата, поэтому мы возвращаемся назад по событиям REDO:
(10) REDO: отпрыск(исаак,исав)
(10) EXIT: отпрыск(исаак,иаков)
(9) EXIT: потомок(исаак,иаков)
(1) EXIT: потомок(авраам,иаков)
Для подцели отпрыск найдено другое сопоставление, которое порождает другой результат для исходной цели потомок. Уже сейчас можно заметить, что это последний потомок Авраама, однако еще остается выполнить определенный объем работы. Проследим далее за последовательностью событий по мере того, как механизм возврата заставляет нас отступать к началу.
(12) CALL: fail
(12) FAIL: fail
(1) REDO: потомок(авраам,иаков)
(9) REDO: потомок(исаак,иаков)
(10) REDO: отпрыск(исаак,иаков)
(10) FAIL: отпрыск(исаак,Ответ)
(13) CALL: отпрыск(исаак,YЗ)
Теперь мы пытаемся применить второе утверждение процедуры потомок.
(13) EXIT: отпрыск(исаак,исав)
(14) CALL; потомок(исав, Ответ)
Еще одна рекурсия
(15) CALL: отпрыск(исав,Ответ)
(15) FAIL: отпрыск(исав,Ответ)
(16) CALL: отпрыск(исав,Y4)
(16) FAIL: отпрыск(исав,Y4)
(14) FAIL: потомок(исав,Ответ)
(13) REDO: отпрыск(исаак,исав)
(13) EXIT: отпрыск(исаак,иаков)
(17) CALL: потомок(иаков,Ответ)
Пытаемся использовать Иакова.
(18) CALL: отпрыск (иаков,Ответ)
(18) FAIL: отпрыск (иаков, Ответ)
(19) CALL: отпрыск(иаков,Y5)
(19) FAIL: отпрыск (иаков, Y5)
(17) FAIL: потомок(иаков,Ответ)
(13) REDO: отпрыск(исаак,иаков)
(13) FAIL: отпрыск(исаак,YЗ)
(9) FAIL: потомок(исаак,Ответ)
(1) FAIL: потомок(авраам,Ответ) нет
Наконец мы закончили. Надеемся, что этот утомительный пример дал вам возможность понять последовательность событий, происходящих при выполнении Пролог-программы. Вы, вероятно, уже заметили, что для любой цели всегда бывает только один ВЫЗОВ (событие CALL) и одна НЕУДАЧА (событие FAIL), хотя может быть сколько угодно ПЕРЕДЕЛОК (событие REDO) и соответствующих ВЫХОДов (событие EXIT). В следующем разделе мы рассмотрим процесс трассировки для более сложного примера – предиката присоединить.
Упражнение 8.1. В приведенной выше модели ничего не говорится о том, как обрабатывается цель – отсечение '!'. Расширьте эту модель, включив туда учет действия отсечения.
