- •Глава 4
- •Введение
- •Три точки зрения на Пролог-программу
- •4. 1. Реляционный подход
- •Изображение отношений
- •Ограничения, обеспечивающие целостность, которые накладываются при записи отношений в программу
- •Ограничения, обеспечивающие целостность, накладываемые при выборке фраз
- •Реализация свойств отношений
- •Нерефлексивность
- •Симметричное и транзитивное отношение
- •Первая попытка
- •Вторая попытка
- •Запоминание списка посещенных мест
- •4. 2. Взгляд на программу с точки зрения потока данных Переход от выходного потока данных к программе
- •Упорядоченные множества ответов
- •Генерирование бесконечного множества
- •Экологический процесс
- •Версия программы, в которой применяется поиск с возвратом
- •Рекурсивная версия программы
- •Планирование производственных операций
- •Сильные и слабые стороны подхода к программе с позиций потока данных
- •4.3. Бихевиористический подход Приведенные ранее примеры, в которых проявился бихевиористический подход
- •Ограничение сферы действия эффектов поведения
- •Программа "найти_или_спросить"
- •Оценка бихевиористического подхода
- •Библиографические заметки
- •Упражнения
Оценка бихевиористического подхода
Одна из проблем, возникающих при проведении в жизнь бихевиористического подхода к программированию, заключается в том, что текст программы при этом получается сложным и не подчиненным "дисциплине" программирования. При таком подходе можно написать программу, которую будет столь же трудно понять, как и худшие в этом отношении программы, написанные на процедурных языках. Именно это произойдет, если без разбора пользоваться предикатами, обладающими побочными эффектами поведения. Реляционный подход и подход к программированию с позиций потока данных обладают большей "дисциплиной", что определяет более стройный стиль мышления программиста и в значительной степени защищает программу от появления запутанных конструкций. Следовательно, рекомендуется прибегать к бихевиористическому подходу только в тех случаях, когда два других подхода к программированию оказываются совершенно неадекватными. Если Вам все же придется воспользоваться бихевиористическим подходом, то попытайтесь ограничить сферы действия побочных эффектов, сводя их в процедуры общего назначения по типу процедуры "найти или спросить", и не включайте их во фразы, выполняющие специфические действия по решению конкретных задач.
И последнее предостережение: на программу, в которой широко используются побочные эффекты поведения, будут в большей степени оказывать влияние различия между версиями языка Пролог, и, следовательно, такую программу будет труднее переносить с одной версии Пролога на другую.
Библиографические заметки
Метод управления поиском при помощи списка посещенных узлов (как это делается в программе "тснр_путешествовать3") подробно исследуется в книге [79].
Идея создания процедуры "найти_или_спросить", приведенной в данной главе, возникла у автора после знакомства со средством опросить_пользователя языка APES [38].
Упражнения
1. Предположим, что факты базы данных "путешествие" (см. разд. 4. 1) содержат еще по одному аргументу, который задает расстояние в милях между двумя городами. Напишите программу, которая вычисляет наиболее короткий путь между двумя городами, при этом за основу возьмите текст процедуры "тснр_путешествоватьЗ"
Существуют два подхода к решению этой задачи Один подход основывается на рекурсии, а второй — на поиске с возвратом
Рекурсивное решение
При рекурсивном подходе применение предиката "findall" позволяет собрать в одном списке все ответы на запрос к программе "тснр_путешествовать3". Далее ответы сортируются, чтобы найти самый короткий маршрут Во многих версиях языка Пролог имеется встроенный предикат "keysort" ("сортировка по ключу"), первым аргументом которого является список структур "-/2", а вторым (выходным) аргументом служит вырабатываемый данным предикатом список структур "-/2", отсортированных по значениям первого аргумента Символ "-“ является обозначением инфиксной операции. Приведем пример запроса к предикату "keysort":
|?— keysort ([6-привет, 3-м (нью_йорк, самолет, лос_анжелес),
2- [один]]. Х).
Х = [2- [один], 3-м (нью_йорк, самолет, лос_анжелес),
6-привет]
Для того, чтобы воспользоваться предикатом "keysort", необходимо так изменить процедуру "тснр_путешествовать3", чтобы она через четвертый аргумент возвращала структуру "-/2". Первым аргументом этой структуры будет расстояние в милях, а вторым - структура "м", описывающая путь между двумя городами. Предположим, что автомобильный маршрут между Ньюарком и Бронксом имеет длину в 25 миль, а автобусный маршрут между Бронксом и Куинсом — 10 миль. Тогда при запросе к процедуре "тснр_путешествовать3" должен выдаваться такой ответ
|?— тснр_путешествовать3 (ньюарк, куинс, М).
М = 35-м (автомобиль, бронкс, м (автобус, куинс))
После проведения сортировки списка ответов на запрос к процедуре "тснр_путешествовать3" ответ, располагающийся в начале списка, будет соответствовать наикратчайшему пути.
Решение при использовании поиска с возвратом
При реализации поиска с возвратом в процедуру "тснр_путешествоватьЗ" следует добавить дополнительный аргумент, содержащий сведения о расстоянии (в милях) для определенного маршрута между двумя городами, Нужно написать промежуточную процедуру, которую можно, скажем, назвать "наилучший_маршрут". У этой процедуры будут те же аргументы, что и у процедуры "тснр_путешествовать3". Процедура "наилучший маршрут" будет вызывать процедуру "тснр_путешествовать3" как одну из своих подцелей. В процедуру "наилучший_маршрут" необходимо ввести глобальную переменную_факт, которую можно, например, назвать "оптимум/2". Употребление этой переменной будет во многом похоже на использование факта "текущ_сумма" в программе "итог_окл" из разд. 3. 11. Первым аргументом факта "оптимум" является структура "м", описывающая маршрут между двумя городами, а вторым аргументом - количество миль для этого маршрута. Первое, что должна сделать процедура "наилучший_маршрут", - это добавить факт "оптимум", содержащий аргумент с очень большим расстоянием, превышающим возможный предел расстояний для данной задачи, например:
оптимум (_, 100000).
Затем процедура "наилучший_маршрут" должна выдать запрос к процедуре "тснр_путешествовать3" и сравнить полученное в результате выполнения этого запроса количество миль со значением расстояния, которое содержится в факте "оптимум". Если количество миль, полученное в результате запроса, будет меньше, чем это расстояние, то процедура должна удалить факт "оптимум" и добавить вместо него новый факт, в котором будут содержаться сведения о расстоянии и о новом маршруте, полученные в результате запроса. Процедура "наилучший_маршрут" должна провести такой поиск с возвратом по всем ответам на запрос к "тснр_путешествоватьЗ". Тогда после исчерпания множества ответов самым коротким будет маршрут, описываемый текущим фактом "оптимум".
Какое из двух решений приведет к созданию более читабельной программы? При каком алгоритме решения будет достигнута более высокая скорость выполнения программы? В какой версии программы будут меньше потребности в памяти7 Какой подход к программированию на языке Пролог (реляционный, с позиций потока данных, бихевиористический) будет в наибольшей степени соответствовать применяемому методу решения в каждом случае?
2. В примере процедуры "найти_или_спросить" правило "можно_запрашивать" охватывает тот случай, когда исходный пункт и пункт назначения известны по фразе "путешествие", а вид транспорта неизвестен. Можно ли добавить другое правило "можно запрашивать", применимое к случаю, когда известны пункт назначения и вид транспорта, а исходный пункт неизвестен?
В представленном виде, процедура "найти_или_спросить" может работать с одной неизвестной величиной, входящей в запрос. Попробуйте написать новую версию программы "найти_или_спросить", в которой допускалась бы обработка более чем одной неизвестной величины в запросе К примеру, эта программа должна реагировать на запрос к фактам "путешествие", в которых известен вид транспорта, а исходный и конечный пункты неизвестны. Можете ли Вы привести примеры ситуаций, в которых найдет применение новая версия процедуры "найти_или_спросить"?