Text
                    Анатолий Адаменко
Андрей Кучуков
Логическое
программирование
и Visual Prolog
Санкт-Петербург
«БХВ-Петербург»
2003

Jd\ и.4/ I Ol.X A28 Адаменко A. H., Кучуков A. M. A28 Логическое программирование и Visual Prolog. — СПб.: БХВ-Петербург, 2003. — 992 с.: ил. ISBN 5-94157-156-9 Книга посвящена наиболее распространенному в мире языку логическо- го программирования Visual Prolog, предшественником и ближайшим "род- ственником" которого является широко известный Turbo Prolog. Рассматри- ваются математические основы логического программирования, история, идеи и методы этого направления науки, его применение в задачах искусст- венного интеллекта и экспертных системах. Описание Visual Prolog — языка и системы программирования, возможности которых значительно шире возможностей только лишь логического программирования — базируется на переводе фирменной документации Prolog Development Center (PDC). По следовательно и подробно рассмотрены вопросы установки системы, син- таксис языка, принципы, методы и особенности программирования, визуаль- ная среда разработки, методы стыковки с другими широко используемыми языками программирования, вопросы создания графического интерфейса и баз данных, визуальное, логическое, процедурное, объектно-ориентирован- ное и системное программирование на Visual Prolog. Прилагается компакт- диск, содержащий дистрибутив системы, упражнения и примеры из книги. Для программистов, студентов и преподавателей вузов УДК 681.3.06 ББК 32.973.26-018 Группа подготовки издания: Главный редактор Зам. главного редактора Зав. редакцией Редактор Компьютерная верстка Корректор Дизайн обложки Зав. производством Екатерина Ко идукова Анатолий Адаменко Анна Кузьмина Вячеслав Каштанов Ольги Сергиенко Зинаида Дмитриева Игоря Цырульникова Николай Тверских Лицензия ИД № 02429 от 24.07.00. Подписано в печать 27.12.02. Формат 70х1001/16. Печать офсетная. Усл. печ. л. 79.98. Тираж 3000 экз. Заказ № 963 "БХВ-Петербург", 198005, Санкт-Петербург, Измайловский пр., 29. Гигиеническое заключение на продукцию, товар № 77.99.02.953.Д.001537.03.02 от 13.03.2002 г. выдано Департаментом ГСЭН Минздрава России. Отпечатано с готовых диапозитивов в ФГУП ордена Трудового Красного Знамени “Техническая книга" Министерства Российской Федерации по делам печати, телерадиовещания и средств массовых коммуникаций. 198005, Санкт-Петербург, Измайловский пр., 29. и:\ 5-94157-156-9 © Адаменко А. Н.. ч. I—III. 2002 © Prolog Development Center А/S, ч. IV—V], прил. 1—2, 2002 © Оревков В. П., прил. 4, 2002 © Оформление, издательство "БХВ-Петербург", 2002
Содержание Предисловие..........................................................27 Введение в Visual Prolog.............................................29 Где может использоваться Visual Prolog?..............................29 Мир искусственного интеллекта....................................30 Системы базы данных..............................................30 Универсальная среда разработки...................................30 Как создавался Visual Prolog.........................................31 ЧАСТЬ I. МАТЕМАТИЧЕСКИЕ ОСНОВЫ ЛОГИЧЕСКОГО ПРОГРАММИРОВАНИЯ.........................................33 Глава 1. Дедуктивные системы ........................................35 Логический вывод и логическое программирование.......................35 Соотношение между содержательными и формальными теориями.............37 Геометрия Лобачевского и разбегающиеся галактики.................38 Формализация мышления и формальные системы.......................41 Кризис основ и программа обоснования математики..................41 Аксиоматический метод и формальные теории............................47 Логика и исчисление высказываний.....................................55 Логические операции над логическими переменными..................56 Алгебра логики...................................................59 Пропозициональные формулы...................................... 63 Логическое следствие и логический вывод..........................65 Метод резолюций..................................................70 Исчисление высказываний как формальная теория....................74 Правила вывода.................................................77 Логическое следствие и формальный вывод..........................81 Глава 2. Исчисление предикатов и теории первого порядка..............84 Отношение и предикат.................................................84 Кванторы.......................................................... 85 Язык логики предикатов...............................................87
Синтаксис языка исчисления предикатов................................92 Семантика исчисления предикатов......................................94 Эквивалентные преобразования формул..................................97 Исчисление предикатов первого порядка...............................100 Аксиомы.........................................................100 Правила вывода..................................................101 Свойства исчисления предикатов..................................102 Прикладные исчисления предикатов....................................104 Теории с равенством.............................................104 Теория частичного упорядочения..................................105 Формальная арифметика...........................................105 Глава 3. Логический вывод в исчислении предикатов...................107 Логическое следствие в исчислении предикатов........................107 Преобразование формул: предваренная форма.......................108 Преобразование формул: скулемовская и клаузальная формы..........ПО Логическое следование: правило резолюций........................111 Метод резолюций в логике предикатов.................................113 Унификация......................................................116 Алгоритм унификации.............................................118 Исчисление метода резолюций.....................................120 Секвенциальные исчисления и обратный метод С. Ю. Маслова............123 Секвенциальное исчисление.......................................124 Секвенциальное исчисление высказываний........................125 Секвенциальное исчисление предикатов (исчисление Генцена).....131 Обратный метод С. Ю. Маслова....................................132 ЧАСТЬ II. ЛОГИЧЕСКОЕ ПРОГРАММИРОВАНИЕ И ИСКУССТВЕННЫЙ ИНТЕЛЛЕКТ......................................... 137 Глава 4. Искусственный интеллект....................................139 Что такое искусственный интеллект...................................140 Логико-лингвистические модели в системах управления.................142 Искусственный интеллект и теория поиска вывода......................145 Современное состояние искусственного интеллекта.....................148 Экспертные системы.............................................. 149 Робототехника................................................... 150 Автономные агенты................................................ 152 Мозг как аналого-цифровое устройство............................154 Искусственная жизнь.............................................154 Чат-роботы......................................................155 Общение с человеком на естественном языке.......................155 Перспективы и тенденции развития искусственного интеллекта..........156 Рейтинг перспективных технологий................................157 Нейронные сети................................................157 Эволюционные вычисления.......................................157 Традиционные направления......................................157
Содержание 5 Военные технологии.............................................158 ЭВМ пятого поколения и ИИ в Японии.............................158 Перспективы логического программирования в ИИ..................159 Глава 5. Экспертные системы...................................... 161 Что такое экспертная система.......................................161 Области создания и применения ЭС...................................162 Общие принципы построения и функционирования ЭС....................163 Примеры ЭС.........................................................164 ЧАСТЬ III. ОСНОВЫ ПРОЛОГА..........................................165 Глава 6. Введение в Пролог.........................................167 Глава 7. Примеры решения задач на языке Пролог.....................177 ЧАСТЬ IV. ПРОГРАММИРОВАНИЕ НА VISUAL PROLOG........................195 Глава 8. Установка и начало работы в Visual Prolog.................197 Системные требования...............................................197 Процедура инсталляции..........................................198 Рекомендации по установке Visual Prolog............................199 Запуск Visual Prolog с CD-ROM......................................200 Обновление версии Visual Prolog....................................200 Запуск Visual Prolog...............................................200 Создание TestGoal-проекта для выполнения примеров..................201 Открытие окна редактора.......................,................203 Запуск и тестирование программы............................... 204 Тестирование примеров данного руководства......................205 Тестирование примеров в Test Goal..............................205 Комментарии к свойствам утилиты Test Goal....................205 Тестирование примеров как автономных исполняемых программ....206 Обработка ошибок.................................................. 207 Глава 9. Возможности Visual Prolog.................................208 Визуальная среда разработки (VDE)..................................208 Эксперты кода..................................................208 Эксперт приложений.............................................210 Интегрированные редакторы для подготовки ресурсов..............210 Возможность импорта ресурсов................................. 211 Текстовый редактор.............................................211 Интегрированный создатель подсказок............................211 Браузер исходного кода.........................................213 Разделение проектов и управление исходным кодом................213 Интерактивная справка Visual Prolog............................213 Интерфейс визуального программирования (VPI)...................214 Компоненты (пакеты) GUI высокого уровня........................214
6 Содержание Компилятор.......................................................214 Проверка соответствия типов компилятором.........................215 Отладчик.........................................................215 Обработка исключительных ситуаций и перехват ошибок..............216 Классы и объекты.................................................216 Переносимый код..................................................217 Открытая платформа...............................................217 Интегрированная возможность сборки...............................218 Подсистема баз данных............................................218 Клиент-серверная архитектура.....................................219 ODBC- и SQL-интерфейсы...........................................219 Инструментальное средство обработки документов...................219 Исходный код для интерпретатора Пролог...........................219 Использование компилятора Пролог.................................220 Исходный код для визуальной среды разработки.....................221 Экспертная система для текстовой анимации........................221 Эксперт меток штрихкодов.........................................221 Комплексные средства разработки программ Internet....................221 Интерфейс с сокетами.............................................222 Поддержка FTP....................................................222 Поддержка HTTP.................................................. 222 Поддержка CGI....................................................222 Поддержка ISAPI..................................................222 Глава 10. Основы языка Visual Prolog.................................223 Программирование в ЛОГике............................................223 Факты и правила................................................ 224 Факты..........................................................224 Правила........................................................225 Запросы...................................................... 225 Размещение фактов, правил и запросов.......................... 227 Переменные: общее представление..................................229 Краткий обзор....................................................229 От естественного языка к программам..................................230 Предложения......................................................230 Предикаты........................................................233 Переменные.......................................................234 Инициализация переменных.......................................235 Анонимные переменные...........................................236 Цели (запросы)...................................................237 Составные цели: конъюнкция и дизъюнкция........................238 Комментарии......................................................240 Сопоставление....................................................241 Программы Visual Prolog..............................................242 Основные разделы Visual Prolog-программ..........................242 Раздел предложений...............................................243 Раздел предикатов................................................243 Как объявить пользовательский предикат....................... 243
Содержание Имена предикатов................................................. 244 Аргументы предикатов..............................................245 Раздел доменов......................................................246 Раздел цели........................................................ 249 Декларации и правила....................................................249 Задание типов аргументов при декларации предикатов................252 Арность (размерность).............................................255 Синтаксис правил................................................. 255 Автоматическое преобразование типов...............................256 Другие разделы программ.................................................257 Раздел фактов...................................................... 257 Раздел констант.....................................................257 Глобальные разделы..................................................259 Директивы компилятора...............................................259 Директива include.................................................259 Резюме..................................................................259 Глава 11. Унификация и поиск с возвратом 264 Сопоставление и унификация.............................................264 Поиск с возвратом......................................................267 Поиск всех решений в Test Goal....................................269 Детальный обзор поиска с возвратом..............................272 Поиск с возвратом для внутреннего целевого утверждения..........276 Управление поиском решений........................................280 Использование предиката fail......................................280 Прерывание поиска с возвратом: отсечение..........................282 Использование отсечений.........................................282 Детерминизм и отсечение.........................................286 Предикат not.............................................. 286 О Прологе с процедурной точки зрения..................................291 Факты и правила в качестве процедур...............................291 Использование правил для условного ветвления....................292 Выполнение проверки в правиле...................................293 Отсечение как GoTo..............................................293 Возврат вычисленного значения...................................295 Резюме.....;..........................................................296 Глава 12. Простые и составные объекты........................... 298 Простые объекты данных..............................................298 Переменные как объекты данных................................. 298 Константы как объекты данных....................................298 Символы.......................................................299 Числа.........................................................299 Атомы.........................................................300 Составные объекты данных и функторы........................... 301 Унификация составных объектов...................................302 Использование знака равенства для унификации составных объектов.302 Использование нескольких значений как единого целого............303 Пример использования составных объектов..................... 303
8 Содержание Объявление составных доменов.................................. 307 Многоуровневые составные объекты..............................309 Определение составных смешанных доменов.........................311 Аргументы множественных типов.................................311 Списки........................................................312 Резюме.......................................................... 313 Глава 13. Повтор и рекурсия...........................................315 Процесс повторения.................................................. 315 Снова поиск с возвратом......................................... 315 Предварительные и последующие операции..........................317 Использование отката с петлями.................................. 319 Рекурсивные процедуры.............................................320 Преимущества рекурсии...........................................321 Оптимизация хвостовой рекурсии.................................. 322 Как задать хвостовую рекурсию...................................323 Из-за чего возникает не оптимизированная хвостовая рекурсия.....324 Использование аргументов в качестве переменных цикла............ 328 Рекурсивные структуры данных..........................................331 Деревья как типы данных...........................................332 Обход дерева.................................................. 333 Создание дерева............................................. 335 Бинарные поисковые деревья........................................337 Сортировка на основе дерева................................... 339 Резюме............................................................ 343 Глава 14. Списки и рекурсия...........................................345 Что такое список?................................................... 345 Объявление списков................................................346 Головы и хвосты.................................................346 Работа со списками............................................... 347 Использование списков.................................................348 Печать списков................................................ 348 Подсчет элементов списка...................................... 349 Хвостовая рекурсия.............................................. 351 Принадлежность к списку...........................................354 Объединение списков............................................ 355 Рекурсия с процедурной точки зрения.............................356 Предикат может иметь несколько вариантов использования..........357 Поиск всех решений для цели сразу.....................................358 Составные списки.................................................... 360 Грамматический разбор списков.....................................361 Резюме............................................................ 364 Глава 15. Внутренняя база фактов Visual Prolog 366 Объявление внутренней базы фактов...........................................366 Использование внутренних баз фактов.........................................368 Доступ к внутренней базе фактов.........................................368
Содержание 9 Обновление внутренней базы фактов...............................369 Занесение фактов во время выполнения программы................369 Считывание фактов из файла....................................370 Удаление фактов во время выполнения программы................ 371 Удаление нескольких фактов сразу..............................372 Сохранение базы фактов во время работы программы................373 Ключевые слова, определяющие свойства фактов....................373 Факты, объявленные с ключевым словом nondeterm................374 Факты, объявленные с ключевым словом determ...................374 Факты, объявленные с ключевым словом single...................375 Примеры использования внутренней базы фактов..................377 Резюме..............................................................379 Глава 16. Арифметические вычисления и сравнения.....................380 Арифметические выражения............................................380 Операции........................................................380 Порядок вычислений..............................................381 Функции и предикаты.................................................382 Генератор случайных чисел...................................... 383 Предикат random/1.............................................383 Предикат random/2.............................................383 Предикат randominit/1.........................................383 Целочисленная и вещественная арифметика.........................384 Функция mod/2.................................................384 Функция div/2............................................... 384 Функция abs/1.................................................385 Функция cos/1.................................................. 385 Функция sin/1...............................................385 Функция tan/1...............................................385 Функция arctan/1............................................386 Функция ехр/1...............................................386 Функция 1п/1............................................... 386 Функция log/1...............................................386 Функция sqrt/1.............................;................. 386 Функция round/1.............................................387 Функция trunc/1.............................................387 Функция val/2...............................................387 Сравнение...........................................................388 Равенство и предикат равенства..................................388 Сравнение символов, строк и идентификаторов.....................391 Символы.......................................................391 Строки.........................................................392 Идентификаторы................................................392 Глава 17. Более сложные приемы программирования.....................393 Анализ потока параметров............................................393 Составной поток параметров......................................394 Задание потоков параметров для предикатов.....................396
10 Содержание Контроль потока параметров........................................ 396 Ссылочные типы............................................ 397 Объявление ссылочных доменов................................ 398 Ссылочные типы и массив trail............................... 399 Применение ссылочных доменов.................................... 399 Ссылочные домены или поиск с возвратом....................... .401 Применение ссылочных типов при реализации бинарных деревьев.......402 Сортировка с помошью ссылочных типов....................... .....403 Функции и возвращаемые значения................................. 404 Управление детерминизмом в Visual Prolog.......................... 407 Система проверки режимов детерминизма Visual Prolog............... 410 Предикаты как аргументы.............................................412 Предикатные значения............................................ 412 Предикатные домены........................................... 414 Бинарные домены ........................................... ...419 Реализация бинарных термов............................. ...420 Текстовый синтаксис бинарных термов............................. 420 Создание бинарных термов........................................ 421 Доступ к бинарным термам...................................... 422 Унификация бинарных термов.................................... 422 Сравнение бинарных термов............................... ..422 Преобразование термов в бинарные термы.......................... 424 Модульное программирование........................................ ..425 Глобальные объявления ........................................ 425 Проекты................................................... 431 Ошибки и исключительные ситуации................................ 432 Обработка исключительных ситуаций и отслеживание ошибок.............432 Сообщения об ошибках.......................................... 435 Обработка ошибок при чтении термов.............................. ..436 Динамическое отсечение....................................... .438 Преобразование типов........................................ 439 Стиль программирования..................................... ...440 Правила эффективного программирования........................... 440 Использование предиката/ш/.................................... 442 Детерминизм и недетерминизм: использование отсечений................443 Глава 18. Классы и объекты................................................444 Инкапсуляция................................................ 444 Объекты и классы.............................................. 445 Наследование................................................ 445 Индивидуальность....................................... .........445 Классы Visual Prolog......................................... .....446 Объявления классов......................................... ...446 Реализация класса ............................................ 447 Экземпляры класса — объекты................................... 447 Уничтожение объектов ........................................... 449 Домены классов.................................................. 449 Производные классы и наследование................................... 450
Содержание 11 Виртуальные предикаты......................................................................................... 4H Статические предикаты и факты................................................................................ 4М Ссылка объекта на себя (предикат this)........................................................................ 456 Области видимости класса..................................................................................... 457 Классы как модули.........................................................................................457 Пользовательские конструкторы и деструкторы.................................................................... 458 Абстрактные классы..............................................................................................461 Защищенные предикаты, домены и факты............................................................................462 Управление доступом в производных классах.......................................................................462 Объектные предикатные значения................................................................................. 464 Объявления объектных предикатных доменов..................................................................468 Формальный синтаксис для классов................................................................................470 Глава 19. Запись, чтение и файлы............................................................................... 473 Запись и чтение.................................................................................................473 Запись....................................................................................................473 Примеры использования предиката write..........................................................475 Предикат write//*..................................................................................................478 Чтение....................................................................................................481 Предикат readin/1.................................................................................................481 Предикаты readint/1, readreai/1, readchar/1...........................................................481 Предикат readtem/2............................................................................................. 482 Предикат fde_str/2....................................................................................482 Передача двоичных блоков................................................................................. 484 Предикат readblock/2..................................................................................484 Предикат writeblock/2............................................................................... 485 Предикат file_bin/2...............................................................................................485 Файловая система в Visual Prolog................................................................................485 Открытие и закрытие файлов................................................................................486 Предикат openread/2...................................................................................486 Предикат openwrite/2..................................................................................487 Предикат openappend/2......................................................................................... 487 Предикат opentnodify/2.........................................................................................487 Предикат filemode/2...................................................................................487 Предикат closefde/1...................................................................................488 Предикат readdevice/1.......................................................................................... 488 Пред и кат writedevice/1.......................................................................................... 488 Переопределение стандартного ввода/вывода.................................................................489 Работа с файлами..........................................................................................490 Предикат fdepos/3.....................................................................................490 Предикат eof/1........................................................................................491 Предикат flush/1......................................................................................492 Предикат existfde/1...............................................................................................493 Предикат searchfile/3.................................................................................493 Предикат deletefile/1.................................................................................493 Предикат renamefde/1........................................................... ........ 494 Предикат disk/1...................... .494 Предика! coprfdc/J .... ОН
12 Содержание Атрибуты файлов........................................................ 494 Открытие и создание файлов........................................... 494 Имена файлов и путей..................................................... 496 Предикат filenamepath/3................................................ 496 Предикат filenameext/3............................................... 497 Поиск в каталогах........................................................... 498 Предикат diropen/3...................................................... 498 Предикат dimatch/10..................................................... 499 Предикат dirclose/1..................................................... 499 Предикат diifdes/I1......................................................500 Манипулирование файловыми атрибутами.........................................502 Предикат fdeattrib/2.................................................... 502 Управление термами в текстовых файлах....................................... 503 Работа с фактами как с термами...........................................504 Резюме...................................................................... 505 Глава 20. Обработка строк в Visual Prolog....................................508 Основные предикаты управления строкой...................................... 508 Предикатfrontchar/3................................................ 508 Предикат fronttoken/3.................................................. 509 Предикат frontstr/4..................................................... 510 Предикат concat/3...................................................... 510 Предикат strJen/2........................................................511 Предикат isname/I..................................................... 511 Предикат format/*........................................................511 Предикат subchar/3.......................................................511 Предикат substring/4.....................................................512 Предикат searchchar/3.................................................. 512 П ред и кат searchstring/3...............................................513 Преобразования типов........................................................ 514 Предикат charJnt/2.......................................................514 Предикат str_char/2..................................................... 514 Предикат str_int/2.......................................................514 Предикат str_real/2......................................................515 Предикат upper_lower/2...................................................515 Предикат 1erm_str/3.................................................... 515 Резюме.................................................................... 518 Глава 21. Внешние базы данных в Visual Prolog................................520 Соглашения об именовании........................... ‘........................521 Селекторы внешних баз данных............................................. 522 Цепочки......................................................................522 Домены внешних баз данных....................................................524 Числа-указатели в базе данных........................................ 524 Домен ref............................................................ 524 Предикат db_reuserefs/2.............................................. 525 Обработка баз данных....................................................... 525 Предикат db_create/3.....................................................525
Содержание 13 Предикат db_open/3.........................................................526 Предикат db_copy/3.........................................................526 Предикат db_openinvalid/3..................................................527 Предикат dbjlush/l.........................................................527 Предикат db_close/l........................................................528 Предикат dbjielete/1....................................................... 528 Предикат db_btrees/2.................... ..................................528 Предикат db_chains/2.................................................... 528 Предикат db_statistics/5............................................... ..529 Обработка цепочек........................................................... 529 Предикаты chain_inserta/5 и chain_insertz/5................................529 Предикат chain_insertafter/6............................................. 530 Предикат chainjterms/5.....................................................530 Предикат chain_delete/2....................................................530 Предикаты chainJlrst/З и chain Jast/3......................................531 Предикаты chain_next/3 и chain_prev/3......................................531 Обработка термов.............................................................. 531 Предикат term_replace/4....................................................531 Предикат term_delete/3.............................................. 531 Предикат refjterm/4........................................................532 В+ деревья............................................................... 535 Страницы, порядок и длина ключа............................................535 Двойные ключи........................................................ 536 Множественный просмотр.....................................................536 Стандартные предикаты для В+ деревьев.................................... 536 Предикаты bt_create/5 и bt_create/6.....................................536 Предикат bt_open/3......................................................537 Предикаты bt_dose/2n bt_delete/2........................................537 Предикат bt_copyselector............................................. 537 Предикат bt_statistics/8................................................537 Предикаты key_insert/4 и key_delete/4.................................. 538 Предикаты keyJlrst/3, key_Jast/3 и key_search/4....................... 538 Предикаты key_next/3 и keyj)rev/3...,...................................539 Предикат key_current/4................................................ 539 Программирование внешних баз данных............................................541 Просмотр базы данных.......................................................541 Вывод содержания базы данных............................................. 543 Создание защищенной базы данных........................................... 544 Обновление базы данных.....................................................545 Использование внутреннего указателя В+ дерева..............................549 Изменение структуры базы данных............................................550 Разделение файлов и внешние базы данных................................. 552 Домены разделения файлов................................................. 552 Открытие баз данных в режиме разделения....................................553 Транзакции и разделение файлов....................................... 554 Предикаты разделения файлов.............................................555 Программирование с разделением файлов......................................556 Реализация высокоуровневого блокирования...................................558
14 Содержание Полный пример разделения файлов.............................................559 Аспекты реализации разделения файлов в Visual Prolog........................564 Резюме....................................................................... 564 Глава 22. Программирование на системном уровне..................................566 Доступ к операционной системе............................................ .....566 Предикат system/l....................................................... 566 Предикат system/3................................................... 567 Предикат envsymbol/2................................................ 567 Предикаты time/4 и date/3/4....................................... ......568 Предикат comline/l.................................................... 569 Предикат syspath/2................................................ 569 Сервисы времени...................................................... .....569 Предикат sleep/]..................................................... 569 Предикат marktime/2.............................................. 569 Предикат timeout/]................................................... 570 Предикат difftime.............................................. 570 Предикат sound/2.................................................. 570 Предикат Ьеер/0....................................................... 571 Предикат osversion/l.................................................. 571 Предикат diskspace/2................................................ 571 Предикаты storage/З и storage/]]........................................ 571 Предикат storage/О.................................................... 571 Операции на уровне бит.................................................... 572 Предикат bitnot/2....................................................... 572 Предикат bitand/3................................................. 572 Предикат bitor/3........................................................ 572 Предикат bitxor/3................................................... 573 Предикат bitleft/3.................................................... . 573 Предикат bitright/3 ....................................................573 Прямой доступ к памяти.................................................... 574 Предикат ptr_dword ..................................................... 574 Предикаты memByte, memWord и memDword................................... 574 Резюме.................................................................... 575 Глава 23. Систематический обзор языка Visual Prolog.............................576 Имена................................................................. 576 Ключевые слова........................................................ 577 Специально определенные предикаты...................................... .577 Разделы программы............................................................. 577 Раздел доменов........................................................ 579 Сокращение объявлений доменов........................................ 580 Синонимы стандартных доменов......................................... 580 Списковые домены................................................. 580 Домены составных элементов с множеством альтернатив.................... 581 Домены составных элементов с единственной альтернативой.................582 Домены file и db_selection........................................... 582 Специально заданные предопределенные домены.......................... 583
Содержание 15 Объявление ссылочных доменов...................................583 Объявление предикатных доменов............................... 584 Раздел предикатов............................................... 585 Режимы детерминизма............................................586 Потоки параметров..............................................587 Функции....................................................... 588 Предикатные значения...........................................589 Объектные предикатные значения................................ 589 Раздел фактов....................................................589 Ключевое слово посору......................................... 590 Ключевое слово global..........................................590 Раздел предложений...............................................591 Простые константы..............................................591 Термы..........................................................593 Переменные.....................................................593 Составные объекты..............................................593 Раздел цели......................................................595 Раздел констант..................................................595 Предопределенные константы.....................................597 Условная компиляция..................................................597 Включение файлов в программу.........................................598 Модули и глобальные программные конструкции..........................599 Модули компиляции................................................599 Имена с глобальной областью видимости............................599 Структура многомодульных программ................................600 Включение всех глобальных объявлений в каждый модуль......... 600 Локальное включение глобальных объявлений......................601 Правила видимости для членов классов.............................602 Опции компилятора для многомодульных проектов....................603 Директивы компилятора............................................604 Директива check determ....................................... 604 Директива errorlevel...........................................605 Управление памятью в Visual Prolog...................................606 Размер стека.....................................................606 Размер глобального стека.........................................607 Размер кучи......................................................608 Экономия ресурсов памяти.........................................608 Глава 24. Интерфейс с другими языками.............................. 609 Использование DLL....................................................609 Вызов других языков из Visual Prolog.................................610 Объявление внешних предикатов....................................610 Соглашения о вызове и передаче параметров........................610 Входные параметры..............................................610 Выходные параметры.............................................610 Возвращаемые значения..........................................611 Множественные объявления.......................................611 Порядок расположения параметров................................612
16 Содержание Префиксный знак подчеркивания...................................612 Соглашение об именованиях для 32-битных Windows.................612 Преобразование имени к верхнему регистру (Pascal)...............613 Выравнивание указателя на стек..................................613 Использование объявления as.....................................614 Реализация доменов................................................614 Простые домены..................................................614 Составные домены................................................615 Многоальтернативные составные домены и структуры................615 Списки..........................................................616 О памяти............................................................. 617 Выравнивание памяти...............................................617 Распределение памяти............................................. 618 Выделение памяти заранее........................................620 Функция sizeof..................................................620 Функции tnalloc и free..........................................621 Примеры обработки списков.........................................622 Вызов Visual Prolog из других языков..............................624 Программа '‘Hello”..............................................625 Стандартные предикаты...........................................625 Вызов ассемблерной подпрограммы из Visual Prolog..................626 ЧАСТЬ V. РАЗРАБОТКА ГРАФИЧЕСКОГО ИНТЕРФЕЙСА ПОЛЬЗОВАТЕЛЯ................................ 631 Глава 25. Создание программы с графическим интерфейсом 633 Визуальное программирование на Visual Prolog..........................633 Начало работы с экспертом приложений..................................633 Использование команды Project | Rim............................ 635 Как изучать сгенерированный код...................................636 Окно проекта....................................................636 Сгенерированный исходный код....................................638 Дерево проекта..................................................638 Браузер исходного кода..........................................638 Основные "горячие” клавиши........................................639 Развитие приложения "Hello World”.................................640 Изменение меню в редакторе меню.................................640 Использование эксперта окон и диалоговых окон...................643 Создание окна "Cross Window"....................................646 Создание нового исходного модуля................................646 Как создать новое окно......................................... 649 Генерация кода для окна по умолчанию............................650 Проверка того, что окно реагирует на события.................. 654 Несколько операций рисования....................................656 Создание окна Sweep...............................................657 Создание модуля SWEEP.PRO...................................... 657 Создание нового меню (меню Sweep)...............................657 Создание растрового рисунка.....................................658
Содержание 17 Создание панели инструментов.....................................660 Создание окна....................................................662 Эксперт панели инструментов......................................663 Рисование с помощью мыши в окне Sweep............................667 Управление панелью инструментов..................................670 Окончание работы.................................................670 Изменение курсора мыши...........................................670 Установка "горячего" элемента для курсора........................672 Создание всплывающего меню.......................................673 Изменение цвета рисунка..........................................675 Окно часов.........................................................676 Окно изображения...................................................677 Создание окна дерева...............................................678 Работа с деревом.................................................680 Создание окна редактора............................................680 Выбор лексем.....................................................681 Вставка текста в редактор....................................... 681 Сохранение текущего текста.......................................682 Обработка буфера обмена............................................683 Печать............................................................ 683 Добавление элементов управления в окно часов.......................685 Использование списка...............................................689 Код для браузера каталогов.......................................690 Создание диалогового окна..........................................692 Эксперт диалогового пакета...................................... 694 Сгенерированный код..............................................697 Динамический обмен данными.........................................700 Глава 26. Средства создания графического интерфейса....................702 Управляемые событиями приложения.......................................702 Запуск VPI........................................................... 703 Окна...................................................................704 Типы окон..........................................................704 Стили окон.........................................................705 Клиентская область окна............................................707 Окно экрана (Screen)...............................................707 Окно задачи (Task).................................................707 Окна верхнего уровня (Top-Level)...................................708 Дочерние окна......................................................708 Элементы управления................................................708 Диалоговые окна...............................................1....708 Обработчики событий................................................709 Возвращаемые значения обработчика событий..........................709 Создание окон......................................................710 Создание обычного окна............................................710 Создание диалогового окна........................................711 Создание элемента управления.....................................711 Динамическое создание окон.......................................711
18 Содержание Уничтожение окон.................................................712 Доступ к клиентской области......................................713 Область отсечения................................................713 Доступ к внешней границе области.................................714 Вычисление клиентской области по внешней границе окна............714 Перемещение и изменение размеров окон............................714 Изменение состояния окна.........................................714 Блокировка и активизация окон..................................715 Скрывание и показывание окон...................................715 Сворачивание, разворачивание и восстановление окон.............715 Изменение пиктограммы, ассоциированной с окном...................716 Изменение текста окна или заголовка окна.........................716 Изменение обработчика событий....................................716 Связь данных с окном.............................................716 Доступ кокну Task.............................................. 716 Доступ к родительскому окну......................................717 Доступ к активному окну......................................... 717 Установка фокуса.................................................717 Доступ к окну, имеющему фокус....................................717 Упорядочивание окон..............................................717 Обновление окон..................................................717 Недостоверное окно.............................................718 Сделать достоверной часть окна.................................718 Определение необходимости перерисовки части окна...............718 Принудительная перерисовка окна................................718 События..............................................................719 Создание окон................................................... 719 Удаление окна....................................................720 Закрытие окна пользователем......................................720 Закрытие графического пользовательского интерфейса...............720 Активизация меню.................................................721 Выбор пункта меню................................................721 События от мыши..................................................721 Двойной щелчок кнопкой мыши......................................722 Перемещение мыши в окне .........................................722 Отпускание кнопки мыши...........................................723 Ввод с клавиатуры................................................723 Нажатие клавиши..................................................723 Отпускание клавиши...............................................723 Событие от горизонтальной полосы прокрутки.......................723 Событие от вертикальной полосы прокрутки.........................724 Получение фокуса.................................................724 Потеря фокуса....................................................724 Стирание фона.................................................. 724 Изменение положения окна.........................................724 Изменение размера окна...........................................725 Изменение состояния окна.........................................725 События GUI самой ОС.............................................725
Содержание 19 "Собственное" рисование...........................................726 Возврат размера элемента "собственного" рисования.................726 Перерисовка окна..................................................726 Истечение интервала времени таймера...............................727 Возникновение программируемого события............................727 Динамический обмен данными........................................727 События уведомления от элементов управления.......................727 Домен controljnfo...............................................728 События уведомления о завершении приложения.......................729 Активизация приложения............................................729 Деактивизация приложения..........................................730 Элементы управления...................................................730 Работа с элементами управления....................................730 Флаги стиля........................................................ ..731 События от элементов управления...................................737 Создание элементов управления.....................................738 Различные элементы управления.....................................739 Элемент группировки........................................... 739 Пиктограммы.................................................. 739 Статический текст.................................................739 Поля редактирования.............................................739 Командные кнопки................................................740 Флажки..........................................................741 Переключатели...................................................742 Списки..........................................................742 Предикаты для работы со списками................................742 Раскрывающиеся списки...........................................746 Редактируемые списки............................................747 Полосы прокрутки..................................................747 Обращение к полосе прокрутки....................................748 Диапазон полосы прокрутки.......................................749 Позиционирование полосы прокрутки...............................749 Соразмерность полосы прокрутки..................................749 "Собственное" рисование для элементов управления..................749 Специальные элементы управления...................................750 Диалоговые окна.......................................................752 Стандартные диалоговые окна.......................................753 Получение ответа пользователя на вопрос Yes/No и Cancel.........753 Выбор файла для открытия/сохранения.............................753 Ввод строки.....................................................754 Выбор из списка строк...........................................755 Выбор и настройка принтера......................................756 Выбор цвета.................................................... 756 Выбор шрифта....................................................756 Отображение ошибки..............................................757 Отображение примечания..........................................758 Диалоговые окна, определенные программистом.......................758 Идентификаторы элементов управления.............................758
20 Содержание Специальные идентификаторы ресурсов элементов управления.......759 Создание диалогового окна......................................759 Инициализация диалогового окна................................ 759 Операции рисования...................................................760 Инструменты рисования............................................760 Перья..........................................................761 Режимы фона....................................................762 Кисти..........................................................762 Шрифты.........................................................763 Режимы рисования...............................................763 Предикаты рисования..............................................764 Рисование пикселов.............................................764 Закрашивание области.......................................... 764 Изображение пиктограмм.........................................764 Изображение контуров фигур.....................................765 Изображение закрашенных фигур..................................765 Вывод текста.....................................................765 Размер текста................................................. 766 Цвет текста....................................................766 Работа со шрифтами...............................................767 Создание шрифта................................................767 Выбор шрифта...................................................767 Получение информации о шрифте..................................768 Работа с цветом......................................................768 Системы координат....................................................770 Диалоговые базовые единицы.......................................771 Масштабирование и панорамирование изображений........................772 Примеры отображения..............................................774 Печать............................................................. 775 Запуск задания на печать.........................................776 Конец задания печати.............................................776 Установка ориентации страницы....................................776 Начало новой страницы............................................776 Окончание печати страницы........................................777 Отмена задания принтеру..........................................777 Получение настроек печати........................................777 Курсор...............................................................777 Предикаты для работы с курсорами.................................778 Работа с мышью.......................................................779 Захват мыши......................................................779 События от мыши..................................................779 Прямоугольные области................................................780 Пересечение двух прямоугольников.................................780 Увеличение размера прямоугольника................................780 Проверка пустого прямоугольника..................................780 Перемещение прямоугольника.......................................781 Проверка, находится ли точка внутри прямоугольника...............781 Объединение двух прямоугольников.................................781
Содержание 21 Обработка событий....................................................781 События, находящиеся в очереди на обработку......................781 Отправка событий.................................................782 Ресурсы..............................................................783 Имена ресурсов...................................................783 Связывание ресурсов..............................................783 Растровые изображения, пиктограммы и курсоры.....................784 Диалоговые окна..................................................784 Меню.............................................................784 Строки...........................................................784 Редактор меню VDE....................................................785 Домен MENU...........................................................785 Определение действий для пункта меню.............................786 Загрузка меню из файла ресурсов..................................786 Изменение текста пункта меню.....................................786 Метки............................................................787 Включение/блокировка пунктов меню................................787 Принудительная перерисовка меню..................................787 Работа с подменю в MS Windows....................................787 Всплывающие меню.................................................788 Таймеры..............................................................788 Запуск таймера...................................................788 Остановка таймера................................................. 788 Изображения..........................................................789 Преобразование изображений в двоичные данные.....................789 Создание изображения операциями рисования........................789 Уничтожение изображения..........................................790 Рисование изображений........................................... 790 Рисование без масштабирования..................................790 Рисование с масштабированием................................. 790 Растровые операции.............................................790 Получение изображения из содержимого окна........................791 Загрузка изображения из файла....................................791 Загрузка изображения из сегмента ресурсов........................791 Получение размеров изображения...................................792 Сохранение изображения в файле...................................792 Вращение изображения.............................................792 Буфер обмена.........................................................792 Получение данных из буфера обмена................................793 Помещение данных в буфер обмена..................................793 Проверка данных в буфере обмена..................................793 Символы вставки......................................................794 Флаги атрибутов VPI..................................................794 Классы окон..........................................................799 Подклассы элементов управления...................................801 Реализация интерактивной справки.....................................801 Работа с метафайлами.................................................802 Загрузка метафайла из файла......................................802
22 Содержание Отображение метафайла в окне....................................802 Уничтожение метафайла...........................................802 Запись метафайла................................................803 Обработка ошибок....................................................803 Проверка правильности параметров................................803 Установка нового обработчика ошибок.............................803 Функции API операционной системы....................................804 Вызов внутренних функций GUI операционной системы...............805 Создание окна вне VPI...........................................805 Внутренние события API операционной системы для окна VPI............805 Получение дескриптора окна GUI операционной системы.............806 Получение контекста устройства GUI операционной системы.........806 Запуск внешних приложений...........................................806 ЧАСТЬ VI. ВОЗМОЖНОСТИ ВИЗУАЛЬНОЙ СРЕДЫ РАЗРАБОТКИ....................809 Глава 27. Особенности визуальной среды разработки для опытного пользователя...........................................811 Использование VDE...................................................811 Контекстные меню....................................................811 Ускоряющие клавиши..................................................811 Панель инструментов и строка подсказки..............................813 Строка подсказки....................................................814 Файл проекта........................................................814 Окно проекта....................................................... 815 Операции над компонентами проекта...............................818 Окно сообщений......................................................819 Браузер кода...................................................... 819 Браузер идентификаторов ресурсов....................................820 Дерево модулей проекта..............................................820 Глобальные опции и INI-файлы........................................823 Опции автоматического сохранения................................823 Шрифты..........................................................824 Опции окна сообщений............................................824 Другие опции....................................................825 Опции построения..............................................825 Опции представления......................................... 825 Опции редактора...............................................826 Каталоги внешних инструментов...................................826 Эксперт приложений..................................................827 Общие параметры.................................................827 Имя проекта...................................................828 Имя VPR-файла.................................................828 Основной каталог..............................................828 Поддержка систем сохранения исходного кода....................829 Параметры целевого файла........................................829 Платформа.................................................... 830 Пользовательский интерфейс....................................831
Содержание 23 Тип целевого объекта.............................................832 Основная программа............................................. 832 Опции VPI..........................................................833 Другие опции.......................................................834 Генератор кода................................................. 835 Каталоги проекта.................................................835 Каталоги внешних инструментов....................................838 Опции компилятора.............................................. 838 Окружение........................................................839 Пользовательская информация........................................839 Генератор справки................................................. 839 Файлы, используемые в проекте..................................... 840 Новые модули исходного кода........................................842 Опции диалогового окна File Inclusions for Module................843 Поддержка работы нескольких программистов над проектом в VDE...........844 PRJ-файл проекта.................................................. 844 Работа с файлами "только для чтения" в режиме работы нескольких программистов..................................................... 845 Автоматическая перезагрузка измененных файлов......................846 Сохранение ресурсов в отдельных файлах описаний ресурсов...........846 Изменение сохраняемых ресурсов.................................. 847 Файлы проекта, сохраняемые в системе контроля SCS..................848 Построение, компиляция и компоновка....................................848 Опции компилятора..................................................848 Опции генерации кода.............................................849 Выходные данные компилятора......................................850 Предупреждения компилятора.......................................852 Другие опции компилятора........................................ 854 Опции генератора кода..............................................855 Построитель программ...............................................856 Символы..........................................................857 Правила построения сценариев.....................................859 Локальные сценарии (правила).....................................860 Сценарий построения............................................. 860 Команды построения.................................................861 Команда Project | Compile Module.................................861 Команда Project | Build..........................................861 Команда Project | Rebuild All....................................861 Команда Project | Stop Building..................................862 Команда Project | Run........................................... 862 Команда Project j Link Only......................................862 Команда Project | Test Goal......................................862 Команда Resource | Build Resource Only...........................862 Редактирование ресурсов................................................863 Название ресурсов..................................................863 Связывание ресурсов................................................864 Растровые изображения, пиктограммы и курсоры.....................864 Диалоговые окна..................................................864
24 Содержание Меню.............................................................864 Строки...........................................................865 Импортирование ресурсов.......................................... 865 Эксперты кода........................................................ 865 Эксперт окон и диалоговых окон................................... 867 Генерация кода по умолчанию для окна.............................867 Просмотр кода, сгенерированного по умолчанию для окна............868 Обработка событий................................................869 Стили кода.......................................................870 Активизация......................................................872 Изменение исходного модуля.......................................872 Другие кнопки в эксперте окон и диалоговых окон..................872 Эксперт пакета диалоговых окон.....................................872 Метод установки..................................................873 Определять как...................................................874 Связывание переменных........................................... 874 Тип переменной...................................................875 Свойства полей редактирования, строковые значения................875 Свойства полей редактирования, числовые значения.................875 Элементы списков, редактируемых списков и раскрывающихся списков.... 876 Начальные значения для списков, редактируемых списков и раскрывающихся списков...........................................876 Свойства полос прокрутки.........................................876 Группы переключателей............................................877 Эксперт панелей инструментов.......................................878 Отладчик Visual Prolog.............................................879 Генерация отладочной информации....................................879 Запуск отладчика...................................................880 Загрузка программы.................................................881 Окна просмотра.....................................................882 Окно модулей.......................................................882 Окна исходного кода................................................883 Меню Run...........................................................884 Установка точек останова......................................... 885 Окно переменных....................................................886 Окно фактов...................................................... 886 Стек вызовов.......................................................886 Окно вызовов предиката trap........................................888 Окно точек отката..................................................888 Представления машинного уровня.....................................888 Дизассемблирование...............................................888 Память...........................................................889 Регистры....................................................... 890 Опции настройки отладчика..........................................890 Глобальные опции.................................................890 Стек вызовов.....................................................891 Дамп памяти......................................................891 Fonts.......................................................... 891 Изменение путей к исходным файлам..................................892
Содержание 25 ЧАСТЬ VII. ПРИЛОЖЕНИЯ................................................893 Приложение 1. Описание прикладных пакетов, облетающих создание VPI-программ................................................895 Пакет диалоговых окон................................................895 Создание и инициализация нового диалогового окна.................896 Обработчик событий для нового диалогового окна...................897 Получение возвращаемых значений от диалогового окна..............897 Домены диалоговых окон...........................................898 Включение/блокировка и отображение состояний элементов управления....898 Получение значений отдельных элементов управления из списка значений.899 Установка/получение значений элементов управления в диалоговом окне..899 Получение значений.............................................899 Установка новых значений.......................................900 Программный интерфейс редактора Visual Prolog........................900 Создание окна редактирования.....................................900 Получение текста из окна редактора...............................901 Добавление нового текста.........................................901 Функции редактирования...........................................901 Установка/получение выделенного текста...........................902 Возвращение информации позиционирования......................... 902 Перемещение знака вставки........................................902 Отмена/восстановление............................................903 Работа с буфером обмена..........................................903 Изменение регистра...............................................903 Вызов диалоговых окон редактора................................ 903 Гипертекстовые ссылки............................................903 Остановка/продолжение обновления редактора.......................904 Отображение/скрытие знака вставки................................904 Скрытие/отображение строки состояния.............................904 Установка цвета элементов языка Пролог......................... 904 Свойства редактирования..........................................905 Пакет окна сообщений.................................................905 Пакет панелей инструментов...........................................906 Создание панели инструментов.....................................906 Изменение размеров панели инструментов...........................906 Удаление панели инструментов.....................................906 Изменение значений на панели инструментов........................906 Создание переключателей на панели инструментов...................907 Пакет "собственных" элементов управления.............................907 Приложение 2. Примеры программ на языке Пролог.......................908 Построение экспертной системы........................................908 Задача выбора кратчайшего пути.......................................913 Приключения в опасной пещере.........................................914 Моделирование элементов аппаратуры...................................917 Ханойские башни......................................................918
26 Содержание Деление слов на слоги.................................................920 Задача расстановки У ферзей...........................................922 Приложение 3. Medication Assistant — медицина, основанная на доказательствах....................................................927 Общее описание проекта................................................927 Медицинская концепция.............................................928 Модель врачебных рассуждений .................................. 929 Описание клинической картины заболевания........................931 Клиническая фармакология и лекарственные средства...............932 Процедура назначения терапии....................................933 Регистрация данных в истории болезни............................934 Архитектура МЭСПР.....................................................934 Технология работы с МЭСПР........................................ 936 Функции прототипа программы (MedAssist).......................... 938 Интерфейс пользователя..........................................938 Главное меню и панель инструментов..............................939 Сектор отображения пути....................................... 939 Сектор редактирования свойств.................................. 939 Сектор просмотра............................................. 940 Сектор лекарственной терапии....................................940 Поддержка основных элементов работы врача.........................940 Навигация по базе данных пациента.............................. 948 Генерация медицинских документов................................949 Области применения................................................... 949 Клиническая практика..............................................950 Химиотерапия опухолей............................................ 950 Медицинское образование.......................................... 950 Телемедицина......................................................951 Медицинское страхование.......................................... 951 Приложение 4. В. П. Оревков. Обратный метод поиска вывода.............952 Общая схема обратного метода..........................................953 Дерево поиска доказательства........................................ 956 Допустимые списки формул и /’-наборы..................................958 Вспомогательные процедуры.............................................959 Исчисление благоприятных /’-наборов...................................963 Приложение 5. Описание компакт-диска................................ 966 Список литературы.....................................................967 Предметный указатель..................................................972
Предисловие Первоначально Пролог разрабатывался как язык для решения задач искусственного интеллекта, он и сейчас очень хорошо подходит для создания экспертных систем и программирования других задач этой области. Но нынешний Visual Prolog — язык, способный решать практически любые задачи современного программирования, система программирования, которая с успехом может применяться не только в тра- диционных для логического программирования приложениях. Тем не менее, корни этого языка находятся в области логического программирова- ния: имеется достаточно широкий круг задач, традиционно относящихся к задачам искусственного интеллекта, для которых первоначально предназначался, а впослед- ствии совершенствовался, язык Пролог (Программирование на основе Логики). Несмотря на то, что логическому программированию, так же как и языку Пролог, в свое время посвящались монографии и огромное количество журнальных статей, многие из которых уже стали библиографической редкостью, около 10 лет книги по этому направлению почти не издавались. Последняя, изданная большим тиражом, "солидная" работа, посвященная Прологу, — перевод книги Ц. Ина и Д. Соломона, "Использование Турбо-Пролога", — вышла в 1993 году (оригинал — в 1987 году). В предисловии авторы пишут: "В настоящий момент на рынке программного обес- печения появилось несколько реализаций Пролога, но они не идут ни в какое срав- нение с Турбо-Прологом компании Borland International, характеризующимся высо- кой скоростью работы и низкой стоимостью". Turbo Prolog — предшественник и ближайший родственник Visual Prolog, но за де- сять лет, с тех пор, как вышел перевод, и за пятнадцать как вышла книга, про- изошли огромные, если не сказать революционные, изменения в информационных технологиях. За последнее десятилетие выросло и вступило в самостоятельную профессиональную ^изнь новое поколение программистов, практически ничего не знающих о логиче- ском программировании и Прологе. С другой стороны, во многих наших вузах нача- ли преподавать основы этого подхода будущим специалистам по информационным технологиям. Для них, в первую очередь, и предназначена эта книга, содержащая основные све- дения, необходимые для понимания идей логического программирования, и перевод лакета фирменной документации — книг по Visual Prolog версии 5.2, для практиче- ского освоения языка и системы Visual Prolog.
28 Предисловие Книга (как и ее название) состоит из практически самостоятельных частей, связан- ных идеей логического программирования. Первые три части (автор к. т. н. А. Н. Адаменко) посвящены проблеме логического программирования в целом. В части 1 изложены теоретические основы, история возникновения, основные идеи и методы этого направления науки, находящегося на стыке программирования и математической логики, а именно теории дедуктивных систем и автоматизации доказательства теорем. В приложении 4 приведен материал, дополняющий эти части, посвященный обратному методу поиска логического выво- да С. Ю. Маслова, подготовленный доктором физ.-мат. наук В. П. Оревковым. В части //дано введение в предметную область искусственного интеллекта и экс- пертных систем. В качестве примера реальной экспертной системы в приложении 3 содержится описание медицинской экспертной системы, разработанной на Visual Prolog (авторы А. А. Темиров, В. А. Юхтенко, И. Е. Белов). В части ///рассматривается переход от содержательной постановки задачи к ее ло- гическому представлению и от логического представления к программе, описаны примеры решения задач на Прологе, приводятся листинги программ с объяснения- ми и подробными комментариями. Конечно же, эти части книги являются лишь введением в предмет логического про- граммирования и не претендуют на полноту изложения. Необходимую информацию, оставшуюся за рамками рассмотрения, читатель может почерпнуть в публикациях, приведенных в списке литературы, на которые даны необходимые ссылки. Содержание книги, начиная с части IV, посвящено системе и языку программиро- вания Visual Prolog. Оно основано на фирменной документации, предоставляемой фирмой Prolog Development Center (PDC) — разработчиком Visual Prolog. Научный редактор перевода — к. т. н. А. М. Кучуков, перевод подготовлен О. А. Громовой и К. Ф. Сергеевой. В приложениях / и 2 содержится дополнительный материал к этим частям книги: описание прикладных пакетов программ и примеры программ на Прологе (построе- ние небольшой экспертной системы, работа с текстом, решение некоторых извест- ных задач искусственного интеллекта). В приложении 5 дано описание прилагаемого компакт-диска. На прилагаемом компакт-диске размещен дистрибутив Visual Prolog Professional вер- сии 5.2, файлы с примерами программ и упражнения. Авторы выражают благодарность компании PDC за разрешение использовать доку- ментацию по Visual Prolog и программное обеспечение на прилагаемом CD-диске, В. П. Оревкову за специально подготовленное для этой книги приложение 4 ("Об- ратный метод поиска вывода'), Ю. П. Петрову (разд. “Кризис основ и программа обос- нования математики" гл. 1), А. А. Темирову, В. А. Юхтенко и И. Е. Белову (прило- жение 3 "Medication Assistant — медицина, основанная на доказательствах"), С. И. Боб- ровскому (разд. "Современное состояние искусственного интеллекта" и "Перспективы и тенденции развития искусственного интеллекта" гл. 4), разрешившим использовать их материалы, сотрудникам издательства "БХВ-Петербург", осуществившим подготовку к изданию, и В. П. Каштанову, проделавшему огромную работу по редактированию книги. Отзывы и замечания можно направлять по адресам электронной почты: adamenko@bhv.ru, ak@pdc.spb.su.
Введение в Visual Prolog Пролог является результатом многолетней исследовательской работы. Первая офи- циальная версия Пролога была разработана Аланом Кольмероэ (Alain Colmerauer) в Марсельском университете во Франции в начале 1970-х годов как инструмент для Программирования ЛОГики. В результате своего развития появился язык более мощный, чем даже такие хорошо известные сегодня языки программирования, как Pascal и С. Пролог известен как декларативный язык. Это означает, что при заданных необхо- димых фактах и правилах, Пролог будет использовать дедуктивные умозаключения для решения задач программирования. Эта его отличительная особенность выгодно контрастирует с традиционными процедурными языками, такими как С, Basic и Pascal. В процедурном языке следует задавать компьютеру пошаговый алгоритм ре- шения конкретной задачи, иными словами, программист должен заранее знать, как решить данную задачу. Пролог-программисту нужно предоставить только описание задачи и основные правила для ее решения. Таким образом, система Пролог предна- значена, в том числе, и для определения того, как найти необходимое решение. Пролог имеет ряд преимуществ по сравнению с процедурными языками программи- рования, вот некоторые из них: 3 для определенных задач программа на Прологе требует только одну десятую часть строк кода по сравнению с аналогичной программой на языке C++; 3 благодаря декларативному (в большей степени, чем процедурному) подходу, та- кие хорошо известные источники ошибок, как зацикливания, устраняются с са- мого начала; 3 Пролог ’’заставляет” программиста начинать с хорошо структурированного опи- сания задачи, поэтому он может использоваться и как средство создания специ- фикации, и как средство реализации продукта. Где может использоваться Visual Prolog? Пролог является очень важным инструментом в программировании приложений искусственного интеллекта и в разработке экспертных систем.
30 Введение в Visual Prolog Высокий уровень абстракции, легкость и простота в представлении сложных струк- тур данных, возможность моделировать логические отношения между объектами и процессами существенно облегчают решение задач в различных предметных облас- тях. По этой причине Visual Prolog широко используется для создания администра- тивных приложений, систем календарного планирования, Web-приложений, дня управления большими и сложными базами данных. Мир искусственного интеллекта Пролог берет свое начало в лабораториях искусственного интеллекта и изначально разрабатывался как язык искусственного интеллекта (ИИ), он прекрасно подходит для разработки экспертных систем и подобных приложений ИИ. Системы, основан- ные на фреймах или правилах, прямой или обратный логический вывод, системы сопоставления с образцом и системы вывода с ограничивающими условиями — все это естественные и изящные выражения базовой семантики Пролога. Используя Visual Prolog, вы можете эффективно программировать такие приложения, как базы знаний, экспертные системы, естественно-языковые интерфейсы и интеллектуаль- ные системы управления информацией. Клиенты PDC создают консультативные системы, средства поддержки принятия решений, диагностические средства, оболочки экспертных систем или приложения естественного языка для различных прикладных областей (банковское дело, авиа- ция, здравоохранение, страхование, медицина, промышленность и т. д.). Системы базы данных Сегодня имеется тенденция решать многие проблемы с использованием технологии баз данных, но такой подход часто приводит к невысоким результатам как на этапе разработки, так и на этапе реализации конечного продукта. Приложение, разрабо- танное на Прологе, может иметь и большую эффективность, и более дружественный интерфейс, наряду с меньшим временем разработки. Visual Prolog компании PDC хорошо справляется с решением традиционных задач ведения баз данных, потому что, помимо возможностей программирования, он имеет полноценный и легкий в использовании механизм поддержки баз данных. Универсальная среда разработки Вообще говоря, Visual Prolog компании PDC — это конкурентоспособная, универ- сальная среда разработки. Visual Prolog все чаще становится предметом выбора раз- работчиков из-за возможностей работы с развитой логикой и в то же время может выполнять те же задачи, что и системы баз данных SQL, системы разработки про- грамм C++, другие инструментальные средства, такие как Visual Basic, Borland Delphi или IBM Visual Age. Оптимизированный компилятор Visual Prolog обеспечи- вает высокую скорость работы приложений. Средства разработки Web-программ — новый, очень важный элемент Visual Prolog. Например, экспертные системы, написанные в Visual Prolog, могут быть подключе- ны к Web-страницам, что играет важную роль в отделах поддержки, системах тор- говли в Internet и некоторых других Web-технологиях.
Введение в Visual Prolog 31 Visual Prolog поддерживает объектно-ориентированный стиль программирования —- мощный инструмент моделирования, который является почти стандартом де-факто в таких языках, как Object Pascal, C++, Smalltalk и т. д. Все эти особенности делают Visual Prolog выгодной с коммерческой точки зрения инструментальной средой разработки программ. Искренне надеемся, что Visual Prolog, как средство разработки программ, окажется полезным инструментом и в вашей работе. Как создавался Visual Prolog Идеи для ряда компиляторов Пролога были рождены в Отделе Информатики Дат- ского Технического Университета, что в конечном итоге привело к созданию Visual Prolog. В 1982 году Том Остерби (Tom Osterby) разработал Пролог-интерпретатор для ком- пьютера VAX. В 1983—1984 годах Йорген Фишер Нильсон (Jurgen Fischer Nilsson) и Гери Нонфьял (Heri Nonfjall) разработали Пролог-препроцессор для компилятора Pascal, в котором представили типовую систему для Пролога и осуществили гло- бальный анализ потоков. В 1984 году Джон Гофман (Jon Hoffmann), Лео Йенсен (Leo Jensen) и Финн Грон- сков (Finn Grynskov) разработали Пролог-компилятор для IBM-PC. Рон Стодден (Ron Stodden), Джонатан Лервилл (Jonathan Lerwill), Клаус Витфелт (Claus Witfelt) занимались написанием документации. Начиная с 1993 года, под руководством Виктора Юхтенко в разработке Visual Prolog принимает непосредственное участие группа PDC в Санкт-Петербурге: Юрий Ильин (редактор и VPI), Борис Белов (визуальная среда разработки —VDE), Александр До- ронин и Сергей Мухин (компилятор и компоновщик), Борис Морозов и Михаил Зайченко (отладчик), Андрей Кучуков (интерактивная справка и обновление документации), Александр Бутовский (инструментальные средства). Возглавляют проект Visual Prolog Лео Йенсен, Томас Линдер Пулс (Thomas Linder Puls), Виктор Юхтенко и Юрий Ильин.
0 В истории науки бывают периоды, когда выше все- го ценится конкретное знание, когда везде торжест- вует эмпиризм и презираются абстрактные схемы. Затем наступают периоды интереса к теоретическим концепциям, обрастание которых плотью фактов откладывается "на ша'\ Эти периодические колеба- ния научного стиля и научной моды входят важной составляющей в колебательные изменения духовно- го климата общества, и можно проследить важные корреляции между различными проявлениями ука- занной волны. С. Ю. Маслов Математические основы логического программирования Г лава 1. Дедуктивные системы Глава 2. Исчисление предикатов и теории первого порядка Глава 3. Логический вывод в исчислении предикатов Зак, 963
ГЛАВА 1 Дедуктивные системы Общеизвестна та роль, которую играет дедукция в по- строении научных теорий. Это относится как к математи- ке..., так и к другим наукам с гораздо менее формализо- ванными дедуктивными средствами. С. Ю. Маслов Под исчислением понимают или составную часть некоторых разделов математики, определяющих правила вычислений и оперирования с объектами того или иного типа (дифференциальное, интегральное, вариационное исчисления), или дедуктив- ную систему. Дедуктивной системой называется способ задания множества путем указания исход- ных элементов (аксиом исчисления) и правил вывода, каждое из которых описыва- ет, как строить новые элементы из исходных. Используются различные термины для обозначения понятия дедуктивной системы: исчисление, формальная (аксиоматическая) теория (система). Это синонимы, хотя имеется некий скрытый подтекст: например, исчислениями называют наиболее важ- ные дедуктивные системы, — исчисление высказываний, исчисление предикатов. Когда некоторую неформальную (математическую или содержательную) теорию можно представить в виде дедуктивной системы, например арифметику или теорию множеств, то говорят о формальной теории — формальной арифметике или аксио- матической теории множеств. Когда говорят о языках программирования, предло- жения которых построены на основе строгих правил таким образом, чтобы для лю- бой последовательности символов можно было сказать, является она правильно по- строенной конструкцией этого языка или нет, то говорят о языке как о формальной системе. Логический вывод и логическое программирование С самого раннего детства мы начинаем рассуждать, причем основное средство на- ших рассуждений — высказывания и, основанные на них, логические выводы: "если будешь слушаться, то мама купит новую игрушку"., "если будет завтра у меня повы- шенная температура, то я не пойду в школу". Таким образом, если слушался маму, то можно рассчитывать на новую игрушку. А если температура не повышенная? Можно
36 Часть I. Математические основы логического программирования ли не идти в школу? Можно, при условии, что ты не готов к контрольной работе по математике, которая как раз сегодня. Напротив, стоит пойти в школу, если темпера- тура повышенная, но не очень, и будет контрольная работа, а ты к ней готов. Позже мы узнаем, что логика бывает разной, например мужской и женской, и что "то, что для русского здорово — для немца смерть". То есть все зависит от системы аксиом и правил вывода. У немца, по-видимому, если верить поговоркам, они иные, чем у русского, а у женщины они другие по сравнению с мужчиной. Если говорить математическим языком, то у них (у немцев и русских, как и у жен- щин с мужчинами) различные дедуктивные системы. Когда мы изучаем в школе геометрию, то мы изучаем евклидову геометрию, но гео- метрии бывают разные. Геометрия — это дедуктивная система, и в зависимости от системы аксиом, т. е. того, во что мы верим без доказательства, получаются различ- ные геометрии. Геометрия, в которой верна аксиома, что через точку, не принадле- жащую данной прямой, можно провести единственную прямую, параллельную дан- ной, называется евклидовой геометрией. Геометрия, в которой она не верна, — неевк- лидовой (геометрией Лобачевского, римановой геометрией). Известно, что луч света, идущий от звезды, искривляется, проходя вблизи другой звезды (гравитационной массы), и таким образом геометрия в космических масшта- бах не является привычной для нас евклидовой геометрией. Существуют и мирно уживаются волновая и квантовая теории света. Одна объясняет одни явления, другая — другие, и поэтому применяются они на практике в различ- ных приложениях, и ни у кого это не вызывает удивления. Теория гравитации А. Эйнштейна — общая теория относительности, исторически строилась как дедуктивная система; современные представления о гравитации по- казывают, что для построения такой теории требуется более сложная система ак- сиом [62]. В самом деле, окружающая нас действительность так сложна, что, по-видимому, не может существовать единой теории Вселенной, хотя мечта о ней будет двигателем все новых и новых физических теорий [12, 62]. В философии тоже существуют различные системы аксиом: что первично, материя или сознание, познаваем или не познаваем мир, существует абсолютный разум или нет? В зависимости от ответов на эти вопросы возникли разные философские тео- рии, различные мировоззрения, и сказать, какая философия правильней, какое ми- ровоззрение лучше, нельзя, если построены они на основе аксиом (постулатов), в которые мы верим, и внутренне они не противоречивы (т. е. нельзя путем логиче- ского вывода в этих теориях получить два взаимно противоположных высказыва- ния). Нельзя сказать, что одно логическое построение (теория, модель окружающего нас мира) лучше или хуже другого: бывает, что и женская логика вполне приемлема, особенно если тот, кто ей в совершенстве владеет, вам нравится (образец "женской логики" — измененное правило вывода modus ponens\ если из А следует В и очень хочется В, то А). Расстояние (метрика) в пространстве элементарных частиц, в нашей квартире и в космосе, поведение людей из первобытного племени и цивилизованных граждан подчиняются различным законам, т. к. соответствуют (осознанно или. неосознанно)
Глава 1. Дедуктивные системы 37 различным логическим построениям. Можно лишь говорить об общих правилах их построения, их общих свойствах и отличиях. Кроме того, важно научиться правильно делать выводы (говорят о логическом выво- де) в той или иной системе аксиом, раз уж мы им верим, и в той или иной системе правил вывода, раз уж мы ими пользуемся. В век компьютерных технологий компьютерная система, делающая выводы на осно- ве заложенных в нее знаний специалистов (экспертная система), стала способна решать не только простейшие логические задачи, но и быть основой важнейших практических приложений. Это могут быть задачи по определению болезней и спо- собов их лечения, поиску полезных ископаемых, диагностике неисправностей авто- мобиля, управлению посадкой самолета или ориентации космической станции. Эти и еще тысячи других задач повседневной жизни, практики и высокой науки могут быть решены на основе методов и программных средств, использующих логи- ческий вывод, т. е. при помощи логического программирования. Как писал Дж. Робинсон во введении к первому номеру журнала "Journal of Logic Programming" в 1984 г., в основе идеи логического программирования лежит описа- ние задачи совокупностью утверждений на некотором формальном логическом язы- ке и получение решения с помощью вывода в некоторой формальной (дедуктивной) системе. Логическое программирование (в широком смысле) представляет собой семейство таких методов решения проблем, в которых используются приемы логического вы- вода для манипулирования знаниями, представленными в декларативной форме [1, 36, 37]. Таким образом, логическое программирование является, по существу, верхушкой "айсберга" аксиоматического метода в науке (см. разд. "Аксиоматический метод и формальные теории" данной главы). В узком же смысле логическое программирование понимается как использование исчисления предикатов первого порядка в качестве основы для описания предметной области и осуществления логического вывода и называется хорновским или резолюци- онным [1]. Прежде всего, логическое программирование связывают с системами программиро- вания, основанными на использовании специальных классов логических формул {definite clauses — дословно — однозначных предложений, называемых хорновскими дизъюнктами или клаузами Хорна) и специальных методов логического вывода (вари- антов метода резолюций), — в качестве логической модели вычислений и способа исполнения логических программ. Самыми известными системами такого рода яв- ляются реализации языка Prolog, Programming in Logic (в русской транскрипции — Пролог, Программирование на основе логики), его вариантов и расширений. Соотношение между содержательными и формальными теориями К содержательным теориям относят физику, химию, биологию, географию, другие естественные науки, предметом изучения которых является "объективная реаль- ность, данная нам в ощущениях". К содержательным наукам, по-видимому, можно
38 Часть I. Математические основы логического программирования отнести и информатику. Цель этих наук: на основе наблюдений, практики, создания моделей окружающей реальности отделить ложное от истинного, от понимания пе- рейти к предсказанию или практическому использованию предмета исследований. Эта цель и процесс познания хорошо выражаются следующей цитатой, с которой невозможно не согласиться: "От живого созерцания к абстрактному мышлению и от него к практике — таков диалектический путь познания истины, познания объек- тивной реальности... Человек не может охватить — отразить = отобразить природы всей, полностью, он может лишь вечно приближаться к этому, создавая абстракции, понятия, законы, научную картину мира..." (Ленин В. И., Поли. собр. соч., т. 29, с. 163, 164). Критерием истинности, объективности содержательных теорий является практика. Однако практика всегда имеет дело с конкретными объектами и явлениями, поэтому она не может подтвердить или опровергнуть полностью какие-то представления. Истина, содержащаяся в научном знании содержательных теорий, всегда относи- тельна. Является ли математика содержательной наукой? Да, когда истина, получаемая в рамках математического подхода, применима на практике, дает правильный резуль- тат, подтверждается в прикладных исследованиях. Если же математика, развиваясь по своим законам и опережая потребности практики, создает собственные теории, вряд ли их можно назвать содержательными до тех пор, пока они не будут востребо- ваны на практике. Эварист Галуа, столкнувшись с содержательной проблемой решения в радикалах полиномиальных уравнений (содержательной в том смысле, что к этой проблеме приводят многие практические задачи), пришел к необходимости рассмотрения спе- циальных структур и, в результате, — к созданию новой области математики — тео- рии групп. Теория групп, математическая теория, созданная в начале XIX века, оказалась впо- следствии не только полезной для различных областей математики, но и примени- мой в физике элементарных частиц, благодаря которой появились многие современ- ные бытовые приборы. Исследование основ математики, начатое исключительно в целях обоснования са- мой математики как метода в науке, привело к созданию математической логики, теории формальных систем и теории алгоритмов, ставших основой создания компь- ютерной науки (computer science). Содержательность формальной теории заключена в ее интерпретациях. Это хорошо видно на примере развития евклидовой геометрии, которая стала, с одной стороны, "полигоном" для теории формальных систем, а с другой — основой для неевклидо- вых геометрий (геометрии Лобачевского, римановой геометрии). Г еометрия Лобачевского и разбегающиеся галактики "Большое видится на расстоянии", в пространстве и времени. Отвлечемся на время от темы математических основ логического программирования, чтобы взглянуть на проблему шире.
Гпава 1. Дедуктивные системы 39 Говоря о формальном и неформальном, невозможно не сказать о нашем представ- лении пространства и о теории, знакомой нам с детских лет. Геометрия, изучаемая в школе, представляет собой теорию привычного нам про- странства, "трехмерного евклидова пространства". Верна ли эта теория? Ответ очеви- ден — да. Единственно правильная ли это теория? Ответ для многих не очевиден: нет. Греческий математик Евклид написал примерно за 300 лет до нашей эры девять книг под общим названием "Начала". Большая часть нашей школьной геометрии заимствована из "Начал". Изложение геометрии в этом труде построено в виде стро- го логических выводов теорем из системы определений и аксиом (постулатов). Ак- сиомы и постулаты (какую разницу видел между ними Евклид, никто не знает) у Евклида разбиты на группы. Пятый постулат выделен гениально предусмотритель- ным Евклидом в отдельную группу и эквивалентен "аксиоме параллельных", кото- рую мы изучали в школе: "через точку вне заданной прямой можно провести одну и только одну прямую, параллельную данной". Многие великие (и невеликие) умы пытались доказать аксиому, т. е. логическим путем вывести ее из других аксиом, но, увы, безрезультатно. Попытки доказать эту аксиому привели в XIX веке к созданию других "неевклидо- вых" геометрий — геометрии Лобачевского и римановой геометрии. Интересна и поучительна история пятой аксиомы, аксиомы параллельности [77]. Омар Хайям (XI—XII вв.), больше известный как поэт, философ и астроном, заме- нил эту аксиому рядом других допущений и пришел к некоторым положениям со- временной неевклидовой геометрии. Следуя идеям Омара Хайяма, монгол Насир ад-Дин в XIII веке пытался доказать аксиому о параллельных. Его работой, уже в середине XVII века, пользовался Джон Валлис. Великий немецкий математик Карл Фридрих Гаусс (его портрет можно было, до появления евро, увидеть на немецкой валюте — марках) в начале XIX века уже вла- дел неевклидовой геометрией, но, по-видимому, не решался публично писать об этом: не хотел показаться смешным, чего впоследствии не испугался Н. И. Лоба- чевский, допуская неочевидное. В своих письмах к друзьям Гаусс писал об "осах", которые в него могут впиться, и о "криках беотийцев", которые раздадутся, если раскрыть его тайны (имелось в виду его знание неевклидовой геометрии). Первыми, кто не побоялся разрушить веру в очевидное, были Николай Иванович Лобачевский в 1826 году и венгр Янош Больяй (иногда его фамилию по-русски пи- шут Бояйи) в 1832 году, не знавший о трудах Лобачевского. Больяй получил положительный отклик от Гаусса (в частной переписке), но Гаусс при этом написал, что он знал об этом уже многие годы. После того, как работы Лобачевского были опубликованы на немецком языке (в 1840-м году), Больяй окон- чательно понял, что не он первым сделал это открытие, разочарование его было столь сильным, что он вообще прекратил занятия математикой (в истории открытий бывало и хуже: Роберт Скотт, который убедился, что пришел к Южному полюсу позже Руаля Амундсена, погиб на обратном пути из-за испытанного разочарования). В отличие от него Лобачевский, занимаясь и другими областями математики, всю жизнь продолжал развивать новую геометрию и бороться за ее признание.
40 Часть /. Математические основы логического программирования Противниками новой теории были и известнейшие математики (Буняковский, Ост- роградский), и просто "передовые’* люди того времени (знающий "что делать" — Чернышевский, например). Рецензент нашел теорию Лобачевского "плодом уродли- вого воображения", а труд Лобачевского объяснил "безрассудным желанием откры- вать новое, при талантах, едва достаточных для усвоения старого" [35, 77]. Геометрия Лобачевского отличается от евклидовой геометрии лишь одной аксиомой параллельности: через точку вне данной прямой может быть проведено более одной прямой, не пересекающей данную. Из этого допущения следует, что прямых, парал- лельных данной, будет бесконечно много. Вдумчивый читатель задаст вопрос: а не является ли эта теория противоречивой? Практичный читатель, привыкший во всем видеть пользу, спросит: "А зачем, собст- венно, все это нужно?" Ответ на оба эти вопроса заключается в доказательстве того, что эта формальная теория является содержательной, в построении соответствующей модели для этой теории. Формальное доказательство непротиворечивости было осуществлено с помощью построения интерпретаций (моделей) Бельтрами, Клейна и Пуанкаре. Какую практическую пользу можно извлечь из этой теории? Вот области, где эта теория пригодилась [35]: □ в первой своей работе Лобачевский, опираясь на впервые измеренные в те годы астрономами годичные параллаксы звезд, показал, что если в физическом про- странстве реализуется его геометрия, то в пределах Солнечной системы отклоне- ния от евклидовой геометрии будут на несколько порядков больше возможных ошибок измерений, т. е. геометрия космического пространства неевклидова^ □ Лобачевский, применяя свою геометрию в математическом анализе, в задаче на- хождения неопределенных интегралов, использовал в "своем" пространстве раз- ные системы координат, в результате чего нашел (или, как говорят, "взял") более двухсот до того "не бравшихся" неопределенных интегралов; □ Пуанкаре использовал геометрию Лобачевского для разработки теории авто- морфных функций, широко применяемой в других областях математики, в част- ности, при решении алгебраических уравнений выше четвертой степени; □ в космологии (науке о происхождении Вселенной) А. А. Фридман в результате решения уравнения Эйнштейна теоретически обосновал теорию расширяющейся Вселенной, что было подтверждено и наблюдениями астронома Э. Хаббла. Мо- делью такого пространства является пространство Лобачеве кого; □ геометрия Лобачевского используется в исследованиях столкновений ядерных частиц и в других вопросах ядерных исследований; □ зрительное восприятие близких областей пространства человеком порождает эф- фект обратной перспективы, объясняемый тем, что геометрия этих областей близка к геометрии Лобачевского (с радиусом кривизны, равным приблизительно 15 метрам) — физическая "очевидность" зрительного восприятия людей не зави- сит, конечно, от теории, но может подтверждать ее содержательность; □ геометрия Лобачевского стала образцом и важным этапом для разработки осно- ваний математики и выработки принципов современного аксиоматического под- хода.
Гпава 1. Дедуктивные системы 41 Таким образом, новая теория (для создания которой потребовалось два тысячеле- тия!) оказалась востребованной. Что первично, а что здесь вторично? В данном слу- чае в начале была теория, казавшаяся современникам Гаусса, Лобачевского, Больяй безумием. Формализация мышления и формальные системы Если геометрия является формализацией окружающего нас пространства, то матема- тическая логика и теория формальных систем, в целом, являются формализацией человеческого мышления и представления наших знаний. Может ли быть аксиоматизировано наше знание и процесс мышления? Можно ли построить алгоритм, позволяющий реализовать процесс извлечения ответов из зна- ний, формализованных в виде аксиом? Если предположить, что сделать это можно, то в результате мы получим формальный метод для получения неформальных ре- зультатов, виртуальную машину, на входе которой будут аксиомы очевидных знаний, а на выходе — теоремы, которые могут быть интерпретированы в терминах содержа- тельных и, возможно, неочевидных и практически полезных выводов. Логическое программирование, возникшее в эру ЭВМ как естественное желание автоматизировать процесс логических выводов, является ветвью теории формальных систем вообще и теории доказательств в частности. История возникновения логики, как математической дисциплины, как раздела ма- тематики, называемого математической логикой, следующая. В 1854 году Джордж Буль, преподаватель математики из Дублина, опубликовал труд под названием "Законы мысли", в котором было показано, что законы формальной логики, впервые описанные Аристотелем (силлогистики Аристотеля), могут быть предметом вычисления. Наука, получившая название алгебра логики, стала началом объединения математики и логики. Следующим этапом в этом направлении стал созданный в 1879 году немецким ма- тематиком Готтфридом Фреге труд "Begriffsschrift" ("Обозначение понятий") — уни- версальный язык, "...в котором можно было бы представить систематическим и ма- тематически точным образом любую возможную форму рационального мышления, которая стала бы частью дедуктивного рассуждения" [74], — та математическая сис- тема, которую мы называем исчислением предикатов. С этого времени начался период, названный "логицизмом": период обоснования математики, основных ее понятий, таких как функция, бесконечность, множество, уже в чисто логических терминах. Кризис основ и программа обоснования математики Так как в математическом анализе идет речь о функциях1, определенных на беско- нечном множестве значений аргумента, то необходимой предпосылкой обоснования математического анализа должно было стать исследование свойств бесконечных 1 Раздел написан по материалам Петрова Ю. П. [64].
42 Часть I. Математические основы логического программирования множеств. Это исследование было выполнено немецким математиком Георгом Кан- тором. От работ Г. Кантора (публиковавшихся начиная с 1872 г.) ведет свое начало теория множеств, лежащая в основе современной математики. Уже во времена Га- лилея было подмечено, что бесконечные множества обладают особыми свойствами, не сводящимися к свойствам множеств конечных. Так, для них несправедливо по- ложение о том, что целое больше своей части. Галилей рассматривал (в 1638 г.) во- прос: каких чисел больше — квадратов натуральных чисел или же всех целых чисел вместе — квадратов и не квадратов? С одной стороны, ясно, что множество квадра- тов является лишь частью множества всех целых чисел, с другой, поскольку каждое натуральное число можно рассматривать как квадратный корень из некоторого квадрата, то между каждым квадратом и каждым натуральным числом можно уста- новить взаимно однозначное соответствие. Тогда уже нет основания утверждать, что целых чисел больше, чем квадратов натурального ряда. Галилей объяснял эти за- труднения тем, что "рассуждая нашим ограниченным разумом о бесконечном, мы приписываем ему свойства, известные нам по вещам конечным и ограниченным. Между тем это неправильно, т. к. такие свойства, как большая или меньшая вели- чина и равенство, неприменимы к бесконечному, относительно которого нельзя ска- зать, что одна бесконечность больше или меньше другой". Следующий шаг к познанию и изучению бесконечного был сделан Георгом Канто- ром, который ввел новое важное понятие — мощность множества. Два множества — по Г. Кантору — имеют одинаковую мощность тогда, когда между их элементами можно установить взаимно однозначное соответствие, когда каждому элементу пер- вого множества может быть поставлен в соответствие элемент второго, и наоборот. Понятие мощности является обобщением понятия равенства на бесконечные мно- жества: два конечных множества будут иметь одинаковую мощность лишь тогда, когда число элементов первого равно числу элементов второго. Для конечных мно- жеств понятие мощности не вносит ничего нового по сравнению со старым поняти- ем числа элементов множества. Однако у всех бесконечных множеств число элемен- тов бесконечно, а вот мощности бесконечных множеств могут быть различными. Таким образом, используя понятие мощности, введенное Г. Кантором в 1878 г., мы получаем возможность анализировать различные бесконечности, сравнивать их меж- ду собой. Прежде всего, Кантор доказал, что одинаковую мощность имеют: □ множество всех натуральных чисел; □ множество положительных четных чисел; □ множество чисел — квадратов натуральных чисел; □ множество всех рациональных чисел — целых и дробных. Все эти множества Кантор назвал счетными, поскольку их потенциально возможно пересчитать — поставить в соответствие числам натурального ряда. Однако сущест- вуют и множества существенно большей мощности — мощности континуума, Кан- тор доказал, что мощность континуума имеют, например, следующие множества: □ множество всех действительных чисел (рациональных и иррациональных); □ множество всех точек отрезка единичной длины; □ множество всех точек квадрата со стороной единичной длины.
Глава 1. Дедуктивные системы 43 Кантор доказал также, что существуют множества сколь угодно высокой мощности, и высказал гипотезу, что не существует множеств, имеющих мощность промежуточ- ную между мощностью континуума и мощностью счетного множества (знаменитая гипотеза континуума Г. Кантора). Отношение современников к исследованиям Г. Кантора было двойственным. Были математики, горячо поддержавшие Кантора с самого начала (например, Рйхард Де- декинд), а были и те, кто встретил идеи Кантора враждебно; особенно непримиримо выступал Л. Кронекер. Резкие нападки Кронекера стали одной из причин тяжелого заболевания Г. Кантора, который начиная с 1885 г. почти не печатал новых работ. Тем не менее, идеи Кантора завоевали математику, и к концу XIX века она уже прочно опиралась на теорию множеств как на свой основной фундамент. "Никто не может изгнать нас из рая, созданного для нас Г. Кантором", — провозгласил Д. Гильберт. В основе обучения математике в университетах теория множеств лежит и сегодня. Одно время казалось, что она действительно позволила выполнить честолюбивый план построения математики как логически безупречной науки, имеющей критерий истины внутри себя. Знаменитый французский математик (и философ) Анри Пуан- каре писал в 1900 г.: "Теперь (после появления теории множеств) математика пол- ностью арифметизирована. Мы можем сказать сегодня, что в математике достигнута абсолютная строгость". Развитие математики (и теории множеств в том числе) опро- вергло эти претензии Пуанкаре. Как раз на грани XIX и XX веков в теории множеств были обнаружены противоре- чия (парадоксы), не разрешенные целиком и до сих пор; они сделали сомнительной веру в абсолютную истинность и строгость математики самой по себе, без ее сопос- тавления с реальным миром. Рассмотрим в качестве примера один из парадоксов теории множеств, обнаружен- ный Бертраном Расселом в 1903 г. Большинство множеств не содержат само себя в качестве элемента. Так, множество всех натуральных чисел — это не натуральное число; множество всех людей земли — это не человек. Назовем такие множества обыкновенными. Однако существуют и необыкновенные множества, содержащие самих себя в качестве элемента. Таково, например, "множество всех мыслимых множеств", — оно само является множеством. Рассмотрим теперь множество всех обыкновенных множеств, назовем его множест- вом М. Каким будет это множество — обыкновенным или необыкновенным? Пред- положим, что оно необыкновенное. Тогда оно встречается среди своих элементов, а этого не может быть, ибо, по условию, элементами множества М являются лишь обыкновенные множества, первое предположение приводит к противоречию. Оста- ется второе предположение: множество М — обыкновенное. Но тогда оно не встре- чалось бы среди своих элементов, которые исчерпывают собой все обыкновенные множества. Следовательно, множество М должно быть необыкновенным, что опять приходит в противоречие с нашим условием. Первое и второе предположения при- водят к противоречию. Это противоречие (парадокс Б. Рассела) не является единст- венным; в теории множеств существуют и другие противоречия (парадокс Бурали- Форти, парадокс Ришара и т. п.). Таким образом, оказалось, что теория множеств, лежащая в основе современной математики, внутренне противоречива.
44 Часть I. Математические основы логического программирования Этому не следует удивляться, если исходить из того, что математика, в конечном итоге, призвана отражать действительность внешнего мира и действительность на- шего мышления, а действительность в своих глубоких основах, как показал еще Ге- гель, всегда диалектически противоречива. Наличие противоречий в основаниях математики (в теории множеств) не подрывает и не должно подрывать доверия к истинности математических теорем. И в матема- тике критерием истины является опыт, практика. Не нужно только понимать этот критерий слишком прямолинейно и упрощенно. Единичный опыт, действительно, не может ни подтвердить, ни опровергнуть математической теоремы. Под критерием практики следует понимать человеческую практику в целом, в ее историческом раз- витии. А история математики показывает нам, что математические истины, как и истины других наук, развиваются, совершенствуются, уточняются (а иногда и опро- вергаются, т. е. оказываются не истинами) в ходе реальной деятельности людей. Обнаружение противоречий в основаниях математики опровергло лишь претензии на абсолютную строгость математики, единственным критерием истинности кото- рой являлось бы лишь отсутствие противоречий, а не опыт, не практика человечест- ва, взятая в целом. Рассмотрим (очень коротко) направления исследований по осно- ваниям математики в XX веке, после обнаружения парадоксов теории множеств. Наличие противоречий в основаниях математики, естественно, вызывало беспокой- ство и побуждало к напряженным исследованиям, тем более что логические следст- вия теории множеств приводили иногда и к весьма "странным" теоремам. Так, поль- ские математики С. Банах и А. Тарский доказали теорему о том, что сферу радиуса R можно разбить на конечное число таких неперекрывающихся частей, что переме- щая их как твердые тела и прикладывая их друг к другу соответствующим образом, можно получить новую сферу радиуса 2R (!), причем не теряется ни одной точки первой сферы и не вводится новых точек. Д. Гильберт писал в 1925 г.: "Надо согласиться, что состояние, в котором мы нахо- димся сейчас в отношении парадоксов, на продолжительное время невыносимо. По- думайте: в математике — этом образце достоверности и истинности — образование понятий и ход умозаключений, как их всякий изучает, преподает и применяет, при- водят к нелепости. Где же искать надежность и истинность, если даже само матема- тическое мышление дает осечку?" Обнаружение противоречий в основаниях логических рассуждений математики не оказало отрицательного влияния на доверие к практическим выводам и рекоменда- циям прикладной математики. В прикладной математике все выводы и рекоменда- ции, перед тем как быть переданными для использования, все равно всесторонне проверялись — даже в те времена, когда искренне верили, что безупречно проведен- ное математическое доказательство обеспечивает абсолютную строгость. Проверка была необходима уже потому, что доказательства проводят люди, а история матема- тики (и не только математики) много раз показывала, что человеку свойственно ошибаться и не допустить ни одной элементарной ошибки в длинной цепи логиче- ских рассуждений весьма трудно. Поэтому открытие диалектических противоречий в основаниях математики доверия к прикладным результатам не подорвало. В 20-х годах XX века работы А. Тарского по математической логике показали, что грамматики естественных языков (русского, английского, польского и т. д.) не обла- дают должной степенью однозначности для того, чтобы обеспечить абсолютную
Глава 1. Дедуктивные системы 45 строгость доказательства. Пусть некоторый математический текст, написанный на русском языке, переводчик А переводит на английский язык. Затем другой пере- водчик В переводит обратно на русский, новый переводчик С — переводит снова на английский и т. д. Даже если все переводчики квалифицированны и добросовестны, после достаточно длинной цепочки прямых и обратных переводов получим текст, сильно отличающийся от исходного. Цепочка прямых и обратных переводов подчеркивает и выявляет неполную одно- значность выражений, свойственную любому естественному языку. Таким образом, доказательство, сформулированное на любом из естественных язы- ков, уже по одному этому не может быть абсолютно строгим. Для преодоления ог- раничений, связанных с неоднозначностью естественного языка, были разработаны целиком формализованные языки математической логики. Описание математиче- ских теорий на этих языках, однако, является слишком сложным (и неблагодарным) занятием. Поэтому и до сего дня подавляющее большинство математических работ пишется на естественных языках, хотя это и не строго. Это обстоятельство подчер- кивает еще раз, что поиски абсолютной строгости бессмысленны. Обнаружение парадоксов теории множеств стимулировало развитие еще одного интересного направления — конструктивной математики. Представители этого на- правления отрицают законность применения в математике абстракции актуальной бесконечности (на этой абстракции, собственно, и основывается современная мате- матика). Признав незаконность актуальной бесконечности, представители конструк- тивной математики должны были тем самым отказаться от закона исключенного третьего вне сферы конечных множеств, отказаться от использования теорем суще- ствования и т. п. Все это резко осложняет математические построения. Можно ли обойтись в математике без понятия актуальной бесконечности — этот вопрос пока остается открытым. Однако новый подход к основам математики со стороны пред- ставителей конструктивного направления позволил им получить ряд новых и инте- ресных результатов. В области конструктивного направления успешно работали и продолжают работать российские математики — А. А. Марков (1903—1979 гг.), Н. А. Шанин (родился в 1919 г.) и др. Не менее интересными были исследования в области аксиоматики, аксиом геомет- рии и арифметики, начало которым положили работы Н. И. Лобачевского, оживив- шие интерес к аксиоматике геометрии. Подробный анализ показал, что аксиомы и постулаты, введенные Евклидом в попытке дать безукоризненно строгое обоснова- ние геометрии, не достигают поставленной Евклидом цели. Фактически Евклид при доказательствах своих теорем пользуется в неявном виде аксиомами, не включенны- ми в начальный список и нигде точно не сформулированными. Неудовлетворитель- ными были и определения Евклида (например, "точка есть то, что не имеет частей", "линия есть длина без ширины" и т. п.). В конце XIX века Пеано, Паш и, особенно, — Д. Гильберт через 24 века, прошед- ших с тех пор, произвели пересмотр определений и аксиом Евклида. Ими было предложено ввести первичные, не определяемые понятия — точка, прямая, плос- кость. Свойства этих понятий выражаются через систему аксиом. Д. Гильберт разделил предложенные им аксиомы на группы: аксиомы соединения, аксиомы порядка, аксиомы конгруэнтности, аксиома о параллельных прямых, экви- валентная пятому постулату Евклида, и аксиомы непрерывности.
46 Часть I. Математические основы логического программирования Система аксиом, сформулированная впервые Д. Гильбертом в 1899 году, лежит в основе современной (евклидовой) геометрии. К системе аксиом предъявляются следующие требования: □ полноты — любую теорему можно доказать, не прибегая к новым аксиомам, кро- ме уже сформулированных; □ независимости — никакую аксиому нельзя вывести как теорему из остальных; □ непротиворечивости — из аксиом нельзя вывести противоречивые следствия. Наиболее трудно доказать непротиворечивость системы аксиом. Именно эта про- блема возникла перед Лобачевским и Гауссом при попытках доказать непротиворе- чивость геометрии Лобачевского. В конце концов, Бельтрами и Клейн решили этот вопрос путем сведения его к другому: они доказали, что геометрия Лобачевского непротиворечива, если непротиворечива евклидова геометрия. Но возник вопрос — а сама евклидова геометрия противоречива или нет? Гильберту в начале XX века удалось сделать следующий щаг и доказать, что евкли- дова геометрия непротиворечива в той же мере, что и арифметика: если непротиво- речива арифметика, то непротиворечива и геометрия. Исследования Гильберта подвели прочный фундамент аксиом под геометрию. Те- перь встал вопрос — нельзя ли построить систему аксиом, на которой основывалась бы арифметика (а через посредство арифметики — и математический анализ)? Долгое время казалось, что такая система аксиом возможна. Тем неожиданнее ока- зался результат, полученный молодым австрийским математиком Куртом Геделем в 1931 году. Гедель показал, что для арифметики, как и для большинства других дисциплин (геометрия является исключением), невозможно составить полную систему аксиом потому, что всегда найдутся истинные теоремы, которые, тем не менее, нельзя фор- мально доказать, исходя из любой системы аксиом, — как бы мы эту систему ни расширяли. Гедель показал, что имеется различие между истинностью и выводи- мостью — существуют теоремы истинные, но не выводимые, не доказуемые в рам- ках данной системы аксиом. Наглядно результат Геделя может быть представлен следующим образом. Пусть мы взяли за основу некоторую систему аксиом и выводим из нее все возмож- ные следствия. Начиная от Евклида и до 1931 года математики были уверены, что эти следствия образуют некоторый ’’материк истины" и до любой точки этого мате- рика мы можем добраться "сухопутным путем", путем дедуктивного рассуждения из принятых аксиом. Гедель показал, что структура истины сложнее и напоминает ско- рее материк, окруженный островами. Существует бесконечное количество утвержде- ний истинных, но не доказуемых в рамках принятой системы аксиом, как бы мы ее ни расширяли. Эти утверждения образуют "острова истинности" за пределами дедук- тивного материка. Исследования Геделя существенно уточнили наши представления о математике. Они показали утопичность надежд на построение единого здания чисто дедуктивной, "безупречно-строгой" математики по образцу "Начал" Евклида. Еще одним уточнением наших представлений об основаниях математики явились работы Курта Геделя и американского математика Коэна в области гипотезы конти- нуума Георга Кантора.
Глава 1. Дедуктивные системы 47 Как уже отмечалось, в основе математики в целом и, особенно, в основе математи- ческого анализа лежит теория множеств. Одной из важнейших теорем теории мно- жеств является теорема о мощности континуума, сформулированная Г. Кантором (не существует множеств, имеющих мощность, промежуточную между мощностью счет- ного множества и мощностью континуума). Кантору не удалось доказать это важ- нейшее утверждение, поэтому его стали называть гипотезой континуума. Многие математики пытались доказать гипотезу континуума, но без успеха. Было предложе- но принять гипотезу континуума в качестве аксиомы и основывать на ней построе- ние теорем множеств. Гедель показал в 1939 году, что гипотеза континуума не про- тиворечит остальным аксиомам теорем множеств и поэтому можно строить теорию множеств, основываясь на аксиоме континуума. Окончательно проблема континуума была решена в 1960—1963 гг. американским ма- тематиком Коэном (родился в 1934, а в 1966 году был удостоен высшего отличия математика — медали Филдса). Коэн показал, что остальным аксиомам теории множеств не противоречит ни гипо- теза континуума, ни ее отрицание, т. е. допущение о том, что существуют множества промежуточной мощности, лежащие между мощностью счетного множества и мощ- ностью континуума. Ситуация с теорией множеств очень напоминает ту, которая сложилась в геометрии после исследования Лобачевского. Подобно тому, как существуют две логически непротиворечивых геометрии — Лобачевского и Евклида — и вопрос о том, какая из них правильнее отображает свойства действительного мира, должен решаться на основе критерия опыта, практики, точно так же существуют и две логически непро- тиворечивые теории множеств: в основе первой лежит гипотеза континуума, в осно- ве второй — ее отрицание. Можно думать, что в дальнейшем выяснится, какая из двух теорий множеств (тео- рия, опирающаяся на гипотезу континуума, окончательно переведенная в ранг ак- сиомы, или теория, опирающаяся на ее отрицание) "верна” и будет лежать в основе математики и математических доказательств. Аксиоматический метод и формальные теории Математическая логика... с момента своего возникновения была направлена на внематематические применения. Д. Буль, Г. Фреге, А. Уэйтхед, Б. Рассел и Д. Гильберт воспринимали математическую логику как науку, в некотором смысле прикладную — прилагающуюся к философии, логике, пси- хологии и философии математики... пожалуй, лишь один Пеано предполагал, что формализация рассуждений сама по себе будет способствовать получению новых результатов внутри математики. С. Ю. Маслов Под аксиоматическим методом [59] понимают способ построения научной теории, при котором за ее основу берется ряд основополагающих, не требующих доказа- тельств положений этой теории, называемых аксиомами или постулатами. Приме- ром может служить построение единой теории материи [12].
48 Часть I. Математические основы логического программирования Аксиоматический метод зародился в работах древнегреческих геометров. Блестящим и единственным образцом применения этого метода, вплоть до XIX века, была гео- метрическая теория, известная под названием "Начала Евклида" (около 300 г. до н. э.). В геометрии Евклида впервые проявилась основная идея аксиоматического метода: получение всего основного содержания теории (геометрии) дедуктивным путем, на основе доказательства теорем, исходя из небольшого числа утверждений — аксиом, содержание которых было, как казалось, очевидным. Не очевидной, как выяснилось, была лишь одна аксиома, выделенная Евклидом: пятый постулат о па- раллельных. Заменив эту аксиому ее отрицанием, Н. И. Лобачевский и Я. Больяй в начале XIX века, независимо друг от друга, открыли новую неевклидову геометрию. Оказа- лось, что, заменив всего лишь одну аксиому, можно получить, чисто логическим путем, стройную геометрию, не уступающую по богатству содержания евклидовой геометрии. Их открытие и стало отправной точкой для развития аксиоматического метода, который лег в основу теории формальных систем. Одновременно с накоплением опыта построения аксиоматических (формальных) теорий, появлением логически безупречного построения элементарной геометрии (М. Паш, Дж. Пеано, Д. Гильберт) и попытками аксиоматизации ’ арифметики (Дж. Пеано) — уточнялось понятие формальной аксиоматической системы, возник- ла теория доказательств. Теория доказательств — это раздел современной математи- ческой логики и предтеча логического программирования. В это же время возник метод интерпретаций, позволяющий устанавливать факты относительной непротиворечивости, т. е. суждения вида "если теория Tj непротиво- речива, то непротиворечива и теория Tj". Так была установлена непротиворечивость неевклидовой геометрии Лобачевского в предположении непротиворечивости евкли- довой геометрии; а вопрос о непротиворечивости гильбертовой аксиоматизации евк- лидовой геометрии был сведен к вопросу о непротиворечивости арифметики. Дальнейшее развитие аксиоматический метод получил в работах Д. Гильберта и его школы в виде так называемого формализма — научной программы по обоснованию непротиворечивости математики точным математическим способом. Программа предусматривала уточнение понятия доказательства с тем, чтобы оно само стало объектом математической теории доказательств. Чтобы сделать возможным точное рассмотрение доказательств, им придается единая точно определенная форма с по- мощью формализации теории: математическая теория заменяется формальной сис- темой. В рамках этого направления было уточнено понятие аксиоматической теории, воз- никло понятие формальной системы. В результате стало возможным представлять не формализованные до конца математические теории как точные математические объ- екты и строить теорию этих объектов — метатеорию. Формальная теория строится как четко определенный класс выражений, формул, в котором некоторым точным способом выделяется подкласс теорем данной фор- мальной системы. При этом формулы формальной системы непосредственно не не- сут в себе никакого содержательного смысла, они строятся из произвольных знаков или символов, исходя лишь из соображений удобства. На самом деле, способ по- строения формул (язык формальной системы) и теоремы формальной системы вы- бираются таким образом, чтобы они наиболее адекватно и полно отражали смысл и
Гпава 1. Дедуктивные системы 49 дедуктивную структуру математической или нематематической (содержательной) теории. Общая схема построения формальной системы (теории) Т следующая: 1. Язык системы Т: • определяется алфавит — перечень элементарных символов системы; • определяются правила построения формул (синтаксис) — формула считается правильно построенной тогда и только тогда, когда она построена в соответ- ствии с этими правилами. 2. Аксиомы системы Т. Выделяется конечное или перечислимое (определяемое с помощью некоторого алгоритма перечисления) множество формул, которые называются аксиомами сис- темы. 3. Правила вывода системы Т. Фиксируется (обычно конечное) множество предикатов Rb i > 0 на множестве всех формул. Если для формул /), ..., ///+] утверждение ЯД/), ..., Fn> Fn+\) истин- но, то говорят, что формула Fn+\ непосредственно выводима из F\, ..., Fn по пра- вилу Я/, что записывают: ' F\, Fn (Ri) Задание языка, аксиом и правил вывода исчерпывает задание формальной системы, как точного математического объекта. Понятие теоремы или выводимой формулы за- дается следующим образом: □ выводом в формальной системе Т называется любая конечная последовательность формул системы Т, в которой каждая формула является либо аксиомой системы Т, либо непосредственно следует из каких-либо предшествующих ей в этом выводе формул по одному из правил вывода Я, системы Т; □ формула системы Т называется теоремой этой системы, если существует вывод в формальной системе Т, заканчивающийся этой формулой. То, что некоторая формула /’является теоремой теории Т, записывают так: Выводом формулы F из формул (множества формул) F\, ..., Fn называется такой вы- вод, в котором могут присутствовать формулы из этого множества. Если такой вы- вод существует, то говорят, что /’(формально) выводима в теории Т из этих формул (множества формул), что записывают так: F\, ..., Fn |— F. Если ясно, о какой теории идет речь, то F], ..., Fn р F. Необходимость обоснования математики и конкретные задачи этой области привели к возникновению идей метаматематического (надматематического) характера, свя- занных с произвольной аксиоматической (формальной) теорией. К этим идеям от- носятся понятия и проблемы непротиворечивости, полноты и независимости систе- мы аксиом, лежащих в основе теории. В рамках построения аксиоматической теории Гедель доказал две теоремы о непол- ноте. Первая из теорем гласит: в любой непротиворечивой формальной системе,
50 Часть I. Математические основы логического программирования содержащей арифметические операции сложения, умножения, кванторы всеобщно- сти, существования и обычные операции обращения с ними, найдется замкнутая формула F (формула, в которой нет открытых, т. е. не связанных кванторами пере- менных, высказываний) такая, что ни F, ни 1 F не выводимы в этой формальной системе. Таким образом, любая, как говорят, достаточно "богатая” теория не может быть дос- таточно хорошо формализована. Несмотря на этот, казалось бы, обескураживающий результат, проведенные исследования имели большое значение для развития многих разделов математической логики. Рассматриваемые далее исчисление высказываний и исчисление предикатов отно- сятся к логико-математическим формальным теориям (формальным или дедуктив- ными системам). Их создание было связано с обоснованием методов построения математических теорий. Можно говорить о том, что эти формальные теории моде- лируют процесс наших умозаключений, процесс логического вывода, осуществляе- мого человеком. Основная их идея заключается в том, что имеется формальный язык, на котором может быть описан минимальный (необходимый) и достаточный набор высказываний, не требующих доказательств, и набор правил (вывода), с по- мощью которых строятся выводы — теоремы. Основная цель создания этих теорий — обобщение и ревизия накопленного матема- тического опыта. Начиная с евклидовой геометрии и арифметики и кончая совре- менными математическими дисциплинами, построение каждой из этих теорий, с одной стороны, основывается на наборе определений и аксиом, являющихся абст- рактным обобщением человеческого опыта, с другой стороны — на обобщении пре- дыдущего математического опыта. На основе накопленного опыта стало возможным формально математически описать и исследовать не только привычную классическую логику, но и не стандартные спо- собы рассуждения, другие логики. Кроме того, появилась возможность обобщить понятие логических исчислений (ис- числения высказываний и исчисления предикатов) на основе тех же принципов. С помощью набора аксиом, правил вывода и определения выводимости можно опи- сать не только множества выражений, интерпретируемых как высказывания, но и объекты произвольного типа. Рассмотрим несколько примеров такого описания. Пример 1 Игра в шахматы [33]. Множество допустимых шахматных позиций можно описать как формальную систему, в которой единственной аксиомой является начальная позиция, правилами вывода — правила игры, теоремами — позиции, полученные по правилам игры из начальной. Для того чтобы однозначно получить все позиции, достижимые из данной за один ход, недостаточно знать правила хода фигур. Чтобы превратить множество шахматных позиций в формальную систему, нужно для каж- дой позиции описать: □ чей ход; □ ходил ли раньше король; □ не был ли последний ход ходом пешки через два поля (для определения взятия пешки на проходе).
Глава 1. Дедуктивные системы 51 Кроме того, надо ввести правила, запрещающие игнорировать шах и понятие заклю- чительных позиций, после которых никакие ходы не разрешаются. Для того чтобы эту идею воплотить в формальную систему, нужно выбрать способ представления позиций (представления знаний), в результате чего могут быть полу- чены, вообще говоря, различные формальные системы. G Пример 2 Раскрой материала [51]. Можно записать в виде дедуктивной системы известную в математическом программировании задачу "о раскрое материала". Имеется материала длиной 100 см и Mi — длиной 120 см. Изделия, которые надо получить, имеют длины 80, 60 и 30 см. Состояния, в которых может находиться система, будем задавать пятеркой чисел т\, пз> где т\> ^2 ~ количества материала первого и второго типа, п\, п2, п3 — соответствующие количества уже произведенных изделий. Исходное состояние системы, задаваемое пятеркой М^, Mi, 0, 0, 0 запишем в виде 5(Л/Ь Mi, 0, 0, 0). Это и будет аксиома системы. Правила вывода таковы: * 1. 5(х + 1, у, z, и, у) |" 5(х, у, z + 1, и, у) — можно, используя первый вид материа- ла, на единицу увеличить количество изделий первого вида. 2 S(x + 1, у, z, и, у) |" S(x, у, z, и + 1, у + 1) — используя первый вид материала, можно увеличить на единицу количество изделий второго и третьего вида. ? 5(х + 1, у, z, и, у) |" 5(х, у, z, и, у + 3) — используя первый вид материала, мож- но произвести три изделия по 30 см. < S(x, у + 1, Z, и, у) |" 5(х, у, z + 1, и, у + 1) — используя второй вид материала, можно произвести одно изделие в 80 см и одно изделие в 30 см. 5. 5(х, у + 1, z, и, у) |" 5(х, у, z, и + 2, у) — используя второй вид материала, мож- но произвести два изделия по 60 см. S(x, у + 1, z, и, у) |" 5(х, у, z, и + 1, у + 2) — по этому правилу раскрой будет следующий: 120 = 60 + 30 + 30. 5(х, у + 1, z, и, у) |" 5(х, у, z, w, у + 4) — по этому правилу из второго вида ма- териала можно изготовить 4 изделия по 30 см. G Этот пример иллюстрирует возможность моделирования в виде исчисления системы, находящейся на каждом шаге в состоянии, из которого она может перейти по одно- му из правил вывода в следующее состояние. В отличие от алгоритма, моделирую- щего детерминированный процесс, этот процесс недетерминированный. Пример 3 Задача трассировки [51]. Рассматривается неориентированный граф, на ребрах кото- рого заданы неотрицательные числа: "пропускные способности". Задание на трасси-
52 Часть I. Ма тема тические основы логического программирования ровку — набор троек (А, В, а), где А и В — вершины графа, а — неотрицательное число, "толшина провода", которым требуется соединить А и В. Граф, представлен- ный такими описаниями, можно считать аксиомой некоторого исчисления. Правила вывода задают следующие преобразования информации. Если в графе есть ребро (А, В, а) и есть ребро (А, С, Р), где а > р, то можно: заме- нить на ребре а на а ~ р; тройку (А, С, р) на тройку (В, С, р), что можно записать в формальной теории следующим образом: В(А, В, а), В(А, С, р), (а > Р) Н Я(Л, В, а - р), R(B, С, р), Deleted, С, р)) Тройку вида (А, А, а) из задания можно удалить: Я(Л, А, а) |- Когда задание станет пусто (что обозначено символом С), то процесс вывода даст некоторую трассировку (способ прокладки проводов) в исходном графе. 6 Пример 4 Процесс функционирования человеко-машинной системы (ЧМС) может быть пред- ставлен в виде функциональной сети — ориентированного графа, имеющего одну начальную (begin), одну конечную (end) вершину и промежуточные вершины разно- го типа, определяющие один из возможных типов работ (операций), выполняемых техническим средством (машиной) или человеком (оператором). Дуги графа опреде- ляют последовательность переходов от одной работы к другой [18, 70, 85]. Каждой вершине ставится в соответствие набор вероятностно-временных характеристик: вероятность правильного выполнения работы, вероятность ошибок операций кон- троля, вероятность правильного решения при альтернативном выборе и среднее время выполнения операций. При проектировании структуры ЧМС, при оценке альтернативных вариантов построения системы, в других задачах эргономического проектирования необходимо оценить характеристики системы в целом по характе- ристикам составляющих ее элементов. Процесс количественной оценки на функциональной сети можно представить как вывод в соответствующей ей формальной системе [2, 3]. При этом аксиомами будут описания элементов сети на языке формальной системы, правилами вывода — пра- вила редукции функциональной сети, заключающиеся в том, что типовые структуры заменяются на эквивалентные им элементы. Редуцируя типовые структуры, в конеч- ном итоге получим оценку всей сети в целом. Например, для простейшей последо- вательной структуры двух типовых — рабочих элементов, правило сворачивания будет следующим: Ж у, Рь 'О, Ш z, р2, ti) h R(x, z, Р]хр2, ?|+Г2), Delete(A(x, у, рь Г0), Delete(A(y, z, Р2, ^)) Здесь R(x, у, р, f) означает наличие в функциональной сети рабочего элемента с именем х, характеристиками 3 и t и следующим за ним элементом с именем у; Delete — операция удаления описания элемента функциональной сети. Смысл этого правила заключается в том, что вместо описания двух последователь- ных элементов остается описание элемента, эквивалентного данной типовой после- довательности, с соответствующими характеристиками. Процесс вывода заканчива- ется, когда в результате вывода будет получен од ин-единственный элемент, эквива- лентный по своим характеристикам всей сети.
Гпава 1. Дедуктивные системы 53 Так же, как и в предыдущем примере трассировки, количество аксиом исчисления уменьшается на каждом шаге вывода, что не является характерным для классических формальных систем, но вполне оправдано в приложениях [22, 51]. Эта идея была доведена до программной реализации [3, 4] на языке Пролог СМ ЭВМ [53]. G Обобщением логических исчислений, нашедшим свое применение при описании языков программирования (с помощью формальных грамматик), стало каноническое исчисление Э. Л. Поста. Идея его заключается в том, что есть некоторый алфавит символов (в грамматиках различают терминальные — элементы самого языка и не- терминальные — элементы метаязыка — программа, цикл, идентификатор и т. д.), аксиомы и правила вывода, позволяющие заменять (переписывать) одни цепочки символов на другие. G Определение 1.1 Каноническим исчислением является четверка К = <Я, Р, А, П>, где Л и Р алфа- вит и переменные исчисления — непересекающиеся множества символов исчис- ления, А — список аксиом исчисления — слов из Л; П — список правил вывода, каждое из которых вида: So (Щ) где т > 1, S,- — слова в алфавите А о Р, все переменные из So (заключения) вхо- дят в слова S], ..., Sm (посылки правила ПД Слово (последовательность символов) Qo непосредственно выводимо из слов С1, , Qm п0 правилу Щ, если при подстановке вместо каждой переменной, вхо- дящей в Sy (т. е. буквы из алфавита Р), некоторого слова в алфавите А (значения переменной) каждое Sy превращается в 0у (/= О, 1, ..., т), что обозначают так: Оь ---J Qm F Со G Простейший пример — определяет (перечисляет) множество целых чисел. Пример 5 W W * * ** — — — — — — * Число можно определить количеством палочек ("зарубок"), обозначаемых как I. На- пример, два как II, четыре — 1111. Каноническое исчисление, порождающее любое натуральное число: А = {|}; Р = {%}; А = {|}; П = {х |— х|} G 2П 9 Более сложный пример [51] — исчисление, порождающее числа вида 2 , т. е. 2, 22, 24, 28 и т. д.
54 Часть I. Математические основы логического программирования Пример 6 Л = {I,*}; ? = U -У}; А = {||, I*П = {х, у X, х*у |- Я Посмотрим, что является теоремами этой формальной системы. 1. Применим к аксиоме |*| первое правило: р х|*ухх|, при х = | и у = |, т. е. за- меним х и >’ в правой части правила на |, получим: 11*1111 2. Используя аксиому || и слово, которое получили, применим второе правило: 3. К выведенному на первом шаге слову ||*|||| можно применить первое правило при х = ||, у = ||||, получим: 11*1111 F 111*111 ШШ 4. К полученному слову можно применить первое правило при х — |||, у = |||||||| 5. По второму правилу, из того, что получили и слова ||||, полученного на втором шаге вывода, сделаем вывод: НН. Н1И1ННННННН h IHHHHIHIIH Если записывать п символов | в виде |<Л>, то последовательность |*|, |(2)? |(2)*|(4)} |(3)*|(9)s |(4)*|(16), |(16) является выводом для |<16> в данном исчислении. Для наглядности процесс вывода записывают в виде "дерева": справа от "веток" ука- заны номера правил вывода: О Важнейшим примером дедуктивных систем является исчисление высказываний, ло- гико-математическое исчисление, определяющее (перечисляющее) множество тавто- логий, т. е. высказываний, составленных из элементарных высказываний при помо- щи логических связок и являющихся истинными, независимо от истинности или ложности составляющих их элементарных высказываний.
Глава 1. Дедуктивные системы 55 Логика и исчисление высказываний Высказывание, понимаемое как предложение естественного языка, может быть ис- тинным или ложным, хотя причина истинности или ложности высказываний бывает разной. Рассмотрим три примера высказываний на естественном языке. 1. "Курица является птицей". 2. "Любой человек жив до тех пор, пока не умер". 3. "Если из того, что был снегопад, следует, что крыша белая, то если крыша не белая, то снегопада не было". Способ (правила) записи некоторых выражений называют синтаксисом. В данном случае это синтаксис русского языка. Правила, по которым определяется смысл вы- ражений, их истинность (или ложность) называют семантикой. В данном случае все три выражения (предложения русского языка) являются истинными, но причина их истинности разная. Первое утверждение выражает некоторый, не зависящий от способа выражения факт — фактическая истина. Второе утверждение является истиной языка. Его истинность следует из смысла слов. Третье — содержит в себе высказывания ("был снегопад", "крыша белая") и связки "не" и "если ... то". Если заменить эти высказывания на другие, например, "волков надо бояться", "в лес нельзя ходить", то получим также истинное высказывание: "Если из того, что волков надо бояться, следует, что в лес нельзя ходить, то если в лес можно ходить, то волков не надо бояться". Если эти высказывания заменить переменными х и у, то предложение "если из х следует у, то из не у следует не х" будет логически истинным независимо от смысла (интерпретации) хи у Таким образом, истинность третьего утверждения заключена в его логическом по- строении. Это высказывание является логической истиной. Истинность или лож- ность выражений в математической логике может зависеть от интерпретации — смысла, приписываемого соответствующим элементам этих выражений, и от их (ло- гической) структуры. Причем наибольший интерес, конечно, вызывают те выраже- ния, которые истинны (ложны) вне зависимости от интерпретации — общелогиче- ские истины. Например, если считать истинными высказывания, что "Всем известный философ Сократ был русским ученым" и то, что "Все русские любят быструю езду", то из них будет (логически) следовать, что "Всем известный русский ученый Сократ любил бы- струю езду", несмотря на то, что каждое из исходных утверждений может быть оши- бочным (по крайней мере, не все русские любят быструю езду). Прежде чем перейти к формальному описанию исчисления высказываний как де- дуктивной системы, рассмотрим основные понятия алгебры логики (булевской ал- гебры). Она исторически возникла раньше, чем само понятие исчисления или фор- мальной системы. Логикой как наукой о правильных рассуждениях занимался еще Аристотель (384—322 гг. до н. э.), но как математическая наука она возникла лишь в середине XIX века, благодаря трудам Джорджа Буля.
56 Часть I. Математические основы логического программирования Логические операции над логическими переменными При изучении информатики (устройства компьютера, языков программирования) возникает понятие логической или булевской переме иной, т. е. переменной, которая принимает одно из двух возможных значений true или false (истина или ложь, 1 или 0), называемых логическими константами. Основные логические операции над логическими переменными: отрицание, конъ- юнкция (логическое "и"), дизъюнкция (логическое '’или”) и импликация (" если... то"). Знаки логических операций называют логическими связками, а выражения, которые получаются при использовании (правильном!) логических переменных и логических связок, — логическими или пропозициональными (от proposition — высказывание) фор- мулами. Замечание Логические операции называют также логическими (булевскими) функциями. Отрицание логической переменной А, обозначается 1 А, или подчеркиванием над буквой А, или -А (этот знак, правда, используется иногда для другой логической операции — эквивалентности): читается 'отрицание А" или "не А" и означает противоположное А значение: в случае, когда А принимает значение истина, А принимает значение ложь, и если А = false, то А = true. То же самое выражают так называемой таблицей истинности (табл. 1.1). Замечание J Здесь и далее в таблицах значение true обозначено буквой И — истина, значение false буквой Л — ложь. Таблица 1.1. Таблица истинности для отрицания А й л л и Как нетрудно заметить, двойное отрицание переменной равно самой переменной, что может быть выражено следующей формулой: 1 1 А = А. {Замечание J Знак равенства означает то, что таблицы истинности для выражения слева и выра- жения справа от знака равенства совпадают. Конъюнкция обозначается А & В, или А /\ В, или А В, по аналогии с умножением чисел, читается как "конъюнкция А и В" или "А и В" Конъюнкция двух логических переменных (табл. 1.2) истинна тогда и только тогда, когда истинны обе пере- менные.
Глава 1. Дедуктивные системы 57 Таблица 1.2. Таблица истинности для конъюнкции А в д&в Л л л Л и л И л л и и и Заметим, что А & В ~ В & А, т. е. при любых значениях переменных, входящих в левую и правую части формулы, значение выражения, стоящего слева от знака равенства, равно значению выражения, стоящего справа от этого знака. Кроме того, (А 8с В) 8с С = А 8с (В 8с С) ~ А & В & С. Скобки указывают порядок выполнения операций. Если порядок применения операций не изменит результата операции (функции), то скобки можно опустить. Дизъюнкция двух логических переменных обозначается A v В или А + В (читается "дизъюнкция А и В" или "А или В") ложна тогда и только тогда, когда ложны обе пе- ременные, при этом A v В ~ В v А (табл. 1.3). Таблица 1.3. Таблица истинности для дизъюнкции А в 00 л л л л и и и л и и и и Так же, как и для конъюнкции, имеет место свойство (A v В) v С = A v (В v Q — = A v В v С. Импликация обозначается А -> В или А В, читается "из А следует В", или "В следу- ет из А'\ или "если Л, то В", или "А достаточное условие (достаточно) для В", или "В необходимое условие (необходимо) для А". А называют посылкой (или антецедентом)} В называют заключением (консеквентом). А -> Сложна в одном только случае, когда А ~ true, В = false, в остальных случаях она истинна (табл. 1.4). Таблица 1.4. Таблица истинности для импликации А в CQ л л И л и и и л л и и и
58 Часть L Математические основы логического программирования ______Замечание J Заметим, что А -> В = 1 A v В, таблицы истинности у формулы слева и формулы справа от знака равенства совпадут. Если скобок нет, то порядок выполнения опе- раций определяется приоритетом: у отрицания он самый высокий, затем выполня- ются конъюнкция и дизъюнкция, потом импликация. Это можно проверить непосредственно с помощью таблиц или с помощью рассуж- дения: в соответствии с определением дизъюнкция справа от равенства ложна толь- ко в том случае, когда ложно 1 А и ложно В; А ложно, когда истинно А, следова- тельно, формула 1 A v В ложна только в одном случае, когда ложно А и истинно В, а в остальных случаях истинна. Это совпадает с тем, когда ложна и когда истинна импликация А В. Таким образом, операцию импликации можно было бы не вводить, а пользоваться вместо нее операциями отрицания и дизъюнкции. Относительно интерпретации логических операций нужно сказать следующее. Ин- терпретация первых трех логических операций не вызывает сомнений, т. е. они вполне соответствуют здравому смыслу. Правда, мы иногда используем разделяющее или; высказывание истинно только в тех двух случаях, когда одно высказывание ис- тинно, а другое ложно. Можно проверить, что таблица истинности для формулы (1 A v В) & (A v В) как раз соответствует такой логической функции. Таблица истинности для импликации определяется исходя из следующих сообра- жений. То, что из истинного значения посылки следует истинное значение заключения — истинно, а из истинного значения посылки следует ложное значение заключения — ложно, не вызывает интуитивного протеста. Непонятной обычно кажется истин- ность того, что из ложной посылки следует ложное заключение и из ложной посыл- ки — истинное заключение (первые две строчки таблицы истинности). Это можно объяснить так: из логических соображений значения истинности для А —> В и В —> А должны отличаться (они логически не эквивалентны), а значения истинности для А В иД В А должны совпадать (они логически эквивалентны). Поэтому, исхо- дя из интуитивно ясных третьей и четвертой строчек таблицы истинности и этих соображений, однозначно определяются первая и вторая строчки табл. 1.4. В то же время импликация не выражает причинно-следственных связей между вы- сказываниями. Если вместо А подставить высказывание "Дважды два равно четырем", а вместо В — "Нева впадает в Балтийское море" (тоже истинное высказывание), то высказывание А —> В будет заключаться в следующем: ''Если дважды два равно четырем, то Нева впадает в Балтийское море"; оно будет формально истинным, т. к. А и В — истин- ные высказывания. Ясно, что никакой причинно-следственной связи между этими высказываниями нет, но формально с этим можно согласиться. Высказывание ("| А —> В), заключающееся в том, что "Из того, что дважды два не равно четырем, следует, что Нева впадает в Балтийское море", также истинное вы- сказывание. Действительно, т. к. посылка ("| А) ложна, а следствие истинно, то им- пликация тоже будет истинной. С этим можно согласиться: Нева впадает в Балтий- ское море независимо от того, чему равно два, умноженное на два!
Глава 1. Дедуктивные системы 59 То же самое можно сказать и про высказывание (1 А ->В): "Из того, что дважды два не равно четырем, следует, что Нева не впадает в Балтийское море". Действи- тельно, ерунда может порождать ерунду, это не удивительно. Высказывание (А -> 1 В)'. "Из того, что дважды два равно четырем, следует, что Не- ва не впадает в Балтийское море" будет являться формально ложным. Это можно оправдать тем соображением, что нельзя делать неправильные выводы из правиль- ных посылок. Таблица истинности для импликации это и "подгонка под правильный ответ": она, например, такова, чтобы высказывание (А & В) -> В было бы всегда истинным (т. е. тавтологией). С точки зрения здравого смысла мы соглашаемся с "тривиальным" утверждением, что если дано и Л, и В, то имеет место В. С формальной точки зре- ния, если В = false, то посылка А & В ложна и, следовательно, единственный слу- чай, когда импликация принимает ложное значение (когда посылка истинна, а след- ствие ложно), невозможен. Таким образом, и формально это высказывание всегда истинно. В качестве дополнительных соображений в пользу разумности определения импли- кации рассмотрим следующий пример. Высказывание "Сумма внутренних углов прямоугольного треугольника равна 180 градусам" истине. Обозначим через А выска- зывание "Фигура является прямоугольным треугольником", через В "Сумма внутрен- них углов равна 180 градусам". Высказывание А -» В принимает значение истина, что подтверждает справедливость четвертой строки таблицы истинности: (true —> true) ~ true. С другой стороны, если треугольник не прямоугольный, то значение А = false, но высказывание все равно остается истинным, что оправдывает вторую строчку табли- цы: (false —> true) = true. В другом случае, когда А ложно, например, фигура является четырехугольником, и Сложно, т. к. сумма углов четырехугольника не равна 180°, таким образом получа- ем, что ложная посылка влечет ложное следствие, но утверждение истинно, что оправдывает первую строчку таблицы истинности (false -> false) = true. Утверждение же о том, что в каком-то прямоугольном треугольнике сумма углов может оказаться не равной 180 градусам, является ложным, что является подтвер- ждением справедливости третьей строки таблицы (true -> false) = false. Еще одна логическая операция — эквивалентность: А = В (иногда для этого исполь- зуют знак ~): А тождественно (или эквивалентно) В принимает значение истина то- гда и только тогда, когда значения истинности А и В совпадают. Значение ложь, когда они различные. Можно проверить, что таблицы истинности для формулы А = В и формулы (А —> В) & (В -> А) совпадают, т. е. А и В имеют одинаковое значение истинности, если из Л следует В, а из В следует А, что вполне соответствует здравому смыслу. Алгебра логики Что же такое алгебра логики! Под алгеброй понимают множество с заданными на нем операциями. Например, в качестве множества можно взять множество веществен- ных чисел, а в качестве операций — арифметические операции.
60 Часть I. Математические основы логического программирования Определение 1.2 Декартовым произведением множеств Мп называется множество всех упорядоченных последовательностей (zwj, m2, ..., mn) таких, что т^е Mj. Обозна- чение декартова произведения М\ х М2Х х Mr Если = М2 = ... = Mn = М — одно и то же множество, то получим частный случай декартова произведения: множество, элементами которого являются лю- бые упорядоченные последовательности из п элементов, каждый из которых принадлежит М, обозначается Мп. О Определение 1.3 Функция, ставящая в соответствие каждому элементу из Л/71 по некоторому пра- вилу (обозначаемому F) некоторый единственный элемент из М, что обознача- ется: F: М" -+ М называется д-арной (д-местной) операцией на множестве М. О Определение 1.4 Множество М с заданной на нем совокупностью операций F- {Fb F2, ..., Fm} называют алгеброй А = <М, F> с носителем (несущим множеством) М и сигна- турой F. О Определение 1.5 Пусть В — {0;1} — двухэлементное множество, и L — множество всех возможных операций на этом множестве, алгебра = <В, L> называется алгеброй логики. Эти операции называют булевскими операциями или функциями алгебры ло- гики. Элементы двухэлементного множества можно обозначать по-разному: о, 1, или ложь, истина, или true, false. Операции, которые были рассмотрены в предыду- щем разделе, принадлежат множеству L. Й Утверждение 1.1 Любая функция алгебры логики может быть выражена с помощью отрицания, конъюнкции и дизъюнкции. Проверим истинность этого утверждения. Рассмотрим в качестве примера функцию (операцию) "исключающее или". Обозначим ее знаком Ф. Таблица истинности для этой функции представлена в табл. 1.5.
Глава 1. Дедуктивные системы 61 Таблица 1.5. Таблица истинности для "исключающего или" А в А ® В Л л Л Л и и И л и и и л Будем выражать А © В в виде дизъюнкции конъюнкций: А © В s Q v С2 v С3 v С4, причем количество С/ возьмем равным количеству строк таблицы: i ~ 1, 4. Оставим только те Ch для соответствующей /-й строки которых значение истинности функции (операции) "разделяющее или" принимает значение истина, т. е. при i = 2 и / = 3 : А Ф В = С2 v С3 Рассмотрим вторую и третью строчки и представим С2 = 1 А & В\ С3 = А & 1 В, т. е. для столбца, в котором значение переменной истинно, возьмем саму переменную, а для столбца, в котором значение переменной ложно, возьмем ее отрицание. В результате получим следующую формулу, выражающую "разделяющее или" через дизъюнкцию конъюнкций, причем каждая конъюнкция состоит из переменных и отрицаний логических переменных А и В\ А @В = (] А & В) V (Л & 1 В) Проверим, что эта формула верна, составив табл. 1.6. Таблица 1.6. Таблица истинности для формулы, эквивалентной "исключающему или" А В 1 А 1В 1 Д& В Д&1В С1А & В) V (А & 1 В) А ® В Л л И и Л л Л Л л и И л и л и и и л Л и л и и и и и л л л л л л Совпадение значений истинности для двух последних столбцов показывает, что формула верна. Замечание J Эквивалентная формула, выражающая ту же самую функцию, была приведена ра- нее. Таким образом, формула ((1 A v 1 В) & (A v В)) = ((1 А &В) v (А & 1 В)) будет истинна при любых значениях А и В, т. е. будет тавтологией.
62 Часть I. Математические основы логического программирования Доказательство Доказательство утверждения в общем виде заключается в следующем. Дизъюнк- ция истинна тогда и только тогда, когда хотя бы одна из ее составляющих при- нимает значение истина. Для тех строчек, для которых значение функции ис- тинно, найдется (по построению формулы) один дизъюнкт, представляющий конъюнкцию истинных значений, т. е. принимающий истинное значение. Ос- тальные дизъюнкты при этом будут принимать значение ложь. Для тех же строчек (распределений значений переменных), для которых значение функции ложно, соответствующего дизъюнкта не будет. Следовательно, все дизъюнкты, входящие в конъюнкцию, будут ложны, и, следовательно, значение формулы, выражающей заданную функцию, тоже будет ложно. Й Если строчек в таблице, в которых функция принимает значение истина, нет, то это означает, что функция всегда ложна и ей будет удовлетворять формула А & 1 А. В качестве примера рассмотрим функцию трех переменных (табл. 1.7). Таблица 1.7. Пример логической функции трех переменных А в с F(A, В, С) И и и И Л и и и и л и л л л и л и и л л л и л л и л л л л л л и Эта логическая функция (логическая операция) будет представлена следующей фор- мулой: F(A, В, С) — (А & В & C)v(]A & В& Для дальнейшего нам потребуются некоторые логические эквивалентности. (Функ- ции алгебры логики, стоящие слева и справа от знака эквивалентности, имеют оди- наковые таблицы истинности.) (А & В) v С = ( A v С) & (В v С) (l.l) (A v В) & С= (А & Q v (В & Q 1 (А & В) н 1 A v 1 В " (A v В) =1 А В (1-2) (1-3) (1-4)
Глава 1. Дедуктивные системы 63 А & 1 А = false (1.5) A v ~\А = true (1-6) Справедливость равенств может быть проверена с помощью построения таблиц ис- тинности. Пропозициональные формулы Под высказыванием (proposition) понимается утверждение, относительно которого можно сказать истинно оно или ложно, при этом оно не может быть и истинным, и ложным одновременно. Из простых высказываний с помощью логических операций, рассмотренных выше, строятся более сложные высказывания. В исчислении высказываний рассмотренные выше символы логических (булевских) операций (функций) 1, &, v, = называют пропозициональными или логическими связками. Логические (булевские) переменные, обозначаемые буквами латинского алфавита (иногда с индексами), называют пропозициональными переменными (пропозициональ- ными буквами). Алфавит языка исчисления высказываний состоит из пропозициональных перемен- ных (букв), логических связок и скобок: (,). Правильно построенные выражения языка исчисления высказываний называются пропозициональными формулами или просто формулами. Определение 1.6 1. Все пропозициональные переменные являются формулами. 2. Если U и И7 — формулы, то (1 0, (U& И7), (Uv И7), (U И7), (U = W) — то- же формулы. 3. Формулами являются только те выражения, которые могут быть получены с помощью пунктов 1 и 2. Это определение позволяет отличить правильно построенную пропозициональную формулу от неправильной, задает правила построения языка исчисления высказыва- ний или, иначе, его синтаксис. Так как буквы, входящие в формулу, сами могут быть формулами, то каждую фор- мулу можно рассматривать как схему бесконечного множества формул, получаемых заменой пропозициональных букв на соответствующие им формулы. Для того чтобы указать это, используют вместо термина пропозициональная формула термин пропо- зициональная форма. Скобки (определяющие структуру формулы) могут опускаться в соответствии со сле- дующим приоритетом логических связок: 1, &, v, =. Любое вхождение знака 1 относится к наименьшей формуле (пропозициональной форме), следующей за ним; после расстановки всех скобок для формул, содержащих знак 1, каждое вхождение знака & связывает наименьшие формулы, стоящие рядом с этим знаком; затем то же
64 Часть I. Математические основы логического программирования самое относится к знаку v, далее к -> — импликации и потом к знаку s — эквива- лентности. В случае равного приоритета скобки расставляются слева направо. Так, например, пропозициональная формула ((((Л & В) & Q & D) £), получае- мая в строгом соответствии с определением, может быть записана в виде А & В & С & D Е, т. к. считается, что если скобки опущены, то формула разбира- ется слева направо, операция & выполняется раньше, чем операция Пропозициональная формула Av В С v D &. Е должна пониматься как ((Л v В) -> (С v (D & £))), поскольку сначала выполняется (применяется) опера- ция &, которая "старше", чем операция v, затем левая дизъюнкция, т. к. направле- ние чтения формул слева направо, потом правая дизъюнкция и далее импликация. Крайние скобки можно опускать без ущерба для понимания формулы, остальные будем опускать в очевидных случаях. Пропозициональной формуле соответствует некоторая функция алгебры логики, принимающая свои значения в зависимости от значений истинности входящих в нее пропозициональных переменных. В зависимости от значения этой функции говорят и о значении соответствующей пропозициональной формулы. Приписывание значений истинности (true или false) пропозициональным пере- менным называется интерпретацией этих переменных. Под интерпретацией формулы понимается приписывание значений истинности переменным, входящим в эту фор- мулу. Определение 1.7 Пусть F(pi, р2, ..., Рп) формула, содержащая переменные рь рз, ..., рп> тогда ее ин- терпретацией будет набор I ~ (vb v2, > v/?)> где каждое v, может принимать одно из двух значений true или false, приписываемое соответствующей перемен- ной pi. Значением формулы F на данной интерпретации I будет /*(/) = v2, равное значению функции алгебры логики, получаемой при данной интерпрета- ции переменных. О Определение 1.8 Пропозициональная формула, истинная при любых значениях истинности вхо- дящих в нее пропозициональных переменных, т. е. истинная при любых интер- претациях, называется тождественно истинной, или общезначимой пропозицио- нальной формулой, или тавтологией. Пропозициональная формула, ложная при любых значениях истинности входя- щих в нее пропозициональных переменных, называется тождественно ложной, или невыполнимой пропозициональной формулой, или противоречием. Пропозициональная формула, которая может принимать истинные значения при некоторых интерпретациях, называется выполнимой. Интерпретации, для кото- рых пропозициональная формула принимает истинное значение, называются ее моделями. О
Глава 1. Дедуктивные системы 65 Пример Формула Р v 1 Р — тавтология, Р & 1 Р — противоречие, Р & Q — выполнимая про- позициональная формула. Ясно, что отрицание тавтологии является противоречием, а отрицание противоре- чия — тавтологией. О Утверждение 1.2 Если U ~ тавтология и U -> V — тавтология, то и V — тавтология. О Доказательство Предположим, что Vложно в какой-нибудь интерпретации, тогда, т. к. U — тав- тология, т. е. истинна в любой интерпретации, то получим, что U -> V ложно в этой интерпретации, что противоречит условию, что U -> V — тавтология, т. е. истинна в любой интерпретации. О Логическое следствие и логический вывод Выявление того факта, что из множества высказываний (формул исчисления) логи- чески следует некоторое другое высказывание (формула) и является, по существу, одной из основных задач исчисления. Определение 1.9 U логически влечет W (Алогически следует из U) в исчислении высказываний, что обозначим как U => А, если пропозициональная форма (£/-> А) является тавтологией. Говорят, что множество формул {Fj, Fi, ..., Fn} логически влечет Fb исчислении высказываний, что будем записывать как {Fb F2, ..., Frt} => F, если формула F] & F2 & ... & Fn -> Fявляeтcя тавтологией. О Замечания J • Словосочетание "в исчислении высказываний", когда это и так ясно, опускается. • Для обозначения логического следования здесь используется знак => (как в [60]). Надо отметить, что в литературе иногда для обозначения логического следова- ния используется знак (например, как в [36]), а знак => используется для обо- значения импликации. Определение 1.10 U и А логически эквивалентны (в исчислении высказываний), что будем запи- сывать в виде U<=> И, если пропозициональная форма (U= W) является тавто- логией. 3 Зак. 963
66 Часть I. Математические основы логического программирования Так как (U = И) то же самое, что (U —> И) & (И—> U), то Un И логически эквива- лентны, если U V и V U. О’ _____Замечание J Будем использовать знак о и для указания эквивалентных преобразований формул. Утверждение 1.3 U Wтогда и только тогда, когда при истинном значении U истинной является и W(в каждой интерпретации, когда истинна U, истинна и W). ’б' Доказательство Действительно, если (U —> W) тавтология, возможны три случая: 1. U и W принимают значение истина. 2. Uложна, a Wистинна. 3. Un И7ложны. Так как U истинно, то возможен лишь первый вариант: Wистинна. Если при истинном значении U истинным является то невозможен случай U истинно, а Сложно, следовательно (UW) всегда истинная пропозициональ- ная формула, т. е. тавтология. о Следствие 1 {Fi, ^2, ..., Fn} F тогда и только тогда, когда при истинных формулах F\, F2, Fn истинной является и формула F (или по-другому, если для каждой интерпретации, в которой истинна каждая формула при /' = 1, 2, л, истинна и формула F). Действительно, если U — F\ & F2 & ... & Fn, то U истинна тогда и только тогда, когда истинно каждое F/ О Следствие 2 Если U^> Ии У=> то U^ И< О Доказательство 1 Предположим, что это не так, т. е. U истинно, а И7 ложно. Но если U истинно, то, т. к. U => у V должно быть истинно, но, следовательно, т. к. У=> и И7 должно быть истинно. Получили, что W истинно и ложно одновременно, т. е. противоречие. Следовательно, первоначальное предположение было неверно и W. О
Гпава 1. Дедуктивные системы 67 Продемонстрируем другое доказательство, основанное на применении эквивалент- ных преобразований формул (см. разд. "Алгебра логики"данной главы). Доказательство 2 Эквивалентная формулировка утверждения того, что если U => V и то U => Wзаключается в том, что формула (((£/-> V) & (Г-> W)) -> (U-> W)) явля- ется тавтологией. Будем использовать эквивалентные преобразования (1.1)—(1.6): (((t/-> V) & (К-> ИО) -> (t/-> ИО) о 1 ((1 t/v И & (1 HvHO) V (1 t/v ИО о ((t/& 1 jo v (v& 1 ИО) v (1 t/v ио о ((t/v 1 U) & (1 И v 1 tO) v ((Kv И0 & (1 JTv И0) « ((true & (1 Kv 1 t/)) V ((Kv HO & true)) 1 Kv 1 t/v Kv W true v lt/v true О Утверждение 1.4 {(t/v JO, (JTvIjo} =>(t/v wo ................................. ...................... 6 Доказательство Могут быть два случая: И истинна тогда, если (((/v F)& (H^v 1 И)) истинна, то т. к. должна быть истинна пропозициональная формула (fVv 1 У), должна быть истинна W\ и, следовательно, (f/v #) истинна; если И ложна, то, т. к. (U v V) должна быть истинна, U истинна и, следовательно, истинна пропозициональная формула (t/v V). Следовательно, эта формула является логическим следствием (t/v JO и (JTvl JO- .............................. О Замечание Эквивалентная формулировка: {( V U), (V W)} => (U v W). Следствие {V, 1 И v ИО => w или {И, (И-> ИО) => И' ..............................................................О Доказательство Следствие будет получено из утверждения, если в утверждении взять U = false. Другой способ доказательства. {И, (V -> И)} IF то же самое, что (V& W)) => W, или что ((V& (V —> W)) —> W) — тавтология. Сведем данные в табл. 1.8.
68 Часть I. Математические основы логического программирования Таблица 1.8. Таблица истинности для (V& ( V w W (V&(V-> W))-> W и и и и и Л и и л и и л л л и л л и л и Последний столбец таблицы доказывает, что данная формула тождественно истинна. Третий способ доказательства (в качестве упражнения) будет заключаться в экви- валентных преобразованиях формы ((И& (/-> И/)) И/), приводящих ее к тож- дественной истине (true). О Вместо каждой буквы пропозициональной формы может быть подставлено любое высказывание естественного языка, причем одни и те же буквы должны заменяться одними и теми же высказываниями. Если сделать такую подстановку в тавтологии, то независимо от высказываний естественного языка мы получим логически истин- ное высказывание. Если пропозициональная формула ложна при любой интерпрета- ции (является противоречием), то при такой подстановке будет получаться высказы- вание, являющееся логически ложным. Истинность или ложность получаемых высказываний не будет зависеть от высказы- ваний-подстановок, т. к. истинность или ложность результирующего высказывания зависит только от логической структуры пропозициональной формы, в которую они подставлялись. Рассмотрим высказывание, приведенное выше, в качестве примера логической ис- тины: если верно, что после снегопада крыша становится белой, то справедливо также и следующее утверждение: если крыша не белая, то снегопада не было. Обозначим буквой S утверждение "снегопад был", буквой В— "крыша белая". Тогда утверждение о том, что "после снегопада крыша становится белой", запишется в виде формулы S -> В. Утверждение "если крыша не белая, то снегопада не было" запишется так:"| В -> "I S. То, что из первого утверждения следует второе (т. е. всегда, когда истинно первое утверждение, истинным является и второе), записывается как логическое следование: (S -> В) => (1 В -► 1 5), что эквивалентно тому, что формула (5—>/?)-> (~|Z?~>1S) является тавтологией. Докажем, что эта формула тождественно истинна (тавтология). Это можно сделать с помощью таблицы истинности или с помощью эквивалентных преобразований (1.1)—(1.6): v(£vl5)^> (^vBvlS)&(UvBvS) <=> true & true <=> true
Гпава 1. Дедуктивные системы 69 Действительно, если в этом примере интерпретировать 5 и В по-другому, рассужде- ние останется логически верным. Можно получить, например, утверждение: "Если верно, что из того, что волков надо бояться, следует, что в лес нельзя ходить, то справедливо также утверждение, что если в лес можно ходить, то волков не надо бояться'. Пример Решение упражнения, приведенного в [54]. Выясним, является ли следующее рассу- ждение логически правильным. 1. Если строить противоатомные убежища (А), то другие государства будут чувство- вать себя в опасности (В), а наш народ получит ложное представление о своей безопасности (0. 2. Если другие страны будут чувствовать себя в опасности (£), то они смогут начать превентивную войну (D). 3. Если наш народ получит ложное представление о своей безопасности (0, то он ослабит свои усилия, направленные на сохранение мира (£). 4. Если не строить противоатомные убежища (1 А), то мы рискуем иметь колос- сальные потери в случае войны (F). 5. Следовательно, либо другие страны могут начать превентивную войну (D), и наш народ ослабит свои усилия, направленные на сохранение мира (£), либо мы рис- куем иметь колоссальные потери в случае войны (F). Метод доказательства заключается в следующем. Каждое элементарное высказыва- ние заменим пропозициональным символом (они стоят в скобках после соответст- вующих высказываний). Составим пропозициональную форму, соответствующую каждому из высказываний посылок (высказывания 1—4) и высказыванию следствию (высказывание 5). 1. (А -> (В& 0) 2. (В -> D) 3. (С^Е) 4. (1Л -> F) 5. ((D& Е) v F) Для того чтобы доказать, что из утверждений 1, 2, 3 и 4 логически следует утвержде- ние 5, т. е., что: {(А(В & С)), (В D),(C £), (1 А Л! =* {{D & Е) v F) надо доказать (в соответствии с определением 1.8), что формула (((Л -> (В & Q) & (В D) & (С -> Е) & (1 А -> /)) -> ((Я & Е) ч F) является тавтологией и, следовательно (в соответствии со следствием к утвержде- нию 1.3), при предположении, что истинны утверждения 1—4, истинным является утверждение 5, что и соответствует нашему (интуитивному, соответствующему здра- вому смыслу) понятию следствия.
70 Часть /. Математические основы логического программирования Будем преобразовывать формулу следующим образом: 1. Заменим всюду в формуле импликацию на дизъюнкцию (например, А -> (В & 0 заменим на 1 A v (В & 0), получим: 1 ((1 A v (В & Q) & (1 В у D) & (1 С v Е) & (A у Л) v (D & Е) у F 2. Воспользуемся эквивалентностью формул ~\(Р& Q) и iPvl0 и эквивалент- ностью формул "| (Р v 0) и 1 Р & 1 Q. (А & (1 В v 1 Q) v (В & 1 D) у (С& 1 Е) у (1 А & 1 F) у ((D & Е) у F) 3. Воспользуемся эквивалентностью формул Р & (Q v /?) и (Р & Q) v (Р &. R): (А & 1 В) v (А & 1 Q v (В & 1 D) у (С & 1 Е) v (1 А & 1 Е) у (D & Е) v F Предположим, что при некоторой интерпретации формула ложна, следовательно все дизъюнктивные члены должны быть ложны, следовательно F должно быть ложно, но тогда А должно быть истинно (т. к. 1 А & 1 F ложно), следовательно В должно быть истинно (т. к. А & 1 В ложно), следовательно D истинно {В & 1 D ложно), сле- довательно Е ложно (D & Е ложно), следовательно С ложно (С& 1 Е ложно), но если С ложно, то А должно быть ложно (А & 1 С ложно). Получили противоречие: А должно быть истинно и А должно быть ложно, что доказывает, что формула ни- когда (ни при какой интерпретации) не может быть ложной и, следовательно, она всегда истинна, т. е. является тавтологией. Откуда следует, что имеет место логиче- ское следствие из множества формул 1—4 формулы 5, т. е. рассуждение является верным. О Метод резолюций Другое рассуждение (дающее ключ к пониманию метода логического вывода, назы- ваемого методом резолюций) заключается в следующем. Чтобы доказать, что {F(, F2, ..., Fn} F, необходимо доказать, что формула Fi & F2 & ... & Fw -> F — тавтология. Для того чтобы доказать, что данная формула является тавтологией, достаточно до- казать, что ее отрицание является противоречием, т. е., что формула: 1 (F] & F2 & ... & Fn-^F) — противоречие. То есть противоречием должна являться 1 (1 (^i & F2 & ... & F„)v/) или формула (Fj & F2 & ... & Fn & 1 F), т. е. противоречи- вым является множество формул Ф, составленное из множества посылок Е], F2, ..., Епи отрицания заключения F: 0={Fi, F2, ..., F„^F} Утверждение 1.5 Формула U противоречива тогда и только тогда, когда U => false. 6 Доказательство Если U противоречива, то это означает, что t/ложна в любой интерпретации, то- гда U -> false — тавтология, следовательно U => false.
Гпава 1, Дедуктивные системы 71 Если же false, то U —> false является тавтологией, следовательно, U не может быть истинной ни в какой интерпретации, то есть, U противоречива. Таким образом, для того чтобы доказать противоречивость множества формул Ф, достаточно доказать, что F\ & F2 & ... & Fn & 1 F => false. o‘ Утверждение 1.6 Если Ии U=> W. то V& W. 6 Доказательство Так как по условию U —> V и U —> W — тавтологии, то в любой интерпретации, когда истинно U, истинными являются И и ИС Следовательно, в любой интер- претации, когда истинно U, истинными являются И& W, следовательно, случая, когда U истинно в какой-либо интерпретации, а V & W ложно в этой интерпре- тации, не бывает. Следовательно, U —> V& W — тавтология и, следовательно, V& Ж 6 Если получить, что Ф ==> R & S и Ф ==> Р & 5, тогда будет иметь место: Ф => (Р & /? & 5 & 1 5), т. е. Ф^> false На каждом элементарном шаге доказательства будем пользоваться правилом, назы- ваемым резолюцией (от слова resolution — решение): {(Uv У), (H'v 1 И)} => (t/v ИО или {И, (1 И v И)} => И' а также тем, что если Е б, то (5 & Е) (S & G) (докажите самостоятельно). Пример Рассмотрим применение этого метода на приведенном в предыдущем разделе при- мере и докажем, что: {(Л -> (5 & Q), (5 -> D), (С -> £), (1Л -> /=)}=> ((Z) & E)v F). Преобразуем формы посылок с помощью эквивалентных преобразований: (А -> (В & Q) <=> (1 A v (В & Q) <=> ((1A v В) & (1 А & Q) (В -+ D) <=> (1 В v D) (С-> £) <=> (1 С v £) (1 А -> F) » (A v F) Отрицание заключения: (1 ((Z) & Е) V F)) <=> (1 (Z) & Е) & 1 Е) <=> ((1 D V 1 Е) & 1 F) Таким образом, необходимо доказать, что множество формул (их конъюнкция, конъюнктивная нормальная форма): F} & F2 & Е3 & Е4 & Е5 & Е6 & Е7
72 Часть /. Математические основы логического программирования где Fi = (1 AvB), F2 = (1 AvC), F3 = (1 BvD), F4 = (1 Cv£), Fs = (AvF), F6 = (1 ZXzl E), F7 = (1 F) противоречиво. {F5, F7} => А, следовательно: (1 AvB) & (1 AvC) & (1 BvD) & (1 CvE) & (AvF) & (] Z>vl E) & (1 F) => (1 AvB) & (1 AvC) & (1 BvD) & (1 CvE) & (] Dv] E) & A {A, F^} ==> С, поэтому: (1 AvB) & (1 AvC) & (1 BvD) & (1 CvE) & (1 D/| E) & A => (1 AvB) & (1 BvD) & (1 CvE) &(] Dv] E) & C {C, F^} => E, поэтому: (1 AvB) & (1 BvD) & (1 CvE) & (1 A/] E) & C => (1 AvB) & (1 BvD) & (] Dv] E) & E {E, F^} => "I D, поэтому: (1 AvB) & (1 BvD) & (] Dv] E) & E=> (] AvB) & (1 BvD) & 1 D {1 D, F3} =>15, поэтому: (1 AvB) & (1 BvD) & 1 D => (] AvB) & 1 В => 1 A \A & A => false, поэтому: (1 AvB) & (1 AvC) & (1 BvD) & (1 CvE) & (AvF) & (1 ZX/| E) & (1 F) => false Следовательно, формула противоречива, что и доказывает исходное утверждение. о’ Как видно, правило резолюции {(£А/| ty, V\ U или, что то же самое, И, z=> Uявляется весьма эффективным средством. Первым шагом для применения метода резолюций служит приведение пропози- циональной формулы к логически эквивалентной ей конъюнктивной нормальной форме. Дизъюнктом (в англоязычной литературе называют clause, что означает предложение в сложносочиненном предложении) называется дизъюнкция конечного числа литер, т. е. пропозициональных символов или их отрицаний. П = (Pivp2v ... vpn) Конъюнктивной нормальной формой (КНФ) называется конъюнкция конечного числа дизъюнктов. Утверждение 1.7 Любая пропозициональная формула имеет логически эквивалентную ей КНФ. О
Глава 1. Дедуктивные системы 73 Доказательство Алгоритм нормализации (приведения к КНФ) заключается в следующем: 1. Заменяем (Х = Y) на (XУ) & (У~> X). 2. Заменяем У) на (1 Av?)* 3. Заменяем 1 (X& У) на (1 Avl У); заменяем 1 (Av?) на (1 X& I У). 4. Заменяем 1 (1 X) на X. 5. Заменяем необходимое число раз, используя следующие эквивалентности: Av(r& Z) <=> (Av У) & (AvZ) Х&(KvZ) о (Х& Y)v(X&Z) После этих преобразований будет получена эквивалентная КНФ. Дизъюнкты, содержащие противоположные литеры (р v~| р) являются тавтологиями и их можно отбросить, т. к. они не влияют на значение формы. Если в пределах од- ного и того же дизъюнкта встречается несколько раз одна и та же литера (например, pvp или 1 ^v~| q), то достаточно записать ее лишь один раз. В результате таких упро- щений будет получена приведенная или совершенная КНФ. Пустым дизъюнктом называется константа false. Если КНФ не содержит ни одного дизъюнкта (иначе пуста), то она эквивалентна true. КНФ общезначима (является тавтологией), если все ее дизъюнкты общезначимы. Дизъюнкт невыполним в том и только том случае, если он пустой. Понятие дизъюнкта важно в логическом программировании и, в частности, в языке Пролог, т. к. описание задачи осуществляется в терминах дизъюнктов, часто назы- ваемых (в переводах на русский) клаузами или клозами. Рассмотрим другой пример (упражнение из [54]). Проверим совместимость множе- ства утверждений, для чего проверим, будет ли конъюнкция пропозициональных формул, выражающих каждое из утверждений, противоречием. Если курс ценных бумаг растет (А) или процентная ставка снижается (В), то либо падает курс акций (С), либо налоги не повышаются (D). Курс акций понижается (С) тогда и только тогда, когда растет курс ценных бумаг (А) и налоги растут f] D), Если процентная ставка снижается (В), то либо курс акций не понижается (1 С), либо курс ценных бумаг не растет А). Либо повышаются налоги D), либо курс акций понижа- ется f] С) и снижается процентная ставка (В). Запишем утверждения в виде пропозициональных форм: = ((AvB) (CvD)) F = (С=(А&1 D)) Fy = (B-+ (1 Cvl А)) Ъ = (1 /Ml C& В))
74 Часть I. Математические основы логического программирования Преобразуем каждое утверждение к форме дизъюнкта: (1 (AvB)v(CvD)) <=> (С = (А & 1 D)), поэтому: Fi <=> ((1Л & 1 B)v(A & 1 Z>)vZ>) <=> ((1 А & 1 S)v((JvZ>) & (1 EhD))) « ((1 AvAvD) & (1 SvJvZ))) <=> (1 BvAvD); F3^(B~> (1 Cvl A))<*(B -> (1 (A & 1 Z))vl A)) « (1 Sv(l /fvZV| Л)) « (1 ZM AvD); F4 «> (1 /Ml C & B)) <» (1 Dv(] (A & 1 D) & B)) <» (1 2X/((1 ^vZ>) & B)) <=> ((1 DvB) & (1 Z\Z] JvZ>)) <=> (1 DvB) Таким образом, получим совершенную конъюнктивную нормальную форму: F{ & F2 & F3 & F4 « (1 BvAvD) & (1 B/| AvD) & (1 DvB) Применение правила резолюций к дизъюнктам (клаузам) этой пропозициональной формы не позволяет прийти к противоречию, и, следовательно, утверждения со- вместны. Исчисление высказываний как формальная теория Рассмотренные нами методы логического вывода, доказательства истинности или ложности формул являются семантическими, т. к. используют интерпретацию фор- мул: придание значений истинности пропозициональным переменным. Один из этих способов — построение таблицы истинности. Другой способ — доказательство того, что данная формула логически (на основе понятия логического следования) следует из некоторого множества формул. Если пропозициональная формула логически следует из пустого множества формул, то она является тождественно истинной или тавтологией. Еще один способ, рассмотренный нами, — метод резолюций. Другой способ доказательства, формально-логический или синтаксический, не тре- бует для своего определения понятия интерпретации формул, он основан на по- строении формальной теории (формальной или дедуктивной системы). Именно построение такой формальной системы или теории и может являться спо- собом моделирования наших рассуждений во многих практически важных случаях. Формальная система представляет собой набор начальных состояний или аксиом, которые могут быть описаны на некотором формальном языке, и правил вывода, позволяющих на основе аксиом и их следствий, получаемых с помощью правил вы- вода, строить новые и новые следствия, называемые теоремами формальной теории. Процесс построения теорем называется формальным выводом или доказательством в формальной теории. Из школьного курса геометрии вспомним, что представляют собой аксиомы и теоремы формальной теории.
Гпава 1. Дедуктивные системы 75 Определение 1.11 Формальная теория (ФТ) — исчисление, дедуктивная или формальная система — представляет собой совокупность (или кортеж): Т = <Л, Л, Я> где Л — язык, определяющий множество правильно построенных выражений (ППВ) или формул формальной теории (задается определениями, называемыми также синтаксическими правилами языка, с помощью которых строятся ППВ фор- мальной теории); А — множество аксиом — выделенное подмножество ППВ; Я — множество правила вывода — множество отношений между формулами. Напомним, что если {Ff, F2, Fn} и У7 связаны отношением А, то говорят, что F непосредственно выводима из {Fb F2, FfJ} по правилу У?, что записывают в виде: Формулы У7!, F2, •••> Рп называют посылками правила /?, a F — его (или их) следстви- ем (заключением). Справа от правила вывода пишут его название (У?). Выводом формулы В из формул Л2, ...» Ап называется последовательность формул Z7!, f2, •••» Fm такая, что Fm~ В, а каждая из Г/ (/= I, ..., т) есть или аксиома или одна из исходных формул Ah Л2, Ап или непосредственно выводима из аксиом и формул f], F2, ..., У7.! или какого-либо их подмножества по одному из правил выво- да. Если существует вывод формулы В из формул А\, Л2, Ап, то говорят, что В выводима или доказуема из Л2, ..., Ап, что записывают так: ^2» •••’ Н" $ Доказательством формулы В в теории Т называется вывод В из пустого множества формул, т. е. вывод, в котором в качестве посылок или гипотез вывода Ah А2, ..., А„ используются только аксиомы. Формула В, для которой существует доказательство в Т, называется доказуемой в теории или теоремой теории, этот факт обозначается: или, когда ясно, о какой теории идет речь: При изучении формальных теорий имеют дело с двумя типами высказываний: с вы- сказываниями самой теории — теоремами и высказываниями о теории на языке, внешнем по отношению к теории метатеоремами.
76 Часть /. Математические основы логического программирования Например, если существует вывод В из Ah А2, ..., АП9 то высказывание ^2» •••» Н" $ является метатеоремой, которую можно рассматривать как дополнительное правило вывода, присоединяемое к теории: Для того чтобы определить исчисление высказываний как ФТ, потребуется опреде- лить три составляющие ФТ: □ язык\ □ аксиомьг, □ правила вывода. Язык исчисления высказываний был определен ранее, когда мы ввели понятие про- позициональной формулы. Множество аксиом и правил вывода "подобраны" таким образом, чтобы любая тео- рема формальной теории была тождественно истинной (истинной в любой интер- претации или тавтологией). И наоборот, любая тавтология была бы теоремой (т. е. выводимой) в ФТ. Такое свойство, если оно имеет место, называется полнотой ФТ. ФТ называется формально непротиворечивой, если не существует формулы (ППВ) F такой, что и F и 1 /’являются теоремами этой теории. ФТ, которая может быть формализована таким образом, чтобы в результате она об- ладала свойствами полноты и непротиворечивости, называется аксиоматизируемой. Под содержательной теорией подразумевается некоторое множество утверждений, которым можно приписывать логические значения, семантика (смысл) этой теории определяется неформально. В формальной теории и семантика формальна: теоремы, которые могут быть фор- мально выведены (из аксиом с помощью правил вывода), являются истинными в данной теории. Теория, обладающая полнотой — идеальная теория, т. к. формальная выводимость из множества аксиом и тождественная истинность утверждений этой теории совпа- дают. Теоремами в этой теории являются те и только те утверждения, которые яв- ляются общезначимыми (тождественно истинными, тавтологиями). Исчисление высказываний, понимаемое как множество пропозициональных фор- мул, допускающих различные интерпретации, т. е. различные значения истинности входящих в них пропозициональных переменных, является аксиоматизируемой тео- рией. Возможны различные системы (схемы) аксиом (а следовательно, и разные, но экви- валентные исчисления), приводящие к одному и тому же множеству теорем — фор- мул, выводимых в данной теории. Исчисление высказываний можно построить, ис- пользуя отрицание, конъюнкцию, дизъюнкцию и импликацию, на следующей сис- теме аксиом:
Глава 1. Дедуктивные системы 77 U-* (И-> U) (U-ь у) _» (((/_> ( И-> ИО) -> (!/-> ИО) (U&. V)-> U (U& Г) -» V U->(V-> (U& Г)) U-> (Uv Г) И-> (t/v Г) (и-> ио -»((г-> ио -> ((t/v V) -> ио) ((/-> и-»(({/-»! И) -> 1 U) 11t/-> и (11) (12) (1з) (Ц) (15) (1б) (17) (18) (19) (1ю) А вот другое исчисление, язык которого содержит лишь знаки двух логических опе- раций отрицания и импликации и всего три аксиомы: t/-> (И-> to (С/-> (И-> H0)-»((t/-> fO->(t/-» ИО) (1 U -»1 И -> ((1 и -> V) -> U) (П1) (П2) (Из) Возможны и другие системы аксиом (а следовательно, и другие исчисления, опреде- ляющие одно и то же множество общезначимых формул). Правила вывода Одно из правил вывода, правило подстановки, может в неявном виде присутствовать в определении того, что является ППВ (в исчислении высказываний они называют- ся пропозициональными формами): все вхождения любой пропозициональной пе- ременной могут быть заменены любой пропозициональной формулой. В этом случае аксиомы называют схемами аксиом, т. к. они определяют бесконечное множество формул. Если же ограничиваться формулами при определении ППВ, то правило подстановки может быть записано в следующем виде: U(V) Смысл его заключается в том, что если U — выводимая формула, содержащая пере- менную А, то формула, получаемая из U заменой всех вхождений А на формулу К, также будет выводимой формулой или теоремой формальной теории. Благодаря этому правилу, каждая аксиома порождает бесконечное множество пропо- зициональных форм, т. е. становится схемой аксиом. Любая из пропозициональных переменных в выводимой формуле (теореме) может заменяться (всюду, где она присутствует) на любую пропозициональную формулу,
78 Часть I. Математические основы логического программирования в результате будет получена теорема исчисления высказываний. Поэтому любая из аксиом определяет бесконечное множество теорем. Второе правило вывода называется правилом заключения (modus ponens): U, V Смысл его заключается в следующем понятном рассуждении (см. утверждение 1.2): если U и UV— выводимые формулы (теоремы), то и И- выводимая формула, теорема исчисления высказываний формальной теории. Докажем несколько теорем формальной теории — исчисления высказываний. Формула А -> А выводима в системе аксиом 11, т. е.: Утверждение 1.8 Доказательство (формальный вывод) I. Аксиома: (U-> (У-> IV)) (([/-> V) -> (U-> IV)). 2. Подставим (по правилу подстановки) (UU) вместо V и U вместо IV: (U((U-+ U) -> U)) -> ((£/-> (U-> U)) (UU)). 3. Аксиома: U -> (V -> U). 4. Подставим ([/-> U) вместо V, получим U _>(([}-> U) -> U). 5. По правилу заключения, используя шаги 2 и 4, получим U)) ->((/-> U). 6. Аксиома: U(И->. U). 7. Подставив в аксиому U вместо V, получим: U (U U). 8. Используя шаги 5 и 7 по правилу заключения, получим: (U-> U). Утверждение 1.9 С/р (И-»С/) Доказательство (формальный вывод) 1. Аксиома Пр U-у (VU). 2. Имеем Un U -> (И -> U), используем правило заключения и, [/> (И-> U) И-> и о
Гпава 1, Дедуктивные системы 79 Другой пример формального вывода в исчислении высказываний, правило силло- гизма (сравните со следствием 2 к утверждению 1.3): Утверждение 1.10 (правило силлогизма) и -> У, И-> и-У W G Доказательство (вывод в формальной теории) Используя утверждение 1.9 как дополнительное правило вывода, получаем V-> U—> (V-> ИО, отсюда, используя аксиому Из и правило заключения: £/-> (И-> ИО, (£/-> (И-> Ир) -> ((Ц-> И ИО) ({/-> У) _> (U-) ИО Так как по условию U -> У, то снова используя правило заключения: *7-» (Ц-у У) -> HQ 6/-> JF О Как уже говорилось, для построения выводов могут использоваться высказывания о теории на языке, внешнем по отношению к языку теории, например на естествен- ном. Такие правила называются метатеоремами. Метатеоремы могут использоваться как дополнительные правила вывода. Основные метатеоремы исчисления высказы- ваний приведены ниже. Пусть Г — список формул, U и V— формулы. Утверждение 1.11 (транзитивность отношения выводимости) Если Г Н U и [7 Н ЦтоГН V. Доказательство непосредственно следует из определения вывода. О Утверждение 1.12 (теорема дедукции) Если Г, 77 Н К то Г Н Доказательство можно найти, например, в [54]. О Замечание J Легко доказывается (докажите самостоятельно): если Г Н U Z то Г, U Н V.
80 Часть I. Математические основы логического программирования Утверждение 1.13 Если Г, U Н Ии Г, U Н "| И, то Г Н 1 U. Это утверждение выражает метод доказательства от противного: предположим, что U верно, придем к противоречию, т. е. выведем два взаимоисключающих утверждения Уи 1 У, следовательно, U неверно. 6 Доказательство Из Г, U Н И по теореме дедукции Г Н U И С помощью правила заключения и из U Ии аксиомы 19 (£/-> Р) ->((£/-> 1 У) -> 1 U) получаем ((£/_» "| И) -> ~| £/)• Из Г, U Н 1 И по теореме дедукции Г Н 1 И Из U Ии ((£/->"! У) -> 1 по правилу заключения получаем 1 U. Таким образом, Г h "| £/. О Утверждение 1.14 (правило исключения промежуточной посылки) и^>(У-> ИО, иН U-> W 6 Утверждение 1.15 (правило перестановки посылок) и^>(У^> ио Н и->([/-> ио Доказательства можно найти в [54]. О Теорема о полноте исчисления высказываний Исчисление высказываний является полной формальной теорией: всякая теорема исчисления высказываний является тождественно истинным высказыванием, всякая тождественно истинная формула является теоремой исчисления высказы- ваний. Доказательство можно найти в [54]. О Следствие Исчисление высказываний — непротиворечивая формальная теория: не сущест- . вует формулы U такой, что U и "| U — теоремы исчисления высказываний. О
Глава 1. Дедуктивные системы 81 Доказательство Предположим, что существует такая формула. По теореме о полноте были бы общезначимы U и "I [7, т. е. была бы общезначима U & "| [7, что невозможно, т. к. эта формула тождественно ложна. Следовательно, предположение неверно, и верно следствие. О Логическое следствие и формальный вывод Концепция логического следствия (см. определение 1,9) обычно используется в кни- гах по логическому программированию и искусственному интеллекту [30, 58] при описании метода логического вывода и методов автоматизации логического вывода. В книгах по математической логике [33, 51, 54] рассматривают исчисление высказы- ваний как формальную теорию и концепцию формального вывода на основе аксиом и правил вывода. Эти два подхода эквивалентны. Утверждение 1.16 Если [7=> И, то 77 Р И Если U р И, то U=> V. 6 Доказательство Если U => И, то U -> V является тавтологией. Следовательно, U -> V являет- ся теоремой исчисления высказываний: р U -> И, по правилу заключения U, U -> V р V, таким образом, U р V. Если U р И, то по теореме дедукции р U -> И, следовательно, U-=> V — тавто- логия, а из этого следует, что U => V. G Докажем правило силлогизма (см. утверждение 1.10)\ U-* И, И^Р 7/ —> W с помощью концепции логического следования. Пользуясь утверждением 1.16, можно доказать, что {77-> и, И-> И7} => 77-> W или что ((77V) & (И-> ИО) -> (77-> W) является тавтологией. Доказать это можно, кроме формального вывода в исчислении высказываний, еще тремя различными способами. Сравним на данном примере их трудоемкость.
82 Часть I. Математические основы логического программирования 1. Рассмотрим таблицу истинности (табл. 1.9). Цифры в шапке таблицы — это но- мера в последовательности выполнения операций. Значения U, К W находятся в столбцах, помеченных цифрой 1. В столбцах, помеченных цифрами 2 и 3, на- ходятся значения истинности импликации и конъюнкции. В столбце таблицы, помеченном цифрой 4, находятся значения истинности всей формы при различ- ных (всего 23 = 8) значениях входящих в нее пропозициональных переменных. Таблица 1.9. Таблица истинности для ((U IV)) ->(Ц-> W) ((и — V) & (И-> и)) (€/-> ИО 1 2 1 3 2 1 4 2 л И л и И л И И л И л и И и И И л И и л Л л и И л И и и и и и И и Л л л и л и Л и л л л и и и И и и и л л л и Л и и и и и и и и Так как при всех возможных значениях пропозициональных переменных значе- ние истинности формы, в столбце 4, истина, следовательно, формула является тавтологией. 2. Другой способ доказательства: применим эквивалентные преобразования для приведения к конъюнктивной нормальной форме: ((£/-> V) & (К-> ИО) -> (^-> ИО <» 1 ((^-> И & (И-> ИО) V (1 и V ИО <=> 1 (£/_> Р) V 1 (К-> ИО v (1 Uv ИО <=> 1 (1 lh К) V 1 (1 Kv ИО V (1 Uv ИО <=> (U&. 1 F) v(K& "| H0v(1 t/v ИО <=> (U&. 1 F) V ((Kv 1 Uv ИО & (1 Wv 1 Uv IV)) (U&. 1 V) V ((Kv 1 £/v И0 & (1 H'v 1 £/v (V)) <=> (U&. 1 V) V (Kvl £/v HO <=> (£/v Kv 1 £/v И0 & Cl Kv Kvl Uv IV) <=> true 3. Использование метода резолюций: (t/-> O«U'v И (K-> ИО <=>1 Kv W Отрицание выводимой формулы: 1 ({/-> ИО <=> 1 (]Uv W) U&l IV
Гпава 7. Дедуктивные системы 83 Таким образом, необходимо доказать противоречивость множества дизъюнктов: {1 Uv V, 1 Kv W, t/J W} 1. t/v И, t/=> И 2. Kv W,~] 1 V. 3. V, "I V=> false. На данном примере видно, что формальный вывод в исчислении высказываний (см. доказательство утверждения 1.10) требует значительных интеллектуальных уси- лий. Использование эквивалентных преобразований проще, метод интерпретаций с помощью таблицы истинности также прост, но требует перебора всех вариантов значений пропозициональных переменных; и самым простым, практически механи- ческим способом доказательства, оказался метод резолюций.
ГЛАВА 2 Исчисление предикатов и теории первого порядка Исчисление предикатов первого порядка — это такая сис- тема в логике, в которой можно выразить большую часть того, что относится к математике, а также многое из раз- говорного языка. Н. Нильсон Не всякие высказывания и не любые логические рассуждения могут быть описаны на языке исчисления высказываний. В некоторых случаях высказывания касаются свойств объектов или отношений между объектами. Кроме того, требуется иметь возможность утверждать, что любые или какие-то объекты обладают определенными свойствами или находятся в некоторых отношениях. Отношение и предикат Разница между понятиями “отношение" и “предикат" состоит в следующем. Преди- кат является "индикатором" отношения, предикат может принимать значение истина или ложь в зависимости от того, имеет место или нет данное отношение. Например, есть отношение порядка (упорядочения) на множестве вещественных чисел — “больше" (для которого используется знак >). Если записать, что 2 > 3, то это будет неверное утверждение. Функция Больше (х, у), при х и у, заданных на множестве вещественных чисел, принимающая значение истина, когда х > у, и ложь в против- ном случае будет предикатом. Вместо имени предиката Больше, можно использовать другое имя, например, More или R. Определение 2.1 Пусть дано множество М Рассмотрим декартово произведение множеств ЛР = Мх Мх ... х М— множество всех упорядоченных последовательностей из п элементов, "энок“ (xj, Х2, хл) таких, что X/g М; л-местным отношением на М называется его подмножество: R с Мп. То есть отношением "больше" будет подмножество множества всех упорядоченных пар вещественных чисел, первое из которых больше второго числа.
Глава 2. Исчисление предикатов и теории первого порядка 85 Замечание Иногда рассматривают множество Mi х /Иг* ... х Мп, где М,— множества различной природы, и подмножества этого множества в качестве отношений. Например, пря- мая и плоскость — объекты, принадлежащие разным множествам, могут находиться в отношении параллельности, собака может быть другом человека (Mi — множество собак, М2 — множество людей) и т. д. Двухместные отношения называются бинарными, например, отношения "больше", "меньше", "равно" — бинарные отношения на множестве вещественных чисел, "на- ходиться на одном расстоянии от данной точки" — на множестве точек. Одномест- ные (унарные) отношения называют признаками или свойствами. Например, свойст- вом является принадлежность к некоторому множеству или классу. Если х и у при- надлежат отношению R, то это записывают или (х, у) е R, или xRy\ например 1 > 1, или а = Ь. Определение 2.2 л-местным предикатом Р(х[, х^ .... хп) называется функция Р: АР1 -> В, где М — произвольное множество, а В = {1, 0}. М называется предметной областью, х,- — предметными переменными. 6 Замечание Элементы множества В вместо 1 и 0 могут быть обозначены как истина и ложь или true и false для того, чтобы подчеркнуть логическую сущность этих функций. Для любых М и п существует взаимно однозначное соответствие между л-местными отношениями и л-местными предикатами. Каждому л-местному отношению R соот- ветствует предикат Р такой, что P(xi, х2, .... хп) = 1 тогда и только тогда, когда (xi, x2t ..., xn)eR, и любому предикату P(xi, х2, ...» хп) соответствует отношение R та- кое, что (xi, х2.xn)eR тогда и только тогда, когда P(Xi, х2, .... хп) = 1. Иначе говоря, областью истинности предиката Р является множество R. Кванторы Пусть Дхь Х2, хл) — предикат, определенный на Мп. Предметные переменные X], х2, хп называют свободными. Выражение Vx/P(xi, , хп) означает: "Для всехх, е М предикат Р(х[, х^ х„) принимает значение истина". Выражение ЗхДх!, х2, х„) означает: "Существует х, е Af такое, что P(xj, х2, х„) принимает значение истина".
86 Часть I. Математические основы логического программирования (Замечание ) Отметим, что можно было бы не вводить квантор существования, так как: ЭхР(х) то же самое, что и 1 (Vxl Р(х)) 1 (УхР(х)) то же самое, что и 3x1 Р(х) Можно было бы обойтись и одним квантором всеобщности, но удобнее, все-таки, использовать оба квантора. В естественном языке используется гораздо больше кванторов: "существует и притом единственный", "существует не более одного", "существует бесконечно много", "почти для всех" и т. д. Если множество {x-i, х2, хп] конечно, то УхР(х) c>P(xi)& ... & Р(Хп), ЗхР(х) P(x,)v ... vP(Xn) Переход от ТХ-Хь xj, ..., хп) к Vxf-/Vb х2, хп) или к х2> хп) называется навешиванием квантора на переменную х{ или связыванием переменной хг Переменная, на которую навешен квантор, называется связанной. При этом число свободных переменных уменьшается на единицу. Выражение, не имеющее свобод- ных переменных, является высказыванием, значение истинности этого выражения не зависит от значений входящих в него предметных переменных. Выражение, на кото- рое навешен квантор, называется областью действия квантора. Значение истинности выражения зависит от значений свободных переменных и не зависит от значений связанных переменных, поэтому переименование связанной переменной в области действия квантора не изменяет значения истинности всего выражения. Пример 1 Пусть Р(х) означает, что х делится на 5 без остатка. Тогда ЗхР(х) — высказывание, не зависящее от х, но зависящее от множества М, на котором оно рассматривается. Например, если М — множество целых чисел, то значение этого высказывания ис- тинно, а если хе(—О, 5), то это высказывание ложно. Если М образовано членами арифметической прогрессии с первым членом, равным 0, и разностью, равной 5, то истинным будет высказывание УхЯ(*)- 6 Пример 2 Большая (или великая) теорема Ферма утверждает, что для любого целого п > 2 не существует натуральных чисел х, у, г, удовлетворяющих равенству хп + у*' ~ zn. Если этому равенству поставить в соответствие предикат РДх, у, г, п), истинный тогда и только тогда, когда оно выполняется, а через 7V(x) обозначить предикат "х — нату- ральное число", то теорема Ферма формулируется так: VхУуУгУn(N(x) & N(y) & N(z) & N(n) & (n > 2) -> 1 РДх, у, г, л)) 6 ПримерЗ Пусть предикат Мать(х, у) означает, что х является матерью для у, тогда ; УуЭхМать(х, у) означает, что у каждого человека есть мать, — истинное утвержде- ние. Эх¥уМать(х, у) означает, что существует мать всех людей, что является другим утверждением, истинность которого зависит от множества значений, которые могут |
Гпава 2. Исчисление предикатов и теории первого порядка 87 принимать у. если это множество братьев и сестер, то оно истинно, в противном случае оно ложно. Таким образом, перестановка кванторов всеобщности и сущест- вования может изменить смысл и значение выражения. 6 Язык логики предикатов Попробуем на примерах убедиться в справедливости высказывания, приведенного в эпиграфе, переводя утверждения с естественного (русского) языка на язык логики предикатов. Пример 4 Друг моего друга — мой друг. Это, вроде бы, ясное утверждение заключает в себе множество неопределенностей, и интерпретировать его можно по-разному. Например, что значит "моего"? Относится это утверждение лично ко мне или ко многим (ко всем)? Для меня верно одно, для другого — другое. Можно, например, понимать это утверждение так, что любой друг любого друга того, кто это утверждает, является его другом. Обозначим того, кто утверждает это, именем, например, Вася. То, что некто х является другом у, запи- шем в виде R(x, у), то есть между х и у есть отношение быть друзьями. Будем считать это отношение коммутативным, то есть если выполняется Я(х, у), то выполняется и R(y, х). То, что у и х являются друзьями Васи, будет записано так: R(y, Вася) и R(x, Вася) Используя логические связки, конъюнкцию и импликацию, запишем следующее утверждение: R (х, у) & R(y, Вася) -> R(x, Вася) Для того чтобы указать, что х и у — не конкретные индивидуумы, а могут прини- мать любое значение, используем квантор всеобщности V, формула будет записана таким образом: \fx\fy(R(x, у) & R(y, Вася) -> R(x, Вася)) что будет означать, что для любых двух друзей, если второй из них друг Васи, то и первый является его другом. Если считать, что если х является другом у, то и у явля- ется другом х: VxVj<^(x, у) -> R(y, х)) и отсюда должно следовать, что VxVy(R(x, у) & R(x, Вася) R(y, Вася)) На самом деле, не обязательно это должно выполняться для любых двух друзей, один из которых дружит с Васей, но может быть найдется пара друзей, обладающая таким свойством. Чтобы записать это, потребуется квантор существования 3: ЗхЗу(Я(х, у) & R(y, Вася) R(x, Вася)) что будет означать, что если найдутся два друга, второй из которых дружит с Васей, то с ним дружит и первый.
88 Часть /. Математические основы логического программирования У любого ли Васиного друга найдется друг, который также дружит с Васей? То есть, верно ли утверждение 3x\fy(R(x, у) & R(y, Вася) -> Дх, Вася)) А если переставить местами кванторы? Получим следующее утверждение: ХХхЗу(Дх, у) & Ду, Вася) -> R(x, Вася)) Смысл его заключается в том, что если для х найдется соответствующий ему инди- вид у такой, что он дружит и с Васей, и с х, то и х дружит с Васей. То, что может быть верно для Васи, может быть верно или не верно для другого ин- дивида (из того же множества). Например, формула VxVyVz(A(x, у) & Ду, г) -> Дх, г)) будет, скорей всего, ложной. Утверждение, заключающееся в том, что для любой пары друзей найдется такой че- ловек, что если он дружит со вторым из этой пары, то он будет дружить и с первым, запишется так: VxVy3z(fl(x, у) & Ду, г) -> Дх, г)) О Пример 5 Рассмотрим множество вещественных чисел и отношения неравенства больше, меньше. Если R(x, у) означает х > у, то формула VxVyVz(tf(x, у) & Ду, г) -> R(x, г)) означающая, что для любых чисел х, у, z из того, что х > у и у > z следует, что х > z будет истинной. Буква R, которой мы обозначили различные отношения в первом и втором примере, называется предикатным символом, х, у, z, обозначающие разные объекты, — предметными переменными. Записанные нами выражения — формулы исчисления предикатов. Смысл, придаваемый этим предикатным символам, предмет- ным переменным и самим формулам, называется интерпретацией. Одна и та же формула, как видим, может быть истинной в одной и ложной в другой интерпрета- ции. Пример 6 Собака — друг человека. Мартик —* собака, Татьяна — человек, следовательно, Мар- тик друг Татьяны. Верно ли это логическое заключение? Пусть Дх, у) — предикат, означающий, что х является другом у, Д(х) — свойство "субъект с именем х является собакой", Д(х) означает, что х — имя человека. Пер- вое утверждение можно записать так: VxVy(7?i(x) & Д(у) -> Дх, у)) Второе и третье утверждения суть R\(M), Д(7), логическое заключение будет иметь вид R(M, Т).
Гпава 2. Исчисление предикатов и теории первого порядка 89 Таким образом, требуется доказать (или опровергнуть), что: {У1УХВД & R2(y) R(x, у)), ^(ЛТ), Я2(7)} R(M, 7) или, что то же самое, доказать или опровергнуть, что (улуХВД & R2(y) -> Ж у)) & Ri(M) & R2(T>) -> R(M, 7) будет истинной формулой в данной интерпретации. Не трудно доказать, что это так и будет, причем вне зависимости от того, какой смысл будет вкладываться в А, R2, х, у, Ми Т, то есть независимо от интерпре- тации или в любой интерпретации. G Пример 7 Для любой прямой и для любой точки, не принадлежащей данной прямой, существует и притом единственная прямая, проходящая через эту точку и параллельная данной прямой. X/x\/y3z (Прямая(х) & Точка(у) & 1 Принадлежит^, х) -> Прямая($ & Принадлежит (у, z) & Параллельные_прямые(х, г)) УхУуЗгЗд (Прямая(х) & Точка(у) & 1 Принадлежит (у, х) & Прямая(б) & Принадле- жит (у, z) & Пар аллельные_прямые(х, г) & Прямая(и) & Принадлежит (у, и) & Парал- лельные_прямые(х, и) -> Равный «)) Это утверждение является аксиомой евклидовой геометрии. Первая формула — утверждение о том, что такая прямая существует. Вторая — утверждение о единст- венности такой прямой. 6 Пример 8 Основное тригонометрическое тождество: sin2(x) 4- cos2(x) = 1. Эта формула является теоремой тригонометрии (основное тригонометрическое тож- дество). \/хРавно(сумма(квадрат($т(х)), квадрат(со$(х))), 1) Здесь х — переменная для обозначения некоторого произвольного числа из множе- ства вещественных чисел, ”Г — предметная константа. Равно — имя предиката, сумма, квадрат, sin, cos — имена функций. 6 Пример 9 Все люди бессмертны. S — человек. Следовательно, S бессмертен. M(S), Х/х(М (х) -> 1(х)) Д5) В этом примере используется запись, когда над чертой записываются посылки, а под чертой заключение. М и 7— предикатные символы, М(х), 1(х) означают, что х —
90 Часть I. Математические основы логического программирования человек их- бессмертен, S — предметная константа, имя человека. Первая посыл- ка этого рассуждения ложна, несмотря на это само рассуждение верно, независимо от смысла (интерпретации) символов М, Д 5. G Пример 10 Все лошади — животные. Следовательно, голова лошади есть голова животного. Х/х(Н(х) А(х)) Vx(3y(x = h(y) & //(у)) -> Зу(х = Ку) & Я(у))) Н(х), А(х) означают, что х — лошадь, х — животное, h — функциональный символ, Ку) “ голова у. Справедливость этого заключения не зависит от конкретного смыс- ла предикатных символов Н и А, функционального символа h. о Из этих примеров видно, что язык логики предикатов позволяет описывать не толь- ко математические факты и закономерности, но и высказывания, и логические рас- суждения (выводы) из произвольных предметных областей. Не всегда просто (однозначно) можно переводить утверждения естественного языка на язык логики (исчисления) предикатов. Пример 11 Ты можешь обманывать кое-кого все время, ты можешь обманывать всех некоторое время, но ты не можешь обманывать всех все время. Введем предикат Дх, у, г), который будем интерпретировать так, что некто х может обманывать некого у в течение промежутка времени г- Если считать, что ты относит- ся к любому индивидууму х, то первое предложение можно записать так: УхЗуУгДх, у, г) второе предложение так: УхУуЗгДх, у, г) Более сложно записать третье предложение. Рассмотрим такой вариант: VxVyVzl Дх, у, z) — высказывание (нет свободных вхождений переменных), озна- чающее, что любой человек любого другого человека в течение любого промежутка времени обманывать не может, — наверное, неправильно передает смысл утвержде- ния (почему же, в течение какого-то может, но не все время). Утверждение можно понимать так, что для любого х найдется кто-то у, что, каким бы ни был промежуток времени г, х не может обманывать у в течение промежутка времени z: Vx3 yVzl Дх, у, г). Эквивалентное высказывание УхЗу] ЗгДх, у, г): Для любого индивидуума х найдется кто-то с именем у такой, что не найдется промежутка времени г, в течение которого х мог бы обманывать у, то есть для любого человека найдется такой, кого он нико- гда не сможет обмануть. Возможен и такой вариант понимания, что для любых х и у найдется промежуток времени z, в течение которого х не сможет обмануть у: VxVyBzl Дх, у, г).
Гпава 2. Исчисление предикатов и теории первого порядка 91 Эквивалентное высказывание VxVy] Х/гДх, У, £) любого человека можно обмануть, но не на любой промежуток времени. Другой вариант: для любого человека х найдется (хотя бы один) человек у такой, что х не сможет обманывать у в течение некоторого (хотя бы одного) промежутка вре- мени z- Vx3y3zl Дх, у, z) Эквивалентное высказывание Vx3 у] ХХ^Дх, у, z)- для каждого человека х найдется человек у, которого можно обмануть, но не на любой промежуток времени; эквива- лентное высказывание Vx] VyV^P(x, у, £): любой человек может обмануть не каждого на любой промежуток времени. Дизъюнкция этих утверждений Vx(3yVzl Дх, у, z) v Vy3 d Дх, У, z) v ЗуЗг] P(x, у, Э) лучше передает смысл исходного утверждения: каждый человек не может обманы- вать кого-нибудь в любой период времени, или не может обманывать всех в какой- то период времени, или не может обманывать кого-нибудь в какой-нибудь период времени. Поступая формально, нужно взять отрицание того факта, что "ты можешь обманы- вать всех все время": Vx("| VyX/гДх, у, z)) о Vx3y] Х/гДх, У, z) о Vx3y3z] Дх, у, г), то есть любой человек не сможет обмануть кого-то в какой-то промежуток времени. Если взять отрицание всей формулы, начиная с "любого х" 1 VxVyVzP(x, У< Z) <=> 3x1 X/y\/zP(x, у, z) <=> ЭхЭу] VzP(x, у, Z) <=> ЭхЭуЭ£1 Р(Х, у, z) то получим следующее утверждение: найдется кто-то, кто не сможет обмануть кого- то в какой-то промежуток времени. Ясно, что каждый из этих "переводов" на язык исчисления предикатов первого по- рядка страдает неточностью по сравнению с оригиналом на естественном языке. По-видимому, это связано с тем, что надо как-то формализовать понятие "все вре- мя". Введем с этой целью функцию Дх, у), аргументами которой являются имена х и у из некоторого множества людей, а значением — все то время, в течение кото- рого х может или не может обманывать у. В этом случае исходное утверждение мож- но записать в виде: VxVyl Дх, у, Цх, у)) <=> Vxl ЭуДх, у, Дх, у)) <=> 1 ЭхЭуДх, у, Дх, у)) Вся фраза в целом может быть записана следующим образом: Vx (ЗуДх, у, Дх, у)) & УуЗгДх, у, z) & Vy] Дх, у, Дх, у))) Пример 12 Если всякий разумный философ — циник, и только женщины являются разумными фи- лософами, то тогда, если существуют разумные философы, некоторые из женщин — циники [54]. Можно перефразировать это высказывание следующим образом: если из того, что для любого, кто является разумным философом, следует, что он циник, и из того, что для любого, кто является разумным философом, следует, что он женщина, тогда, если предположить, что разумные философы существуют, то существуют женщины, которые являются циниками.
92 Часть I. Математические основы логического программирования Введем следующие предикаты: А(х) — х является разумным философом, С(х) — х является женщиной, С(х) — х является циником. (Vx«x) С(х)) & Vx(<x) -> 0(х))) -> (ЗхЛ(х) -> 3х(0(х) & С(х))) Является ли эта формула тавтологией, т. е. является ли это рассуждение верным, мы проверим позже. о Синтаксис языка исчисления предикатов Пока мы говорили о языке логики предикатов на содержательном уровне. Для того чтобы формально описать язык как язык формальной теории (исчисления), необхо- димо задать множество его символов (словарь) и правила построения выражений (формул) языка — его синтаксис. Конструкции языка: логические связки, кванторы, предметные переменные, пред- метные константы, функциональные символы, предикатные символы, термы, выска- зывания, атомарные формулы и формулы. Так как язык исчисления высказываний является частным случаем языка исчисле- ния предикатов, то высказывания (пропозициональные формулы) являются форму- лами языка исчисления предикатов. Символы языка: □ логические связки'. { , &, v, □ кванторы. {V, 3}; □ предметные переменные-, обычно, но не обязательно, обозначаются последними буквами латинского алфавита, иногда с индексами: {w, v, w, х, у, г, х, X], х2, ...}; □ предметные константы, обычно, но не обязательно, обозначаются первыми бук- вами латинского алфавита, иногда с индексами или пишутся с большой буквы, или используются сочетания букв {д, Ь, с, а^ А, В, ...}. ______Замечание j В языке программирования Пролог предметные переменные начинаются со строч- ной буквы, а имена, предметные константы — с прописной буквы. Функциональные символы. g, ... F, ср, ...}. Могут использоваться также обозна- чения известных математических функций и операций: sin, cos, In, >1, 4- и т. д. Пре- дикатные символы. {Р, Р], ..., 0, 0], ..., R, Замечание j Каждый функциональный или предикатный символ имеет определенную "местность* ("арность"), которая иногда указывается в виде верхнего индекса, например, f™, p(3}t FP\ иногда это записывается так: /711 р/3, R/2 или определяется по контексту. Термы — это или предметные константы, или предметные переменные, или выраже- ния вида
Гпава 2. Исчисление предикатов и теории первого порядка где /V0 — л-местный функциональный символ, tn — термы, п > 0 (при п =0 такой терм называют предметной или функциональной константой). Атомарные (или элементарные) формулы — это выражения вида /л) где Р — л-местный (л-арный) предикатный символ, — термы, л>0, при п = 0 атомарная формула совпадает с пропозициональной переменной. Формулами исчисления предикатов являются либо атомарные формулы, либо выра- жения вида: 1 F (F, & F2), (Fj V F2), (Fj -> F2), (Fj s F2), VXyF, 3x,F где F, Fj, F2 — формулы, jq — предметная переменная. Литералами называется формулы вида Р или 1 Р, где Р — атомарная формула. Любое вхождение предметной переменной х,- в формулу вида Х/Х/Гили 3XjFназывает- ся связанным, а предметная переменная связанной. Формула, к которой применяется квантор по данной переменной, называется областью действия этого квантора. Предметная переменная (вхождение предметной переменной) называется свободной (свободным вхождением), если она (оно) не находится в области действия какого- либо квантора. Формула, не содержащая свободных переменных, называется замк- нутой. Такая формула представляет собой высказывание, не зависящее от предмет- ных переменных. Пример 13 Р(х, у) & R{£) \fx{R{x) & 0(у)). В этом примере первое вхождение переменной х является свободным, второе связанным, первое и второе вхождения переменной у являются свободными, единственное вхождение переменной z является свободным. Таким образом, переменная х является одновременно и свободной, и связанной в этой формуле, а переменные у и z — свободными. 6 Пример 14 Vx(P(x, а) —> Зх7?(х)) не является формулой, так как VxQ и 3xQ — формулы только в том случае, если Q не содержит связанных вхождений х, выражение Vx(3xP(*)) не является правильно построенным выражением. 6 Пример 15 \fxP(x, у) 3xR(x). Здесь две связанные переменные получили одно и то же имя, что допустимо. Пример 16 ЧхР(х) —> 7?(х). Здесь одна и та же переменная является и связанной, и свободной. Одну из переменных лучше переименовать Х/хР(х) —> R(y). О
94 Часть I. Математические основы логического программирования Семантика исчисления предикатов Задав правила построения формул исчисления предикатов, мы задали синтаксис формальной теории. Можно написать цепочку символов, не задумываясь об их смы- словой нагрузке, которая будет правильно построенным выражением, то есть фор- мулой исчисления предикатов. В зависимости от смысла, подразумеваемого под этими символами, формула будет истинной или ложной. В примерах 1 и 3 истинность или ложность формулы зависит не только от смысла предикатного символа, но и от значений, которые могут принимать предметные пе- ременные. В примере 2 (большая теорема Ферма) можно изменить интерпретацию предикат- ного символа PF(x, у, z, и), чтобы формула была заведомо истинна или заведомо ложна. Формула VxVyVz(R(x, у) & R(y, z) -> R(x, г)) ложна в интерпретации, которую она имеет в примере 4, но истинна в интерпрета- ции примера 5. Формула (VxWiW & Р1(У) Ж j)) & R^M) & R2(T)) Я(М, Т) истинна в интерпретации примера 6 и будет истинной в любой другой интерпре- тации. Таким образом, истинность или ложность формул исчисления предикатов может быть проверена путем приписывания смысла языковым конструкциям, т. е. их ин- терпретации. Формула в зависимости от интерпретации может быть истинной или ложной, но может от нее и не зависеть. Для того чтобы определить интерпретацию, необходимо, прежде всего, задать мно- жество значений, которые могут принимать свободные переменные, операции, при- писываемые функциональным символам, отношения для предикатных символов. Интерпретацию / можно определить следующим образом [36]: Определение 2.3 Интерпретацией / множества формул исчисления предикатов называется сле- дующая четверка (кортеж): / = <М, /v, If, 1Р>, где М — непустое множество, область интерпретации; Iv — отображение, кото- рое сопоставляет каждой предметной переменной элемент из М; If — отобра- жение, которое сопоставляет каждому п-местному функциональному симво- лу f некоторую функцию If (/) : Mn -+ М; 1р — отображение, которое сопостав- ляет каждому ^-местному предикатному символу Р некоторую функцию /ДР) : Л/* —> {true, false}. G
; лава 2. Исчисление предикатов и теории первого порядка 95 Правила, по которым формула F принимает свое значение истинности 1(F): □ предметная константа — определенный элемент из М\ 21 если х — свободная переменная, то I(x) — Iv(x); 21 если f — функциональный //-местный символ, то I(f(t{, 0) = If(f(I(t(y ..., I(tn)))', □ если F~ предикатный ^-местный символ, то f(P(t[, /*)) = /р(ЛДЛ)> •••, /(0)); □ если tjHtj— термы, то /(// = //)—true, если I(tj) = I(tj), в противном случае false; □ если F и G формулы, то 1 F, (F& 0, (Fv 0, (F-+ 0, (F= 0 интерпретируются как в исчислении высказываний; П I(VxF) = true, если /(/*) = true вне зависимости от 4(х); □ 1(ЭхГ) = true, если 1(F) = true хотя бы при какой-нибудь интерпретации Iv(x). Формула F называется истинной в данной интерпретации /, если 1(F) = true, ложной в данной интерпретации, если 1(F) = false. Таким образом, интерпретация исчисления предикатов состоит из множества М и отображения, ставящего в соответствие каждой предметной константе элемент из М, каждому л-местному функциональному символу — л-местную операцию на М, л-местному предикатному символу — л-местное отношение на М. Термы исчисле- ния, не содержащие предметных переменных, отображаются в элементы из М. Алгеброй (см. определение 1.4) называется множество с заданными на нем операциями. Моделью называется множество с заданными на нем отношениями. Алгебраической системой называется множество с заданными на нем операциями и отношения- ми [41]. Таким образом, интерпретация исчисления предикатов заключается в отображении конструкций языка на алгебраическую систему, определенную на М (с носителем М). Формула, не содержащая свободных переменных, замкнутая формула, представляет собой истинное или ложное высказывание о функциях и отношениях на множестве М. Формула, содержащая свободные переменные, открытая формула, представляет собой некоторое отношение на множестве М, которое превращается в высказывание о выполнении этого отношения для предметных констант, интерпретируемых как имена элементов из М. Определение 2.4 Открытая (содержащая свободные переменные) формула исчисления предикатов называется выполнимой в данной интерпретации, если при некоторой подста- новке предметных констант она превращается в истинное высказывание. В про- тивном случае она называется невыполнимой (противоречивой, ложной) в дан- ной интерпретации. Пример 17 ЗхДх, у)~+УхР(х, у) — формула открытая, так как содержит свободную переменную у. Пусть Р(х, у) интерпретируется как х > у на М — [а, £]. Если b > у > а, то посылка истинна, а заключение ложно и формула ложна. Если взять у — а, то и левая и пра-
96 Часть I. Математические основы логического программирования вая части истинны и вся формула истинна, следовательно, формула выполнима в данной интерпретации. Если Р(х, у) интерпретируется как х >у, М = (а, /»), то левая часть будет всегда истинна (при любом у), а правая — всегда ложна, следова- тельно, формула тождественно ложна или невыполнима в данной интерпретации. 6 Определение 2.5 Открытая формула называется (тождественно) истинной в данной интерпретации, если она выполняется (превращается в истинное высказывание) при любой под- становке предметных констант. Формула, истинная в любой интерпретации, на- зывается общезначимой или тавтологией. Формула, ложная в любой интерпрета- ции, называется противоречием (противоречивой) или невыполнимой. 6 Рассмотрим несколько примеров, поясняющих это определение. Пример 18 Формула УхУуУг(7?(х, у) & R(y, z) -+ 7?(х, г)) является замкнутой формулой. Она представляет собой высказывание, которое будет истинным, если 7?(х, у) интерпре- тировать, например, как неравенство или равенство на множестве чисел или как отношение кровного родства на множестве людей. Если же интерпретировать как отношение дружбы на некотором множестве людей, то формула может быть истин- ной или ложной в зависимости от этого множества. Ь’ Пример 19 Формула Vx(J\x) ч "I Р(х)) — замкнутая формула. Она является тождественно-ис- тинным высказыванием в любой интерпретации — общезначимая формула. Пример 20 Формула 3x(/Xx) & "I Л*)) — замкнутая формула. Это тождественно ложная формула в любой интерпретации — невыполнимая формула (противоречивая формула или противоречие). Ь’ Определение 2.6 Интерпретация называется моделью некоторого множества формул, если любая из формул истинна в данной интерпретации. Как и формулы исчисления высказываний, формулы исчисления предикатов делятся на три класса: □ общезначимые формулы — формулы, истинные в любых интерпретациях; □ противоречия — формулы, невыполнимые (ложные) в любых интерпретациях (отрицание общезначимых);
Гпава 2. Исчисление предикатов и теории первого порядка 97 □ нейтральные формулы — формулы, истинные в одних и ложные в других интер- претациях. Наиболее важным классом формул являются общезначимые формулы, так как лю- бая интерпретация является их моделью, или, проще, это общелогические формулы, справедливые независимо от предметной области. Так как формулы исчисления высказываний являются формулами исчисления пре- дикатов, то и тождественно истинные формулы (тавтологии) исчисления высказыва- ний являются общезначимыми формулами исчисления предикатов. Общезначимые формулы исчисления предикатов являются общезначимыми и в лю- бой формальной теории первого порядка (см. разд. "Прикладные исчисления предика- тов" данной главы). Для исчисления высказываний существует стандартная процедура исследования ис- тинности формул — определение значения формул на всех возможных наборах зна- чений пропозициональных переменных. В исчислении предикатов такая процедура затруднена из-за бесконечного множества интерпретаций формул исчисления пре- дикатов. Эквивалентные преобразования формул Определение 2.7 Формулы называются эквивалентными, если при всех подстановках предметных констант (при всех интерпретациях) они принимают одинаковые значения. В частности, все общезначимые (тождественно истинные) и все тождественно ложные формулы эквивалентны. О Если две формулы Р и Q эквивалентны, то формула P=Q общезначима. Определение 2.8 Формула Q логически следует из формулы Р, если в интерпретации, когда ис- тинна Р, истинной является и Q, что будем обозначать Р Q. О Аналогично тому, как это было в исчислении высказываний, Р Q тогда и только тогда, когда /М Q тождественно истинна (общезначима). Так как формула Р= Q эквивалентна формуле (Р Q) & (QР), то эквивалент- ность формул (вместо утверждения, что формула Р = Q общезначима) можно запи- сывать так: Р <=> 2, то есть из Р логически следует Q, а из Q логически следует Р. Определение 2.9 Формула F логически следует из некоторого множества формул £ = {/*), ..., Гл}, если при каждой интерпретации, когда все Р] истинны (т. е. когда истинна их конъюнкция), истинной является и F, что будем записывать {/*), ..., Fn} /^или S=> F. О 4 Зак. 963
98 Часть /. Математические основы логического программирования Утверждение 2.1 "I ЗхДх) <=> Vx~l F(x) (2 J) G Доказательство Если правая часть истинна, то для любого х истинна 1 Дх), т. е. ложна Дх), а значит не найдется х, при котором истинна формула Дх), следовательно, ис- тинно высказывание 1 ЗхДх). Если правая часть ложна, то найдется х — а такое, что F(a) истинна, следователь- но, существует х, при котором Дх) - истина и, значит, 1 ЗхДх) - ложь, т. е. и левая часть ложна. Таким образом, эти формулы эквивалентны. О Аналогично доказывается формула: VxF(x) <=> 3x1 Дх) (2.2) Утверждение 2.2 (\/хДх) & VxG'(x)) <=> \/х(Дх) & (7(х)) (2.3) G Доказательство Если левая часть истинна, то, какое бы ни было х, истинны по отдельности Дх) и <7(х), следовательно, истинно Дх) & С(х), а значит, истинна правая часть. Если левая часть ложна, то при каком-то х = а ложна F[a) или при каком-то х = b ложна G(b). Следовательно, при х = а или х - b будет ложна Дх) & G(x) и, значит, ложна правая часть. Таким образом, снова получаем, что левая и правая формулы при одних и тех же значениях х истинны или ложны. 6 Аналогично доказываются формулы: (ЗхДх) v 3xG(x)) <=> Зх(Дх) v G(x)) (ЗхДх) -> Зх(7(х)) <=> Зх(Дх) -> (7(х)) (2.4) (2.5) Если в формулах 2.3—2.5 поменять V на 3, а 3 на V, то получатся не эквивалентно- сти, а логические следствия в одну сторону: (Х/хДх) v VxG(x)) \/х(Дх) v (7(х)) (2.6) Зх(Дх) & (7(х)) (ЗхДх) & Зх(7(х)) Vx(F(x) -> G(x)) (\/хДх) -> Vx£(x)) (2.8) Следствием формулы 2.8 является формула: Vx(F(x) = G(x)) (\/хДх) s Ух(7(х)) (2.9)
Гпава 2. Исчисление предикатов и теории первого порядка 99 Логическое следствие означает, что утверждение слева от знака логического следст- вия более сильное, чем правое. Поясним формулу 2.6 на примере. Пусть, например, х — множество товаров. F(x) означает, что товар с наименованием х дешевый, G(x) — товар качественный. Тогда, если любой товар дешевый или лю- бой товар качественный УхДх) v VxG(x), то из этого следует, что любой товар или дешевый, или качественный \/х(Дх) v G(x)). Если же любой товар дешевый или ка- чественный Ух(Дх) v G(x)), то могут найтись товары недешевые, но качественные, и некачественные, но дешевые, так что будет неверно утверждение, что все товары дешевые, и неверно утверждение, что все товары качественные. Утверждение слева будет ложно. Аналогично для формулы 2.7, из того, что есть товар дешевый и есть товар качественный ЭхДх) & 3xG(x), не следует, что есть товар и качественный, и дешевый одновременно Эх (Дх) & G(x))- Если же есть товар, обладающий обоими качествами, то можно утверждать, что есть товар, который обладает первым из них, и товар, который обладает вторым (может быть, единственный экземпляр, обладаю- щий обоими качествами). Ранее было показано (см. пример 3), что перестановка кванторов разного типа при- водит к неправильному результату. Следующие эквивалентности показывают, что перестановка одинаковых кванторов допустима. VxVyF(x, у) <=> УуУхДх, у) ЭхЭуДх, у) <=> ЭуЭхДх, у) (2.10) (2.11) Если R — формула, не содержащая свободных вхождений х, F и G — любые форму- лы, не содержащие кванторов по х, то будут иметь место следующие эквивалент- ности: R & УхДх) <=> Vx(F & Дх)) R v 3xF(x) <=> 3x(7? v Дх)) R & ЭхДх) <=> Эх(7? & Дх)) R v УхДх) <=> Vx(7? v Дх)) R -> VxF(x) <=> Vx(7? Дх)) R -> 3xF(x) <=> Эх(/? Дх)) ЭхДх) -> R о Эх(Дх) A) УхДх) R Ух(Дх) R) (2.12) (2.13) (2.14) (2.15) (2.16) (2.17) (2.18) (2.19) В исчислении предикатов (так же, как и в исчислении высказываний) действует принцип двойственности: если в логической эквивалентности, не содержащей связ- ки поменять местами true и false, & и v, кванторы V и 3, то опять будет полу- чена логическая эквивалентность. Утверждение 2.3 Если ДЛ) — формула, в которой выделено вхождение Л, ДВ) — формула, полу- ченная из ДЛ) заменой этого вхождения А на В, тогда, если А <=> В, то ДЛ) « ДВ). G
100 Часть I. Математические основы логического программирования Например, пусть имеется формула: F[A) = 3y(F(x, у) & 1 VxG(x, у)) Здесь А =1 VxG(x, у) можно заменить на В = 3x1 G(x, у), получим эквивалентную формулу: К&) = Зу(Дх, у) & Зх 1 6(х, у)) Исчисление предикатов первого порядка Для исчисления высказываний, кроме проверки формулы с помощью таблицы ис- тинности, имеется еще один эффективный способ для проверки того, что данная формула является тавтологией. Этот метод заключается в формальном выводе фор- мулы из множества аксиом с помощью правил вывода исчисления. Для исчисления предикатов процедура проверки формулы с помощью таблицы истинности не при- менима. Тем не менее существует эффективный метод проверки того, что формула исчисления предикатов является общезначимой — формальный вывод формулы в соответствующем исчислении. Аксиомы Существуют представления исчисления предикатов, отличающиеся использованием различных наборов логических связок и разными системами аксиом. В состав аксиом входят аксиомы исчисления высказываний (какая-нибудь из систем аксиом) и две собственные аксиомы. 1. Аксиомы исчисления высказываний U-+ (Г-> Ц) (U-+ (Г-> W)) -> ((U-+ V) -> (UИ^) (2.20) (2.21) (1 [/^1 ^->((1 V)U) (2.22) 2. Собственные аксиомы исчисления предикатов. Для того чтобы сформулировать собственные аксиомы исчисления предикатов, нуж- но ввести понятие терма, свободного для переменной в данной формуле. Терм I называется свободным для переменной х в формуле F, если никакое свободное вхождение х в формулу F не лежит в области действия никакого квантора по пере- менной, входящей в терм /. Например, если термом является переменная у, а формула имеет вид (VyP(j)) & 2(*), то у свободно для х, так как х не находится в области действия квантора по у, чего нельзя сказать в случае, если формула имеет вид Уу(Р(у) & Q(x)). Если терм t = Дх, у), то он свободен для переменной z в формуле ЗуДх, у) -> Q(z), но не свободен в формуле ЗДДх, у) Q(z)). При этом выполняется следующее: □ любой терм, не содержащий переменных, свободен для любой переменной в данной формуле;
Глава 2. Исчисление предикатов и теории первого порядка 101 □ терм свободен для любой переменной в формуле, если никакая переменная тер- ма не является связанной в этой формуле; □ переменная х свободна для х в любой формуле; □ любой терм свободен для переменной в формуле, не содержащей свободных вхождений этой переменной. УхДх) ДО F(t) ЗхДх) (2.23) (2.24) В этих аксиомах t свободен для х в формуле Д ДО получена из Дх) заменой всех свободных вхождений х на /. Если эти требования не соблюдаются, то это может привести к неверным заключе- ниям. Рассмотрим следующий пример. В качестве Дх) не может быть формулы ЭуМ(у, х). Подстановка в аксиому 2.24 даст: Vx3yM(y, х) ЗуМ(у, у) Пусть М(у, х) интерпретируется как множество людей таких, что у является матерью х, тогда получим высказывание: "если для всякого х найдется у, являющаяся его матерью, то найдется у, являющаяся сама себе матерью". Так как посылка истинна, а заключение ложно, то высказывание ложно. Правила вывода В этих аксиомах предполагается, что G(x) содержит свободные вхождения х, a F их не содержит, в противном случае можно получить неправильные выводы. Правило заключения (modus ponens) такое же, что и в исчислении высказываний: (2.25) Правило обобщения или правило введения квантора всеобщности: F-+ G(x) F VxG(x) Правило введения квантора существования'. &х) F (2.26) (2.27) 3xG(x) F Пример 21 Пусть Дх) и 2(х) такие, что Дх) (?(х) истинно при любом х, например, х — лю- бое вещественное число, Дх) означает, что х > 0, (?(х) означает, что модуль х > 0. Применив, невзирая на требование, чтобы левая часть импликации не содержала свободных вхождений х, правило введения квантора всеобщности (правило обобще- ния), получим формулу:
102 Часть /. Математические основы логического программирования Р(х) -> VxQ(x) которая истинна при х < 0 и ложна при остальных х. Применив теперь корректно правило введения квантора существования, получим формулу ЭхР(х) -> VxQ(x) которая всегда ложна, так как левая часть импликации — истинное высказывание, а правая часть — ложное ((2(0) — ложно, и, следовательно, VxQ(x) — ложное выска- зывание). Свойства исчисления предикатов Как уже говорилось ранее, при рассмотрении свойств исчисления высказываний и методов получения общезначимых формул существуют два подхода: семантический, на основе интерпретации формул, и синтаксический, на основе формального вывода из аксиом с помощью правил вывода и вспомогательных метатеорем. Для исчисле- ния высказываний существует метод построения таблиц истинности. Для исчисле- ния предикатов этот метод либо затруднен, либо невозможен, поскольку для уста- новления всех значений формулы требуется перебор значений предметных перемен- ных, который приведет либо к большим вычислениям, либо вообще невозможен, так как область интерпретации может быть бесконечной. Если область интерпретации М — {«i, ..., а„} конечна, то tfx/V) о Ж) & & Ж) ЗхР(х) <=> V Р[а2)ч ... V Р[ап) Заменив все формулы, содержащие кванторы, с помощью этих соотношений, полу- чим формулу, не содержащую кванторы. Истинность такой формулы на конечной области проверяется с помощью конечного числа подстановок и вычислений значе- ний истинности. Для бесконечных областей этот метод непригоден. И семантический и формально-логический (синтаксический) методы приводят к од- ному и тому же результату. Напомним, что формальная теория, в которой всякая доказуемая (выводимая в дан- ной теории) формула (теорема) тождественно истинна (общезначима), а любая об- щезначимая формула доказуема, называется полной. Теорема (Геделя) о полноте исчисления предикатов Исчисление предикатов первого порядка — полная теория. О Определение 2.10 Теория называется семантически непротиворечивой, если ни одна из ее теорем (формул, выводимых в данной теории) не является противоречивой, то есть лож- ной в любой интерпретации. G
Гпава 2, Исчисление предикатов и теории первого порядка 103 Теория, имеющая модель, непротиворечива, так как ее теоремы не будут ложны в любой интерпретации. Поскольку все теоремы исчисления предикатов (как и исчисления высказываний) общезначимы, то любая интерпретация будет моделью множества теорем исчисле- ния предикатов, ни одна из теорем не будет противоречивой и, следовательно, ис- числение предикатов (как и исчисление высказываний) — непротиворечивая теория. Теория называется формально непротиворечивой, если не существует формулы F такой, что F и ~| Г являются теоремами теории. Для любой теории, содержащей ис- числение высказываний, из того, что она семантически непротиворечива, следует, что она и формально непротиворечива. Верно и обратное, более глубокое и гораздо сложнее доказываемое утверждение: по всякому формально непротиворечивому множеству формул (теорем некоторой теории) можно построить его модель, — ин- терпретацию, в которой все они будут истинны. Теорема Множество формул формально непротиворечиво в том и только в том случае, ес- ли оно семантически непротиворечиво. 6 Исторически к возникновению формального (синтаксического) подхода в математи- ке привели парадоксы теории множеств, когда выяснилось, что в теории множеств, рассматриваемой на семантическом, содержательном уровне, есть противоречия. Любая математическая теория рассматривалась с точки зрения описываемой ею мо- дели, поэтому неевклидовы геометрии Лобачевского и Больяй не воспринимались современниками до тех пор, пока не были найдены геометрические модели этих теорий. Лобачевский и Больяй обосновывали законность этих теорий отсутствием в них противоречий, то есть исходя из современного понятия формальной непротиворечивости (см. гл. 1). Если некоторое множество формул (правильно построенных выражений) с заданной интерпретацией можно аксиоматизировать так, что будет получена полная (адекват- ная) и непротиворечивая формальная теория, то в этом случае говорят, что множе- ство формул (содержательная теория) аксиоматизируемо, теория называется аксио- матизируемой. Определение 2.11 Формальная теория называется разрешимой, если существует алгоритм, который для любой формулы определяет, является ли она теоремой (т. е. может ли быть построен вывод этой формулы в данной теории) или нет. — Исчисление высказываний является разрешимой теорией, так как для любой фор- мулы можно построить таблицу истинности и определить, будет ли она тождествен- но истинной (тождественно ложной) формулой, а следовательно, будет она или нет теоремой исчисления высказываний. Исчисление предикатов не обладает этим свойством.
104 Часть I. Математические основы логического программирования Теорема (Черча) Исчисление предикатов неразрешимо. G Несмотря на это, есть случаи, когда разрешающий алгоритм построить удается. Это имеет место для исчисления, содержащего только одноместные предикаты, и для формул, которые не содержат кванторов. Если некоторая формула является теоремой исчисления предикатов, то существует алгоритм для проверки этого факта. В противном же случае сделать это невозможно. С точки зрения логического программирования это означает следующее. Если стро- ится вывод для выводимой формулы, то он может быть построен за конечное время. Если тот же алгоритм будет пытаться распознать невыводимую формулу, то этого ему сделать не удастся (аналог зацикливания). Прикладные исчисления предикатов Построенное выше исчисление предикатов называется исчислением предикатов пер- вого порядка. Слова "первого порядка" означают, что в этой теории кванторы до- пускаются только по предметным переменным и не допускаются по предикат- ным и функциональным символам, т. е. запрещены выражения вида VPC/^x)) или V/(P(f (х))). Кроме того, запрещены предикаты, которые в качестве своих аргумен- тов имеют другие предикаты, например P(Q(x)). Хотя в языке исчисления предикатов предусмотрены предметные константы и функциональные символы, — они заготовлены как бы впрок. Такое исчисление предикатов называют чистым исчислением предикатов. Исчисление предикатов, в котором имеются определенные функциональные, предикатные символы и пред- метные константы, называется прикладным исчислением предикатов или теорией пер- вого порядка. Например, в качестве предикатных символов используются предикаты =, >, <; функциональных букв — знаки арифметических операций; предметных кон- стант — натуральные числа, единица (в теории групп), пустое множество (в теории множеств). Кроме того, к аксиомам чистого исчисления предикатов добавляются собственные аксиомы, в которых участвуют конкретные предикатные буквы и пред- метные константы. Теории с равенством Большинство прикладных исчислений содержит предикат равенства и определяю- щие его аксиомы. Аксиомы для равенства: 1. Vx(x = х) 2. (х = у) -> (Дх, х) -> Дх, у)) где Дх, х) — произвольная формула, Дх, у) — получается из Дх, х) заменой некого- рых свободных вхождений х на у при условии, что у в этих вхождениях также оста- ется свободным. Всякая теория, в которой эти формулы являются аксиомами или теоремами, назы- вается теорией с равенством.
Гпава 2. Исчисление предикатов и теории первого порядка 105 Теория частичного упорядочения Пусть имеется единственная предикатная буква R(x, у), означающая отношение, аналог неравенства, поэтому будем его обозначать х < у. Пусть теория содержит две собственные аксиомы: 1. Vxl (х < х) 2. VxVyV^(x <у&у<г-»х<г) Всякая модель этой теории называется частично упорядоченной структурой. Формальная арифметика Первое полуаксиоматическое построение получило название системы аксиом Пеано. □ О — натуральное число; □ для любого натурального числа есть другое натуральное число, обозначаемое х’ и называемое непосредственно следующее за х; □ 0 * х для любого натурального числа; □ если х = у’, то х = у; О если Q есть свойство, которым, быть может, обладают одни и не обладают другие натуральные числа, и если: • 0 обладает свойством 2; • для любого натурального числа х из того, что х обладает свойством Q, следует, что и натуральное число х' обладает свойством Q, то свойством Q обладают все натуральные числа (принцип индукции). Этих аксиом, вместе с некоторым фрагментом теории множеств, достаточно для построения не только арифметики, но и теории рациональных, вещественных и комплексных чисел [54]. Тем не менее эта система имеет ряд интуитивных понятий (например, "свойство’*) и не является полной формализацией. Построим систему аксиом теории первого порядка, основанную на системе аксиом Пеано. Единственной предметной константой является 0 (ноль). Одноместную функцию />(х), дающую следующее за х число, будем обозначать х'. Двухместные функции сложения /](х, у) и умножения ^(х, у) обозначим привычным образом: х+у и ху, вместо А(х, у) и 1 А(х, у) будем писать х = у и х / у Собственные аксиомы формальной арифметики:
106 Часть I. Математические основы логического программирования □ X + у = (х + у)'; □ хО = 0; □ лУ = (ху) + х; □ ДО) —> (\/х(Дх) —> Дх')) —> УхДх)), где Дх) — произвольная формула теории. Теорема (Геделя) о неполноте Любая формальная теория, содержащая формальную арифметику, неполна: в ней существует и может быть построена замкнутая формула /’такая, что 1 /’истинно, но ни Д ни 1 Дне выводимы в этой теории. G Эта теорема имеет важное методологическое значение, заключающееся в том, что для достаточно "богатых" математических теорий не существует адекватной форма- лизации, то есть они не могут быть аксиоматизированы. Арифметика и большинство других дисциплин (исключением является лишь геомет- рия) не могут быть аксиоматизированы, нельзя составить полную систему аксиом потому, что всегда найдутся истинные теоремы, которые, тем не менее, нельзя фор- мально доказать, исходя из любой системы аксиом, — как бы мы эту систему ни расширяли. Есть различие между истинностью и выводимостью — существуют тео- ремы истинные, но не выводимые, не доказуемые в рамках данной системы аксиом.
ГЛАВА 3 Логический вывод в исчислении предикатов Не существует метода для установления общезначимости или ее отсутствия для про- извольной формулы исчисления предикатов: исчисление предикатов является нераз- решимым. Тем не менее для некоторых классов формул это сделать можно, в частности если формула исчисления предикатов общезначима, то существует процедура для уста- новления этого факта. Та же процедура, применяемая к формулам, не являющимся общезначимыми, может привести к бесконечной последовательности операций, т. е. никогда не закончится. Язык логического программирования Пролог, как и современные системы искусст- венного интеллекта, представления и извлечения знаний, основан на исчислении предикатов и методах автоматизации логического вывода. Подробное изложение вопросов, связанных с автоматизацией доказательства теорем в исчислении предикатов первого порядка (методом резолюций) можно найти, на- пример, в книгах [57, 58, 61, 68, 81, 86]. Вопросам, связанным с обратным методом С. Ю. Маслова, посвящены дополнения в [81] и [43—46, 49-51]. Логическое следствие в исчислении предикатов Пусть есть множество формул исчисления предикатов S— {Aj, ..., Fn}. Если каждая из формул этого множества истинна при данной интерпретации, то говорят, что данная интерпретация удовлетворяет этому множеству или является моделью этого множества формул. Так же как и в исчислении высказываний, формула Алогически следует из некото- рого множества формул S = {Аь ..., Fn}, если при каждой интерпретации, когда все А/ истинны (т. е., когда истинна их конъюнкция), истинной является и А, что будем записывать или {А], Ай} => А, или S => А. По-другому: каждая интерпретация, удовлетворяющая *V, удовлетворяет также и А
108 Часть I. Математические основы логического программирования (Замечание J Если S=>F, то это означает общезначимость формулы Е & ... из чего следует формальная выводимость в исчислении предикатов (- F^ & ... & Fn F, а из этого следует, что & ... & Fn р F или, по-другому, S |- F: из множества формул S может быть формально (в исчислении предикатов) выведена формула Е Неразрешимость исчисления предикатов будет означать, что для произвольных S и F нельзя в любом случае определить, имеет место логическое следование или нет, но если действительно S Е, то существует процедура, которая покажет это за конеч- ное время. С точки зрения программирования на Прологе это означает, что форму- ла, которую надо доказать в прикладном исчислении предикатов, должна логически следовать из множества формул, составляющих программу. В противном случае про- грамма или будет работать неправильно, или правильно, с точки зрения логики, но не будет приводить к требуемому результату. Если S Г, то любая интерпретация, удовлетворяющая S’, удовлетворяет F или, по-другому, никакая интерпретация, удовлетворяющая S’, не удовлетворяет "I F и, следовательно, множество формул S и {1 является неудовлетворимым или проти- воречивым. То же самое может быть выражено следующим утверждением: формула F, & ... & F„&1 false будет истинной в любой интерпретации или F\ & ... & Fn & "I F ==> false. Для того чтобы доказать, что из множества формул логически следует данная, берут отрицание этой формулы и добавляют его к исходному множеству формул. После чего доказывают противоречивость формулы, являющейся конъюнкцией формул исходного множества и данной. По существу, это не что иное, как метод доказатель- ства от противного. Примеры использования этого метода можно найти, например, в школьном учебнике геометрии. Предположим, что формула, для которой нужно найти ее противоречивость, полу- чена. Следующим шагом, который обеспечит возможность для применения метода резолюций, является приведение ее к форме предложения или клаузалъной форме. Преобразование формул: предваренная форма Определение 3.1 Предваренной формой называется формула вида Q\X\QjX2^. Q^nM, где Q; — один из двух возможных кванторов, Xj — переменные, входящие в формулу, М — формула, не содержащая кванторов. C?iXi£?2x2 •• ~~ называют префиксом^ М — матрицей формулы. О Утверждение 3.1 Для любой формулы исчисления предикатов существует логически эквивалент- ная ей предваренная форма. О
Гпава 3. Логический вывод в исчислении предикатов 109 Доказательство (конструктивное) заключается в алгоритме эквивалентных преобра- зований, содержащем следующие шаги: 1. Исключить связки эквивалентности и импликации: F = G & (G—>F) F-> G <=> "I Fv G 2. Уменьшить область действия отрицания так, чтобы знак отрицания был приме- нен не более чем к одной предикатной букве, для этого надо использовать сле- дующие эквивалентные преобразования: • "| VxF<=> 3x1 F • 1 3xF<=> Vxl F • 1 (F& G) Fvl G • l(FvG)«l F&l G • llF^F 3. Переименовать, если это необходимо, связанные переменные так, чтобы в облас- ти действия каждого квантора каждая связанная им переменная имела бы инди- видуальное имя, и имена свободных и связанных переменных не совпадали. Это можно сделать, т. к. имя связанной квантором переменной не влияет на значение формулы: Х/хДх, у) & 3xG(x, y)vl\x) <=> X/xPG^ у) & 3zG(z, v)vP(w) 4. Удалить кванторы, если область их действия не содержит переменной, связывае- мой квантором (квантифицированной переменной): УхУгДх, у, а) <=> ЧхР(х, у, а) 5. Переместить кванторы в начало формулы: 3xAx)v3xG(x) <=> 3x(Ax)vG(x)) Х/хДх) & VxG(x) <=> Х/х(Дх) & G(x)) 6. Если R — формула, не содержащая свободных вхождений х, то будут иметь место следующие эквивалентности: R & Х/хДх) <=> Vx(7? & Дх)) R & ЗхДх) <=> Зх(А & Дх)) Пример 1 Ух(Дх) & Vy3x(l 2(х, y)->VzF(a, х, у))) 1. Ух(Дх) & Vy3x(Q(x, y)vVzA(a, х, у))) 2. Ух(Дх) & Vy3w(Q(w, y)v/?(a, w, у))) 3. VxVXA*) & 3w(2(w, y)v/?(a, w, y))) 4. VxVy3w(Ax) & (Q(w, y)v/?(a, w, y))) О
110 Часть I. Математические основы логического программирования Пример 2 у))) & "I ^y(Q(x, у)->Лу)))) 1. Ух(1 ЛхМУуС P(y)vP(f(x, у))) & 1 vxl Q(x, y)v/V)))) 2. Vx(l P(xMVy(] P(y)vP(f(x, у))) & ЗЯ(1 Q(x, y)vP(y)))) 3. Vx(l P(x)v (Vy(l P(y)vP(f(x, y))) & 3y(Q(x, у) & 1 Р(У)))) 4. Vx(l ax)v(Vy(l P(y)vP(f(x, y))) & 3z(Q(x, z) & 1 Лг)))) 5. VxVy3z(l Л*М(1 P(y)vP(j\x, y))) & (Q(x, Z) & 1 Лг)))) ...........................................................................6 Преобразование формул: скулемовская и клаузальная формы Следующий шаг на пути применения метода резолюций заключается в приведении формулы, которая должна быть опровергнута (метод резолюций — метод приведения к противоречию или опровержению) к скулемовской, а затем к клаузалъной форме. Рассмотрим следующую замкнутую предваренную форму: F= 3xVy3zVw3vVwF(x, у, z, и, v, w) 1. Обозначим значение х, которое существует в соответствии с первым квантором, предметной константой, например буквой а, отбросив при этом квантор сущест- вования: F= Vy3zVwVv3w/(a, у, г, v, w) 2. Тот факт, что по всякому у может быть найдено значение для г, может быть вы- ражен некоторой функцией z ~ f{y), называемой скулемовской. F — 4y\/u\/v3wF{a, y,f(y), и, v, w) 3. Тот факт, что для любых у, и и у найдется w, можно выразить скулемовской функцией w = g(y, w, у), которая подставляется вместо w: F= VyVuVvFla, y,f(y), и, v, g(y, и, v)) Так как все переменные должны быть связанными, кванторы существования исклю- чены, а порядок кванторов всеобщности не влияет на значение формулы, то кванто- ры всеобщности отбрасывают, предполагая, что все переменные ими связаны. S> = F(a, у, f(y), и, v, g(y, и, v)) Для первого примера: F = WxVy3u(P(x) & (Q(u, y)vR(a, и, у))) Sf= Р(х) & (Q(f(x, у), y)vR(a,f(x, у), у)) и = f(x, у) — скулемовская функция.
Глава 3. Логический вывод в исчислении предикатов 111 Для второго примера: F= vwy3z{\ /V)v((l /V)v/VU Я)) & (C(X, Z) & 1 №)))) SF= 1 /\x)v((l P(y)vP(f(x, y))) & (Q(x, g(x)) & 1 /^x)))) < ~ g(x) — скулемовская функция. Клаузалъной формой называется скулемовская форма, матрица которой является конъюнктивной нормальной формой, т. е. конъюнкцией дизъюнкций. Для первого примера: s = {Pixy, Q(f(x, у), y)vR(a, f(x, у), у)} Для второго примера: приведение к конъюнктивной нормальной форме аналогично приведению в исчислении высказываний, используется формула: Av(B & Q « (AvB) & (AvC) 1. (1 P(x)vl /w w, У))) & (1 /W(C(x, £(x)) & 1 f\g(x))) «. 2. (1 a*)vl P(y^lV(.x, y))) & (1 P(x)vQ(x, g(x))) & (1 Р(Х)Л W)) Получена клауз аль ная форма, которую можно записать в виде множества дизъюнк- тов (предложений или клаузов — от англ, clause)’. S — {1 ЛхМ Ay)v/V(x, У)); 1 P(x)4Q(x, £(х)); 1 P(x)vl №))} Логическое следование: правило резолюций После того как получено множество дизъюнктов, осуществляется процесс опровер- жения, аналогичный тому, как это делалось в исчислении высказываний: на каждом элементарном шаге доказательства используется правило, называемое резолюцией (от англ, resolution — решение): {(UvV), (FK-l V)} => (UvW) или {И, (1 KvJP)} => W Теперь рассмотрим применение метода резолюций в исчислении предикатов для следующих примеров [54]. Пример 1 Всякий, кто находится в здравом уме, может понимать математику. Ни один из сы- новей Гегеля не может понимать математику. Сумасшедшие не допускаются к голосо- ванию. Следовательно, никто из сыновей Гегеля не допускается к голосованию. Введем предикаты: □ Z(x) — х находится в здравом уме; □ М(х) — х может понимать математику; □ 6(х) — х является сыном Гегеля; □ 7V(x) — х не допускается к голосованию. Тогда: □ первое предложение можно записать так: X/x(Z(x)-->M(x)); □ второе: 1 3x(G(x) & М(х));
112 Часть I. Математические основы логического программирования □ третье: Vx(l Z(x)-»JV(x)); □ четвертое предложение, являющееся заключением из первых трех: 1 3x(G(x) & 1 7V(x)). Опустим знак квантора, стоящий перед всей формулой, предполагая, что доказа- тельство будет проведено для некоторого фиксированного х, но т. к. х — любое зна- чение, то оно будет справедливо для всех случаев. Заменим импликацию на дизъ- юнкцию, "внесем” знак отрицания, стоящий перед квантором существования, заме- нив его на квантор всеобщности, и получим следующие предложения: 1. 1Z(x)v4/(x) 2. 1 G(x)vl М(х) 3. Z(x)vjV(x) 4. I 67(x)v7V(x) Докажем: {(1 Z(x)vM(x)), (1 67(x)vl M(x)), (Z(x)vMx))} 1 G(x)vN(x) Для доказательства методом резолюций возьмем отрицание заключения ~| (~| G(x)vjV(x)) <=> G(x) & ~|jV(x), добавим его к множеству посылок и докажем проти- воречивость полученного множества формул: {(1 Z(x)vM(x)), ("I 67(x)v"| Л/(х)), (Z(x)vMx)), 67(x), 1 7V(x)} false Вывод: 1. {(1 ад! Mix», ад => 1 м(х) 2. {1 М(х), (1 => 1 Z(x) 3. {1 Z(x), (Z(x)vN(x))} => Мх)} 4. ад, 1ад => false Другой вариант вывода: 1. {(1 Дх)уВД), (Z(x)vMx))} => M(x)vN(x) 2. {(M(x)vMx)), 1 N(x)} => M(x) 3. {M(x), (167(x)vl М(х))} => 1 G(x) 4. {1 G(x), G(x)} => false Таким образом, логическое рассуждение является верным. О Пример 2 Докажем правильность рассуждения: если всякий разумный философ — циник, и толь- ко женщины являются разумными философами, то тогда, если существуют разумные философы, некоторые из женщин — циники, приведенного в примере 12 (см. разд. "Язык логики предикатов" гл. 2), которое там было представлено в виде следующей формулы: (Vx(7?(x)->C(x)) & Vx(J?(x)->G!(x))->(3xA(x)->3x((7(x) & С(х))))
Гпава 3. Логический вывод в исчислении предикатов 113 Требуется доказать, что данная формула — тавтология, или, что то же самое, дока- зать, что: (Ух(Я(х)->С(х)) & Vx(7?(x)->G(x)) => (3xfl(x)->3x((7(x) & С(х)))) Преобразуем посылку и следствие, вынеся квантор по формулам 2.3 и 2.5: Vx(7?(x)->C(x)) & Vx(A(x)->G(x)) <=> Vx((7?(x)->C(x)) & (7?(x)->G(x))) ЗхЯ(х)->Зх((г(х) & C(x)) <=> Зх(Я(х)->((г(х) & C(x))) Квантор всеобщности отбросим, рассуждая, как и в предыдущем примере, а отбра- сывая квантор существования, подставим вместо х предметную константу а, с ее помощью обозначив то значение х, при котором выполняется формула. Получим следующую задачу логического вывода: {(Я(х)->С(х)), (Я(х)->С7(х))} (R(a)-+(G(a) & С(а))) Преобразуем отрицание заключения: 1 (R(a)-+(G(a) & ад)) « R(a) & (iG(fl)vlC(fl)) Добавив отрицание заключения к множеству посылок, получим следующую задачу на опровержение: {(1 7?(x)vC(x)), (I fl(x)v(7(x)), R(a), (1 С(л))} => false Логический вывод с помощью резолюций будет следующим: 1. {(1 7?(x)vC(x)), Я(л)} => С(а) 2. {C(«), (1 C(a)vlC(a))}^l G(a) 3. {1 G(a), (1 7?(x)v(7(x))} => 1 R(d) 4. {1 R(a), R(a)} => false Таким образом, мы проверили, что исходное рассуждение является логически верным. Метод резолюций в логике предикатов Методом автоматизации логического вывода в исчислении предикатов первого по- рядка, на котором основана реализация языка логического программирования Про- лог, является метод резолюций Дж. Робинсона [73]. Описанию этого метода и его теоретическим основам посвящены работы [30, 36, 39, 58]. Напомним терминологию, обычно используемую при описании этого метода. □ Под множеством формул понимается конъюнкция этих формул. Любая формула исчисления предикатов может быть приведена к предваренной конъюнктивной нормальной форме, и, таким образом, к форме множества дизъюнктов (предло- жений), причем каждая переменная в них считается относящейся к области дей-
114 Часть I. Математические основы логического программирования ствия некоторого квантора общности, кванторы существования исключаются путем введения функций Скулема. □ Под литералом понимают атомарную формулу или ее отрицание, таким образом, исходная формула представляется в виде множества дизъюнктов (предложений), или, другими словами, конъюнкции дизъюнкций литералов. Если вместо пере- менных литерала подставить выражения, не содержащие переменных, то такое выражение называется константным частным случаем. Например, для формулы (литерала) "| Р(х, у) константным частным случаем будет 1 Р^а), Ь), получаемая подстановкой на место значений переменных х и у кон- стант g(a) и Ь. Основная идея метода состоит в следующем. Для того чтобы доказать, что формула Алогически следует из некоторого множества формул 5*1 = {Ац AJ, можно доказать, что множество формул Si = А}, является противоречивым (неудовлетворимым), т. е. не существует интерпретации, в которой бы оно удовлетворялось. S2 = ^{1 F} = {Ft, .... F„, 1 F} Тогда множество S противоречиво тогда и только тогда, когда: 1 S2 <=> 1 (Ft & ... & F„ & 1 F) <=> 1 (F, & ... & F„)vF<=> (F( & ... & F„) -> F«=> St-*F является общезначимой (истинной в любой интерпретации) формулой или, иначе, что 5*1 А. Таким образом, необходимо найти способ доказательства противоречивости множе- ства предложений Si. Для того чтобы определить интерпретацию множества формул, необходимо задать область интерпретации. Проблема заключается в том, что таких областей может быть бесконечно много. В качестве такой области берут множество Ду, называемое универсумом Эрб рана, об- ладающее следующим важным свойством: если на Н$ множество S неудовле творимо, то оно неудовлетворимо на любой другой области интерпретации. Универсум Эрбрана строится (рекурсивно) следующим образом: 1. Множество всех предметных констант (константных букв), входящих в при- надлежит Ду. Если ни одной предметной константы не содержится в S', то Ду со- держит произвольную предметную константу, называемую любым именем. 2. Если термы tt (J = 1, ..., п) принадлежат Ду и fj — какой-либо функциональный п-арный символ, встречающийся в S, то^(Гь ..., /л) также принадлежит Ду. Примеры: Пусть 5= {A(«)v2(x, Z>); ~]P{x)^Q{b, у); "1Я(х, а, с)}, тогда Ду = {а, Ь, с}. Пусть 5= {P(x)vQ(x, у); НДх)); тогда Hs = {a, f(a), g(a), f(f(a)), g(g(a)), ...}.
Гпава 3. Логический вывод в исчислении предикатов 115 Пусть S= {Дх, o)v0(/'(x)); ДЬ, у)}, тогда Ду = {«, />,/(«), Л*),/(/(«)),/(/(/>)), -Л Пусть S = {"I Дх); &Дх, у), g(x))v^(x, у)}, тогда Ду = {а, Да, а), Да), ДДа, а)), Да, Да, а)), ДДа, а), а), Да, Да)),/(Да), а), Да, ДДа, а))),ДДДа, а), а)), ...}. .......................................О Как видно из примеров, универсум Эрбрана может быть конечным или бесконеч- ным, но счетным множеством. Эрбрановской базой для S называется множество всех константных частных случаев для всех атомарных формул, входящих в дизъюнкты из S и получаемых при подста- новке элементов из Н$. Примеры 5= {Дх)ДДа, y);j Да)У\ (Дх, у)}, то Ду= {а} база Эрбрана Е$ = {Р(а)9 Q(a9 а)}. 5={/V, a)vQ(f(x)Y Р(а, у)}, Hs= {a9f(a)9f(f(a)),f(f(f(a)))9 ...}, Es= {/>(«, «), /(«)), АЯ«), «), P(a9f(f(a)))9 P(f(f(a)l a), P(f(f(a))9 f(a))9 ...}. ........ О Метод резолюций основывается на теореме Эрбрана, доказанной в 1930 году. Теорема Эрбрана Множество дизъюнктов S невыполнимо тогда и только тогда, когда имеется ко- нечное невыполнимое множество Sc константных частных случаев этих дизъюнк- тов на Другими словами, множество S невыполнимо, если можно найти такие подстановки констант вместо предметных переменных, при которых полученное множество дизъюнктов будет противоречивым. Пример Пусть S = {Дх, у); 1 ДДа), b)vR(x, у); 1 R(x, b)}, Hs={a,b,/(a),/(b),...}. Если в качестве константного частного случая взять множество (высказываний) SC9 получаемое заменой х на f(a) и у на Ь, то получим: Se = {ДДа), b); 1 Д/(а), b)vR(/(a), b); 1 R(/(a), b)} являющееся невыполнимым.
116 Часть I, Математические основы логического программирования Покажем это путем эквивалентных преобразований: b) & (1 ЛГ(а), b)vR(fta), />)) & 1 R(f(a), b) « (P(f(a), b) & 1 Ptf(a), b)vP(f(a), b) & R(f(a), Z>)) & 1 R(f(a), b)^ (falsev/W), b) & R(f(a), b)) & V«f(a), b) « P(f(a), b) & R(f(a), b) & 1 R(f(a), b) « ЛЯ«), b) & false <=> false Следовательно, по теореме Эрбрана множество S невыполнимо. Методом резолюций для высказываний Sc: {Р(Да), b), 1 Р(Да), b)vR(f(a), b)} => R(f(a), b) {R(f(a), b) & 1 R(f(a), b)} => false В случае, когда множество состоит из большого числа дизъюнктов, а каждый из дизъюнктов состоит из большого числа литералов, найти такое множество неудовле- творимых частных случаев не так просто, как в данном примере. Этот поиск может осуществляться на основе построения семантического дерева [58, 81]. Метод резолюций (в исчислении предикатов) представляет собой процедуру опро- вержения (доказательства противоречивости) множества дизъюнктов, включающую в себя унификацию, т. е. нахождение нужной подстановки и, собственно, доказа- тельство противоречивости данного множества дизъюнктов при данной подстановке (данном константном частном случае). Унификация Рассмотрим S= {Р(х, Да))чР(х, f(y))vQ(y); 1 I\z, Да)); 1 Q(z)}. Если вместо z (во второй и третьей формулах) подставить х, вместо у (в первой фор- муле) подставить я, то получим: Р(х,Да)); 1 G(x)}, которое противоречиво (Ж /(fl))vG(fl); 1 Дх, Да))} 2(х), {(2(й); 1 GW} => false. Процесс подстановок с целью получения подходящих резолюций дизъюнктов назы- вается унификацией. Рассмотрим этот процесс более подробно [57, 58]. Пусть имеется атомарная формула (литерал) Дх, у, г). Термы литерала могут быть предметными переменными, предметными константами или выражениями, содер- жащими функциональные буквы. Результат подстановки вместо переменных раз- личных термов называется частным случаем, подстановочным частным случаем, или примером данного литерала. Для Дх,Ду), Ь) это могут быть: □ Д^Ду), Ь)' □ Дх,Да),Ь)-
Гпава 3. Логический вывод в исчислении предикатов 117 3 РШ, □ P(c,f(d), b). Первый вариант называется алфавитным вариантом исходного литерала, т. к. пере- менные в нем заменены на другие переменные. Последний (из четырех) вариант называется константным частным случаем или атомом, т. к. ни один из термов не содержит переменных. Подстановку можно представить в виде множества упорядоченных пар: э = {(Л, Vj), (Г2, V2), .... (/„, v„)} или: 9 = {/i | vb Г2 | v2, t„ | vn] где (rz, vz) или ti | у, означает, что повсюду переменная у, заменяется на терм В рассмотренных примерах были использованы следующие подстановки: □ а = {(^, х), (у, у)} — вместо х подставлено z> вместо у подставлено у; з Р = {(«,у)}; 3 Y = {(g(z), х), (д, у)}; □ 8 = {(с, х), (а, у)}. Будем обозначать РЭ частный случай (пример) литерала Р, получающийся при ис- пользовании подстановки 0: Р(х, /(у), b)5 ~ P(c,f(a), b). Композицией двух подстановок аир называется результат применения Р к резуль- тату подстановки а с последующей подстановкой тех пар из Р, в которых имеются переменные, не содержащиеся в а, и выбрасыванием тех пар из р, в которых имеет- ся переменная, вошедшая в а. Например, если: х = (Яу, Ф, 4г), Р = Ж Му, ММ, то «Р = {/(/>, с)|х, с|у, Ь\у, ф, d|w)} Здесь в результирующую подстановку не вошла подстановка л|х и Ь\у, т. к. перемен- ная х заменяется по первой подстановке. Для подстановок справедливо следующее: □ (73ос)р = РаР, т. е. последовательное применение подстановки а, а затем подста- новки р приводит к тому же результату, что и применение композиции подста- новок; □ (ар)у = сс(Ру), т. е. композиция подстановок ассоциативна. Результат применения подстановки 0 к каждому из литералов множества литералов обозначается {Д}0. Определение 3.2 A W*- v* V* V* * * Ч * V* V* V* V* * * Ч* V* * V* V* * * * * V* V* V* * * * V* * V* V* * V* V* V* V* V* V* Множество литералов {A,} i— 1, ..., п называется унифицируемым, если существует подстановка 0 такая, что L$ = L$ — ... = Lffi. Подстановка 0 в этом случае на- зывается унификатором для {AJ. О
118 Часть I. Математические основы логического программирования Пусть дано множество литералов {Дх, /(у), Л), Дх, ДД, Л)}. Тогда унификатором будет подстановка 0 = {а|х, Л|у}, в результате оба литерала будут унифицированы к общему виду P(a,f(b), b). Эта подстановка не была простейшей: достаточно было применить подстановку 0р = в результате чего был бы получен общий пример P(x,f(b), b), при этом 0 = 0Д0Ь где 0j = {а|х}. Пусть имеется множество литералов: {Дх, у), Да, у), 1 Д?, Л)} Это множество является неунифицируемым, т. к, никакая подстановка не может привести к общему виду (общему примеру) формулу и ее отрицание. Определение 3.3 Наиболее общим или простейшим унификатором для множества литералов {L,} яв- ляется подстановка 0р, если для любого другого унификатора 0 выполняется 0 = 0?0Ь 6 Существует алгоритм, назы ваемы й алгоритмом унификации, которы й при водит к наиболее общему унификатору для унифицируемого множества {Д} или сообщает о невозможности это сделать, если множество не унифицируемо. Определение 3.4 Множеством рассогласования множества литералов называется множество отличающихся термов, в первой слева позиции. 6 Для множества {Дх, Ду, Да)). Дх)), Дх, Ду, х))> у)} множеством рассогласования является {Да), х}: в первой слева позиции первого литерала стоит терм Да), который отличается от соответствующей (первой слева) позиции второго литерала — х. Алгоритм унификации Рассмотрим алгоритм унификации для двух литералов. На каждом шаге алгоритма подстановка применяется к каждому из литералов. Пусть на £-м шаге алгоритма: Lk — множество литералов, Dk — множество рассогла- сования, 0£ — осуществляемая подстановка. Алгоритм унификации заключается в следующем. Д — исходное множество литералов, 0q = е —"единичная" (ничего не меняющая) подстановка. На к~м шаге: □ если Dk = 0 (т. е. нет различающихся литер), то конец алгоритма, 0*, результи- рующая подстановка, — наиболее общий унификатор; □ если Dk не содержит переменных, то конец алгоритма, множество литералов не унифицируемо;
[лава 3. Логический вывод в исчислении предикатов 119 3 если множество рассогласования содержит переменную vk и терм tk, и если vk входит в tk, то конец алгоритма, множество литералов не унифицируемо. В противном случае переходим к следующему (к + 1)-му шагу. На (к + 1)-м шаге алгоритма: 0 = {^| 0^+i - 00^ выполняем подстановку для каждого из литералов, входящих в Lk: Lk+i = Lffi (или Lk+i - Lq 0^+0 Замечание j Если количество литералов больше двух, то последовательно унифицируется пер- вый со вторым литералом, затем результат унификации с третьим и т. д., пока не будет унифицировано все множество литералов или получен ответ о том, что мно- жество не унифицируемо. Утверждение 3.2 Пусть L — конечное множество литералов. Алгоритм унификации всегда закон- чит свою работу, при этом если множество литералов унифицируемо, то будет найден наиболее общий унификатор для L, в противном случае будет сообщено, что множество L не унифицируемо (т. е. не существует унификатора). Доказательство приведено в [57], более подробная информация об алгоритмах унификации в [81]. Рассмотрим примеры использования данного алгоритма (подчеркиванием отмечены элементы множества рассогласования). Пример 1 Lo = {P(x,f(y, gig)), h(x}), L\x,f(y, $), у}, Dq = {g(a), x}. 1. Do Ф 0, xeZ>o, т- e- не входит в состав букв g(a). 2. 9 = 0! = 990 = 9; L\ = ZqG] = {P(x,f(y, gM), Л(х))9ь P(x,f(y, x), y)9j} = = {P(g(a),f(y, g(a)), Mgfa).)), P(g(a),f(y, g(a)), у)). 3. Li = {P(g(a),f(y, g(a)), /?(#(й))), Kg&),f(y, g(a)), y)}; D{ = {A(g(a)), y}. 4. Di ф 0, ye Di, yeh(g(a)). 5. 9 = {A(g(a))|y}, 92 = 90} = {g(a)|x, A(g(a))|y}; L2 = Z]92 = {P[g(d),f(h(g(a)), g(a)), h(g(d))), P^a),, f(h(g(a)). g(«)), A(g(a)))}; Z?2 “ 0- 6. D2 = 0, L= {P(£(a), g(a)), h(g(a)))}, Qp = {g(a)|x, h(g{a))\y} - наиболее общий унификатор, конец алгоритма. О
120 Часть /. Математические основы логического программирования Пример 2 А) = №, Ж £Ш), Л(«)), Р(х, f(a, и), г)}, Do = {gtz), и} 6 = W|«}; L{ = {P(x,f(a, g(z)), h(g(z))), P{x,f(a, &z)), z)}, D{ = {h(g(z)), zY, гей(^г)), конец алгоритма, множество не унифицируемо. О Унификация является важнейшей составной частью метода резолюций, а алгоритм унификации — одним из важнейших механизмов языка логического программиро- вания Пролог. Исчисление метода резолюций Метод резолюций можно рассматривать как правило вывода в исчислении, реализуе- мом программой на языке логического программирования Пролог. Правильно построенными выражениями (формулами) этого исчисления Т$ являются предложения (дизъюнкты) и множества предложений (см. разд. "Преобразование фор- мул: скулемовская и клаузалъная формы" данной главы). Пусть имеется такое множество предложений 5 и дизъюнкт F, для которого требует- ся доказать, что S' F. Возьмем предложения из S' в качестве аксиом исчисления 7$. Единственное правило вывода (правило резолюции) следующее. Пусть имеются пред- ложения Z)j и Z?2 и унифицируемые литералы £] и £2 такие, что £]0 = L2Q, тогда: Z)]v£j, ^2 DfivD2Q В основе этого правила лежат два правила вывода исчисления предикатов: правило подстановки и правило заключения (modus ponens): По правилу подстановки £j(x) |- L\(t) и L2(x) |- L2(f), причем унифицируемость Ц и £2 означает, что имеется подстановка 0, при которой £]0 = L20 — L, тогда: (D[v£])0, (£>2v~| £2)0 то же самое, что DfivLO, £0->£>20, откуда по обобщению прави- ла modus ponens\ FvE, F- GvE получим £>]0v£0, £0 -> Dfi h Z)i0vZ)20. Определение 3.5 Резолютивным выводом в исчислении Т$ называется последовательность предло- жений, каждое из которых либо является предложением из S', либо получено из предыдущих предложений с помощью правила резолюции. О
Гпава 3. Логический вывод в исчислении предикатов 121 Определение 3.6 Формула (предложение, дизъюнкт) F выводима (доказуема, является теоремой) в исчислении Ту, S' |- F, если существует конечный резолютивный вывод, по- следним предложением которого является F. Утверждение 3.3 Если формула Г выводима в Ту, то Г логически следует из S: S => F Утверждение 3.4 Множество дизъюнктов S' невыполнимо тогда и только тогда, когда существует резолютивный вывод пустого дизъюнкта. Замечание J Пустой дизъюнкт (обозначается символом О или 1) будет выведен в том случае, если будут выведены некоторый литерал L и его отрицание 1L. Семантически это соответствует получению противоречия: S => false. Таким образом, метод резолюций полон: для того чтобы доказать, что из множества S1 логически следует F, S^ => Ft можно доказать формальную выводимость пустого дизъюнкта из множества 52 = /}. То есть, Si |- □ и наоборот, такая (формаль- ная) выводимость пустого дизъюнкта означает, что Г логически следует из множест- ва предложений Sj, и (т. к. исчисление предикатов полно) что S| |— Г в исчислении предикатов. Последнее соответствует формальному выводу F в теории первого по- рядка (прикладном исчислении предикатов), прикладными аксиомами которого является множество аксиом Sj. В Прологе используют метод резолюций для хорновских предложений (дизъюнктов), содержащих не более одного положительного (без отрицания) литерала: F v "I F\ v "I F^ v ... v 1 Fn что эквивалентно Fi & Fi & ... & Fn -> F и записывается на Прологе в виде: F:- Fb F2, ..., Fn Предложения такого вида называют в Прологе правилами. Если литералов с отрица- нием (отрицательных литералов) нет, то такие предложения называют фактами. Множество правил и фактов (конъюнкция соответствующих предложений) составля- ет программу, описание данной предметной области в виде множества аксиом при- кладного исчисления предикатов.
122 Часть I. Математические основы логического программирования Предложение, не содержащее положительных литералов, что эквивалентно 1 (Gi & Qi &... & а,) называется целью (goal) или запросом к программе и представляет собой отрицание теоремы, присоединяемой к множеству предложений (правил и фактов). Выполнение программы заключается в резолютивном выводе этой теоремы, в доказа- тельстве (выводе) последовательно Qb 02, •••, Qn- Причем предполагается, что все переменные в правилах замкнуты квантором всеобщности, а в запросе — квантором существования. При выполнении программы (в Прологе используется одна из стра- тегий метода резолюций, называемая линейной резолюцией) осуществляется унифика- ция (поиск по образцу), присущая резолютивному выводу, в процессе чего и нахо- дятся значения переменных, удовлетворяющие запросу. Выполнение программы по методу линейной резолюции осуществляется следующим образом. Берется Q\ и первый в программе дизъюнкт F v 1 F\ v 1 F} v ... v 1 Fm та- кой, что F и Q\ унифицируемы: существует НОУ (Наиболее Общий Унификатор) 9 такой, что /9 = Q]9, тогда по резолютивному правилу вывода Fvl fivl F2v ... vl Fm, 1 Qi (1 F,vl F2v ... vl F^Q получится новый запрос: (1 Fjvl F2v ••• vl Fmvl C2v ... v 1 Qn)G В этом новом запросе правило линейной резолюции будет применяться к (1 F[)Q и т. д. до тех пор, пока не будет получен пустой запрос (пустой дизъюнкт). Таким образом, с точки зрения логики, программу на Прологе можно рассматривать как формальную систему метода резолюций Т$, где S представляет собой множество хорновских предложений (дизъюнктов), а выполнение программы представляет со- бой процесс опровержения объединения множества А с отрицанием формулы запро- са 1 Q (вывод пустого дизъюнкта). Или, что то же самое, вывод запроса-формулы Q = 3x^*2 - 3xw (0! & 02 & ••• & Qn^ из множества S методом линейной резолюции. Утверждение 3.5 Пролог является универсальным языком программирования: любой алгоритм может быть записан в формализме метода резолюций с хорновскими дизъюнк- тами. Доказательство этого утверждения можно найти в [57]. В качестве примера исполь- зования правила линейной резолюции в языке Пролог рассмотрим следующий при- мер (пример 12 разд. "Язык логики предикатов” гл. 2, пример 2 разд. "Логическое следо- вание: правило резолюций” данной главы).
Гпава 3. Логический вывод в исчислении предикатов 123 Пример Имеется следующая задача на опровержение: {(1 А(х) v ОД), (1 Я(х) v ОД), ОД, (1 G(a) v 1 ОД)} => false В виде программы этот пример может быть записан следующим образом: 2(х):-R(x) . G(x):~R(x). ? (а) . GOAL: G(а), C(a). Первые два дизъюнкта записаны как правила на языке Пролог, третий — факт, целевое утверждение (запрос) — G(a) & С(д). Выполнение программы будет следующим. Берем отрицание первого, требующего доказательства, утверждения G(a) и первое правило, унифицируемое с ним: G;x) :-R(x) . Правило резолюции дает следующий результат: G(a), 1 Я(х) v G(x) |- 1 Я(д) при подстановке 0 = {д|х}. Новая цель будет иметь вид: GOAL: R(a)r С(а). Применяя резолюцию к отрицанию R(a) и факту R(a), получаем О — пустой дизъ- юнкт. Оставшийся запрос имеет вид: CGAL: С (а) . и унифицируется с правилом С(х) :-R(x). С(«), С(х) V 1 Л(х) |- R(a). Теперь запрос имеет вид: GOAL: R(a) - Отрицание 1 R(a) и факт R(a) дают пустой дизъюнкт □, и теперь запрос пуст, про- грамма выполнилась, значение х равно а. О Секвенциальные исчисления и обратный метод С. Ю. Маслова Неоднократно утверждалось, что логика в некотором смысле моделирует человече- ское мышление (например, название классического труда Буля было "Исследование закона мысли", а сформулированной целью — "собрать некоторые вероятные указа- ния относительно природы и устройства человеческого ума"). Вместе с тем несо- мненно, что аппараты логического типа сами по себе моделируют лишь механизм оформления конечных результатов мышления. Они моделируют не "как мозг откры-
124 Часть /, Математические основы логического программирования вает истину", а то, что он признает истиной. Постановка же центрального вопроса теории поиска вывода нацелена на выявление того, "как можно было бы открыть такую-то истину" [51]. Определения формальной системы, исчисления предикатов дают ответ на вопрос о том, что можно назвать процессом доказательства, но не дают ответа на вопрос, как его искать. Более того, долгое время считалось, что проблема поиска доказатель- ства психологическая, а не логическая [14]. Но именно этот вопрос является центральным для теории поиска вывода, методов автоматизации доказательства теорем [15]. Развитие теории поиска вывода, заклю- чающееся в выявлении структуры возможных выводов объекта по исчислению и объекту в языке этого исчисления, тесно связано с развитием вычислительной тех- ники и проблематикой искусственного интеллекта. Именно вычислительные маши- ны сделали возможным практическую реализацию методов и алгоритмов логическо- го вывода, а в исследованиях по искусственному интеллекту был поставлен вопрос о том, как осуществляется процесс решения задач человеком. Особенно важно, что дедуктивные системы можно применять не только как средст- во формальной фиксации результатов мыслительного процесса, но и как аппарат анализа самого этого неформального процесса. Может быть развита теория поиска вывода в исчислениях, которая приведет к открытию некоторых механизмов творче- ства, т. к. широкие классы творческих задач можно рассматривать как задачи по нахождению вывода в специально подобранных дедуктивных системах. Теория по- иска вывода при этом оказывается тесно связанной с вопросами психологии творче- ства, автоматизации научных исследований, с практикой построения систем искус- ственного интеллекта [51]. Обратный метод поиска вывода [43] (см. приложение 4) был предложен на год рань- ше метода резолюций [88] и независимо от него. Первоначально он также предна- значался для секвенциального (или генценовского) варианта классического исчис- ления предикатов и для стандартизованных формул метода резолюций. Оба метода примерно эквивалентны, что подтверждалось теоретическими оценками [45] и ма- шинными экспериментами [52]. Важнейшей особенностью обратного метода С. Ю. Маслова является то, что он применим к секвенциям произвольной структуры. Более того, этот метод может быть распространен на произвольные секвенциальные исчисления [46, 51]. Секвенциальное исчисление В классическом исчислении предикатов, называемом гильбертовским, имеются ми- нимальные (необходимые и достаточные) дедуктивные средства для порождения любой общезначимой формулы исчисления предикатов. Эти средства удобны для исследования самого исчисления, но совершенно не приспособлены для практиче- ского вывода общезначимых формул (см. утверждение 1.8 гл. 1 — довольно сложное доказательство тривиального факта, А -> А). Формализм, более приспособленный для получения такого вывода, называется ис- числением Генцена, или исчислением естественного вывода, или секвенциальным вари- антом классического исчисления предикатов.
Гпава 3. Логический вывод в исчислении предикатов 125 Будем обозначать с помощью Г, Д — множества (возможно, пустые) формул (исчис- ления высказываний, исчисления предикатов), для знака секвенции будем исполь- зовать знак ^=. Секвенцией называется правило вида Г |= Д, левая часть правила — антецедент^ пра- вая — сукцедент. Смысл правила заключается в следующем: если выполнены (ис- тинны) все формулы из Г, то выполнена хотя бы одна формула из Д. Если Г ~ (т41, ^2, Лл}, Д = (Bj, В2, Вт} — множества формул исчисления предикатов (исчисления высказываний), то секвенции Г (== Д соответствует следующий логиче- ский смысл: А\ & Л2 & ... & Ап => В] v В2 ... v Вт т. е. из множества формул Г логически следует дизъюнкция формул из Д, а значит общезначимой является формула 1 Ai v 1 Л2 v ... v 1 Ап v В[ v В2 v ... v Вт если формулы Л/, Д — атомарные, то секвенция означает истинность соответствую- щего дизъюнкта в логике предикатов. Вместо знака конъюнкции используют запятую, вместо дизъюнкции — точку с запя- той, секвенция принимает следующий вид: Л1, Л2, ..., Ап f= В]; В2; •••> Вт Из смысла секвенции следует, что порядок записи формул в левой и правой частях секвенции не меняет их значения. Секвенциальное исчисление высказываний В секвенциальном исчислении высказываний к символам исчисления высказываний добавляются два символа: знак секвенции и точка с запятой. Формулы совпадают с формулами рассмотренного нами исчисления высказываний. В качестве схемы аксиом берется одна-единстве иная схема: Г, А |= Д; А где Г и Д — множества формул, А — любая формула исчисления высказываний (т. е. Г & А => AvA, что является тривиально истинным логическим выводом). Правила вывода генценовского исчисления высказываний в редакции Кангера [57]: 1. Правила введения отрицания: • слева от знака секвенции, обозначение правила (1 к=): г ,1 А |= Д
126 Часть I. Математические основы логического программирования • справа от знака секвенции, обозначение правила (f="l): Г, л|= д Г |= д;1 А 2. Правила введения конъюнкции: • слева от знака секвенции, обозначение правила (&|=): Г, А, В |= Д Г, А & В |= Д • справа от знака секвенции, обозначение правила (р&): Г |= Д; А Г |= Д; В Г |= Д; Л & В 3. Правила введения дизъюнкции: • слева от знака секвенции, обозначение правила (v[=): Г, А |= Д Г, В |= Д Г, A v В |= Д • справа от знака секвенции, обозначение правила (pv): Г |= Д; Л; В Г |= Д; A v В 4. Правила введения импликации: • слева от знака секвенции, обозначение правила Г, Д |= Д Г |= Д; Л Г, Л -> В |= Д • справа от знака секвенции, обозначение правила (р->): Г, А |= Д; В Г |= Д; Л -> В Каждое из этих правил является обратимым истинным утверждением в исчислении высказываний: т. е. если справедлива (справедливы) секвенция (секвенции), стоя-
Гпава 3, Логический вывод в исчислении предикатов 127 щая (стоящие) в числителе правила, то справедлива секвенция, стоящая в знамена- теле, и наоборот. Докажем, например, истинность первой секвенции правила импликации. Утверждение 3.6 Г, В А и Г А; А тогда и только тогда, когда Г, А -* В А. G Доказательство Числитель секвенции, Г, В |= Д и Г |= А; А означает, что ((Г & В) -> А) & (Г -> (Avt4)) — общезначимая формула. Преобразуем с помощью эквивалентных преобразований: ((Г & 5) -> Д) & (Г -> (ДуЛ)) (1 Tvl ^Д) & (1 ГуДуЛ) (1 ГуДМЛ & 1 5) Знаменатель секвенции, Г, А —> В рД означает, что (Г & (А —> В)) => Д или что (Г & (А -> А общезначима. (Г & (А -> В)) -> А <=> 1 Fv(l А & 1В)vA, т. е. эквивалентна той же самой формуле, что и числитель и, следовательно, общезначимость одной из формул означает общезначимость другой, т. е. справедливость одной из секвенций означает спра- ведливость другой. Другой способ доказательства, синтаксический, на основании утверждений (ме- татеорем) о выводе в исчислении предикатов: Г, 5^=А и Г^=А; А соответствует в классическом исчислении: Г, В р А и Г |— А; А или, по теореме дедукции, Г |— 5 ч А и Г |— Awl или Г |-(1 2?vA) & (Av/4) или Г |— ((1 В & A)v&), что то же самое, что г Ни -> 5)-»А или Г, (А 5)|- А, что соответствует Г, (А -> В) ^=А. Справедливость и обратимость остальных правил секвенциального исчисления вы- сказываний можно показать аналогично. Определение доказательства и выводимой (доказуемой) формулы в секвенциальном исчислении (высказываний и предикатов) совпадают с соответствующими определе- ниями в классическом исчислении. Определение 3.7 Доказательством (выводом) в секвенциальном исчислении называется конечная последовательность секвенций, каждая из которых либо аксиома, либо получена из предыдущих секвенций этой последовательности по одному из правил вывода. Секвенция называется выводимой (теоремой) секвенциального исчисления, если существует вывод, в котором она является последней. G
128 Часть I. Математические основы логического программирования Докажем, для примера, что U-+U является теоремой в секвенциальном исчислении высказываний (аналогично утверждению 1.8, гл. 1 для классического исчисления высказываний). Для этого надо доказать секвенцию £= U-+U По правилу введения импликации (£=->), читаемому снизу вверх (считая, что мно- жества формул Г и А пусты), получим U U, т. е. аксиому. Теперь переходя от ак- сиомы к следствию из нее, читая правило сверху вниз, получим доказательство утверждения: t/|= U £= и-+и В данном случае само доказательство элементарно (сравните со сложностью того же самого утверждения 1.8 гл. 1 в исчислении высказываний), но схема доказательства всегда одна и та же: сначала, используя правила вывода снизу вверх, осуществляется поиск доказательства, правила применяются до тех пор, пока "листьями" дерева по- иска не станут аксиомы. Затем, применяя правила вывода сверху вниз, "читая" дере- во поиска сверху вниз, т. е. от аксиом к исходной секвенции, получаем само доказа- тельство. В качестве примера рассмотрим доказательство следующей общезначимой формулы (см. разд. "Исчисление высказываний как формальная теория" гл. /, аксиома исчисления высказываний 1$ первой схемы аксиом)'. (Л-> Q-*((£-* 0->((Л vB)-+ Q) Дерево поиска вывода получается, когда правила, обозначения которых приведены справа, применяются к исходной секвенции, записанной в корне дерева. Листьями дерева будут аксиомы, которыми заканчивается поиск вывода (вверху): (А-+С), (В-+С), J Н С (v 1=) (7, В~уС^ А С В~>С, А С\ А (А-+С), (В-+С), (AvB) |= С (А-+С), (5->О|= ((AvB)~+C) (А-+С) |= ((B^Q-+((AvB)-+C)) |= ((B^C)-+((AvB)-+C)) (k ~>) (h (k —>) Вывод, с помощью правил, применяемых сверху вниз, можно "прочитать" следую- щим образом:
Гпава 3. Логический вывод в исчислении предикатов 129 С, В \= С; А в\=С;А;В В -> С, В |= С; А (->|=) 5->С, В |= С; А С, В->С, в\=- С (А-ьС), (В-,С), В |= С С, В->С А |= С В->С, А |= с- А (->|=) (-ф) (Л-yQ, (5->Q, A С (Л->0, А |= С (А-+С), (В-Л), 5 |= С (А-Л), (В^С), (Av В) |= С ------------------г--------------- (h->) (А-Л), (ВЛ) ((AvB)-^C) ---------г------------------------ (l=->) (АЛ) f= ((5->С)->((Лу5)->С)) Дерево поиска вывода и вывод представлены на рис. 3.1 (аксиомы заключены з прямоугольники). Докажем в качестве примера логическое следствие, записанное в виде секвенции см. разд. 'Логическое следствие и логический вывод" гл. 1): (А-ЛВ & Q), (В-Л)), (С-Л), (1А-Л) |= (D & Е); F Будем применять правила вывода снизу вверх, процесс поиска дерева вывода будем записывать в порядке применения соответствующих секвенций, от корня дерева вы- вода доказываемой секвенции к листьям — аксиомам. □ По правилу (->!=), применяемому к исходной секвенции получим: (Л->(5 & Q), (В-Л)), (С-+Е), F |= (О & £); F — аксиома; (А->(В & Q), (5->0), (С->£) (D & Е); F; 1 А. 3 По правилу (|="1): Д (А->(В & О), (В-Л)), (С->Е) |= (D&E); F. f 3 По правилу (—>|=): A, (B-^-D), (С-^-Е) [= (D & Е); F; А — аксиома; А, В, С, (В-Л), (СЛЕ) |= (D & Е)-, F. 5 Зак. 963
130 Часть /. Математические основы логического программирования (А-+С), (5->С), (Jv5) |= С (А->С), (В->С)\= (C4v.B)->Q ([=->) (Л->0 |= ((В->С) -> ((^->0) (к=->) |= (^->Q->((B->Q -» ((A/B)->Q) Рис. 3.1. Дерево вывода секвенции р (А—>С)—>((В—>С)~>((AvB)—>С)) □ По правилу (->р): А, В, С, (С->Е) (D & Е); F; В — аксиома; А, В, С, D, (С-+Е) [= (D & Е); F. □ По правилу (^= &): и А, В, С, D, (С-+Е) D; F— аксиома; А, В, С, D, (С-+Е) |= £; F. □ По правилу А, В, С, Z), Е |= £; F — аксиома; А. В, С, D\=- Е; Е, С ~ аксиома.
Глава 3. Логический вывод в исчислении предикатов 131 Видим, что все листья дерева поиска вывода (расположенного корнем, т. е. исходной секвенцией, вверх) оказались аксиомами, т. е. данная формула выводима в секвен- циальном исчислении высказываний. ’Прочитав" дерево поиска вывода от аксиом к корню, будет построен вывод этой формулы. Для правила вывода исчисления высказываний (правила заключения) соответствующая ему секвенция имеет вид: Дерево поиска вывода и дерево вывода для этой секвенции получаются с помощью единственного правила вывода (->^=): Множества формул, выводимых в обоих вариантах исчислений высказывания, сов- падают. Секвенциальное исчисление предикатов (исчисление Генцена) Определение правильно построенного выражения (формулы) секвенциального ис- числения предикатов совпадает с определением классического исчисления. К сим- волам добавляются те же символы, что и в исчислении высказываний (знак секвен- ции и точка с запятой). Схема аксиом исчисления (единственная) та же, что и в секвенциальном исчисле- нии высказываний: Г, А А; А. Правила вывода включают в себя те же самые правила вывода, что и в секвенциаль- ном исчислении высказываний, но к ним добавляются правила вывода, связанные с необходимостью учета кванторов: Г |= А; А(у) Г |=Д; УхА(х) (h'7') Г, А(у) А Г, 3x40) |= Д где переменная у не входит в нижнюю секвенцию свободно; Г |= А; Л(/); ЭхЛ(х) Г А; Зх4(х) (|=3) Г, A(t), VxA(x) |= Д Г, Vx4(x) |= А (vh где переменная х не входит свободно в верхнюю секвенцию.
132 Часть /. Математические основы логического программирования В качестве примера докажем формулу (секвенцию): (\fxF^x)v\fxG(x))^yx(F(x)vG(x)) Дерево поиска вывода (рис. 3.2) получается при использовании правил вывода снизу вверх (от знаменателя к числителю), начиная с исходной секвенции, которая распо- лагается в корне дерева. Вывод секвенции получается при использовании правил вывода сверху вниз (от чис- лителя к знаменателю), его запись идет от листьев дерева вывода — аксиом, к корню дерева. Ду) |= Ду); «(у) G(y) |= Ду); G(y) tfrfW |= /i>)v(7O) VxG(x) |= F(y)vG(y) Ух Их) [= У х( F(x)x Gfx}} УхС(х) pVx( F{x)\'G(x)) У xF(x)vy xG(x) |= У x( F(x)x G(x\) [= (УхР[х)\/УхС(х))^Ух(Р(х)\/С(х)) Рис. 3.2. Дерево поиска вывода и вывод для р (VxF(x)vVxG(x))-»Vx(F(x)vG(x)) Обратный метод С. Ю. Маслова С этим методом и идеями, заложенными в его основу, можно связывать надежды на универсализацию методов автоматического доказательства теорем с целью их рас- пространения на произвольные, а не только логические исчисления [14, 26, 51]. Возможно, что и будущее логического программирования, понимаемого в широком смысле, будет связано с этим методом (и его модификациями), идеи и возможности которого еще ждут своих ученых-исследователей и практиков-программистов. К сожалению, в отличие от метода резолюций Дж. Робинсона, получившего широ- кую известность благодаря его использованию в языке Пролог, метод С. Ю. Мас- лова известен довольно узкому кругу специалистов. Полное представление об обратном методе С. Ю. Маслова можно получить по его оригинальным работам, по работам его коллег, учеников и последователей [15, 26, 43, 44, 46, 49—52, 61]. В приложении 4 приведено описание этого метода, подготов- ленное доктором физ.-мат. наук В. П. Оревковым специально для этой книги.
Гпава 3. Логический вывод в исчислении предикатов 133 Для широкого круга неспециалистов в области математической логики доступна, пожалуй, лишь работа С. Л. Катречко [26]. Следуя ей, поясним лишь общую идею метода на примере, уже использовавшемся в разд. '"Логическое следование: правило резолюций " данной главы. Пример Всякий, кто находится в здравом уме, может понимать математику. Ни один из сы- новей Гегеля не может понимать математику. Сумасшедшие не допускаются к голосо- ванию. Следовательно, никто из сыновей Гегеля не допускается к голосованию. Проверка правильности данного рассуждения была сведена там к доказательству следующего логического следствия: (1 Z(x)vM*)), (1 М(х)), (Z(x)vMx))| G(x)vN(x) Представим формулу, общезначимость которой будем доказывать в дизъюнктивной нормальной форме, в виде дизъюнкции конъюнкций, что получается из секвенции 1 ZvM 1 Gv"] М, Z'jN (= 1 G- N : помощью трех применений правила ("] f=): |= Z& 1 М- G&M\ 1 Z& 1 У; 1 G- N Для доказательства секвенции, представленной в таком виде, достаточно использо- вать лишь одно правило вывода (Н&) и аксиому |= Г; 41 А. Дерево поиска вывода и вывод (рис. 3.3) для данной формулы: |= Z; М; 1Z; G; N |= 1Z; М; 1V; 1б7; N (И &) |= Z; м-, 1Z& 1G; N |= 1Л/; М- 1Z& 1М 1G; N (И &) |= Z& Ъ/; Af; 1Z& Tv; 1G; N |= Z& W; G;1Z& 1G; N (= Z& ~\M\ G & M; lz& X 1G; Рис. 3.3. Дерево поиска вывода и вывод для t Z & ~\М; G & М, lz & 1/V; 1G; N
134 Часть I. Математические основы логического программирования Каждый литерал, входящий в формулу F = Z& 1 MvG & Л/vl Z& 1 Л\/| GvN закодируем с помощью двух чисел: номера конъюнкта (подформулы) в формуле и номера литерала в подформуле: F— 1 |12v2]22v3j32v40v50 Знак конъюнкции, как это часто делают, опущен: Z в первой конъюнкции (подфор- муле) закодирована как 1(, 1 М как 12, G как 2b 1 Z в третьей конъюнкции как 3b 1 N как 32, литералы конъюнктов, содержащих единственную литеру, кодируются с индексом 0, 1 G как 4q и N как 50. Составим исчисление Ср (благоприятных наборов), соответствующее данной фор- муле Е Множества кодов будем называть наборами. Аксиомами этого исчисления или благо- приятными наборами будем называть наборы, содержащие пару кодов для контрар- ных литералов (со знаком отрицания и без него) из различных конъюнктов, напри- мер: [11 3J, т. е. код для Z в первом конъюнкте и код для 1 Z в третьем. Аксиомами Ср будут следующие благоприятные наборы: Hi 31], [12 22], [2! 40], [32 50]. Правило вывода благоприятных наборов в этом исчислении следующее. Если множество благоприятных наборов содержит все коды какого-либо конъюнкта (подформулы), то из него получается (выводится) благоприятный набор, состоящий из исходных кодов, за исключением кодов этого конъюнкта. Например, [2j 4q] h [2i], т. к. 4о содержит все коды подформулы 4 (состоящей из одного литерала); [32 50] h [32]; [11 3d, [12 22] h [3i 22], т. к. список наборов содержит все коды подформулы 1 (li и 12). Понятие вывода в этом исчислении стандартное, набор считается выведенным, если он является последним в процессе вывода. Теорема о полноте Формула F выводима в секвенциальном исчислении С тогда и только тогда, когда в исчислении благоприятных наборов Ср может быть выведен пустой благоприятный набор. Доказательство можно найти в [26]. Построим такой вывод (рис. 3.4) для данной формулы F в соответствующем ей ис- числении Cf [11 3(], [325о] F [11 50] Hi 50] h [li]
Гпава 3. Логический вывод в исчислении предикатов 135 |= Z; М; 1Z& 1ЛГ; 1G; 7V (= 1Л/; М; 1Z& ~\N; 1G; 7V (1) N |= Z& ~]М; М\ 1Z& X 1G; |= Z& ~\М; G; 1Z& 17V; 1<7; N F— 1] 12v2]22v3i32v4ov5o Рис. 3.4. Вывод формулы Рв исходном исчислении С и исчислении благоприятных наборов Ся Пояснение: из двух благоприятных наборов, аксиом исчисления, по правилу вывода удален полный список кодов для литер подформулы 3 (3] и 32), результат — остав- шиеся коды, — набор [Ц 50], т. к. подформула 5 состоит из одной литеры, то ее код 5о можно удалить, в результате получен (выведен) набор [Щ: H1L th 22] |- [22] Пояснение: полученный (выведенный) на первом шаге набор и аксиома имеют пол- ный набор кодов подформулы 1 (И и 12), поэтому их можно удалить, результат — набор [22]: i22], [2i 4q] |- [40] 4q] |- [ ] Пояснение: набор, выведенный ранее [22], и аксиома [2i40] имеют полный набор кодов подформулы 2, а [4о] — полный набор кодов подформулы 4, следовательно, эн может быть удален, результат — пустой список соответствует окончанию процес- са вывода в исчислении Ср Исчисление Ср эквивалентно (в смысле выводимости) исходному и позволяет коди- ровать выводы целого класса формул определенной структуры. Благодаря анализу информации о структуре формулы и учету особенностей построения секвенциаль-
136 Часть I, Математические основы логического программирования ных выводов, удалось частично избавиться от слепого перебора, характерного для секвенциальных исчислений. Построение выводов стало более осмысленным. Обратный метод может быть распространен на произвольные секвенциальные ис- числения [46, 50]. Он оказался удобным аппаратом для построения разрешимых классов, на его основе удалось получить обобщения известных результатов, найти ряд новых разрешимых классов, объединить единой схемой почти все имеющиеся результаты по разрешимым фрагментам классического исчисления предикатов [46]. Свое название метод получил исходя из того, что он как бы "перевернул" дерево поиска вывода с направления снизу вверх в направлении сверху вниз. ( Замечания • Не следует путать обратный метод С. Ю. Маслова с обратным методом, о кото- ром говорится, например, в [68]. В книгах и статьях по методам искусственного интеллекта, когда говорится о поиске решений задач в пространстве состояний, рассматриваются прямой и обратный методы (методы прямой, обратной и встречной волны). В зависимости от того, как решается задача: путем синтеза простых задач в более сложные (пока не будет решена поставленная задача — прямой метод); либо путем декомпозиции исходной задачи на более простые (пока не будут получены задачи, решение которых уже известно, — обратный метод); либо совмещением: от простых задач к более сложным и от задачи к подзадачам. Под обратным методом поиска вывода в [68] подразумевается про- цедура поиска вывода, идущая от целевой формулы к аксиомам, подобно поиску вывода в секвенциальном исчислении. • В методе резолюций, аналогично обратному методу, по данной формуле строит- ся соответствующее ей исчисление, эквивалентное, в смысле вывода данной формулы, исчислению предикатов.
ЧАСТЬ 11 Решение задач — специфическое достижение разу- ма, разум же — особый дар, которым наделен че- ловек. Способность к преодолению препятствий, к нахождению обходного маневра там, где не видно прямого пути, возвышает умное животное над ту- пым, человека — над самым умным животным и талантливых людей — над другими людьми. Д Пойа [65] На сегодня мы еще очень далеки от осуществления анализа и описания высших форм человеческой деятельности, мы даже не научились в объективных терминах давать определения многих встречающих- ся здесь категорий и понятий. А. Н. Колмогоров [31] Логическое программирование и искусственный интеллект Глава 4. Искусственный интеллект Глава 5. Экспертные системы
ГЛАВА 4 Искусственный интеллект Впервые после фундаментального пересмотра картины мира, связанного с именами Коперника и Дарвина, разра- ботка методов искусственного интеллекта возвращает нас к вопросу о месте человека в природе. По существу, впер- вые оспаривается исключительность разума. Всякая задача, для которой неизвестен алгоритм решения, априори относится к искусственному интеллекту. Ж. -Л. Лорьер [39] Для чего же нужна вся эта математика? Действительно, как говаривал Гамлет, принц датский, "...суха теория, мой друг, а древо жизни зеленеет". Любая теория интересна "широкой общественности" ровно настолько, насколько она полезна. Впрочем, вряд ли каждый человек, слушающий музыку, вспоминает о квантовой теории, с которой начинались современные компакт-диски. Умеет ли машина мыслить, нужны ли исследования по искусственному интеллекту (ИИ) — эти вопросы, популярные еще не так давно, окончательно ушли в прошлое, раз в магазинах бытовой техники стали появляться бытовые приборы со значком AI, Artificial Intelligence (Искусственный интеллект). По сообщениям прессы, по данным Internet известно, что в области ИИ работают и военные ведомства, и ведущие западные фирмы — AT&T, Intel, General Electric, Sharp, Hitachi, Siemens и др., причем "перспективы повышения конкурентоспособ- ности на базе ИИ столь велики, что их перестали обсуждать публично" [83]. Язык Пролог родился в 1971 году в Марселе на основе системы, разработанной Алэном Колмероэ и называвшейся systemes-q [32]. Эта система возникла из потреб- ности лингвистов в обработке символьной информации. Вскоре после доклада, сде- ланного уже Робертом Ковальски, осознавшим значение этой системы в более ши- роком смысле, чем только перевод текстов, на конгрессе IFIP в 1974 году появилось новое направление, названное логическим программированием. Первым реальным воплощением этого направления и стал новый язык программирования [74]. Разви- ваясь благодаря усилиям специалистов, для которых логическое программирование стало главным их делом, из экспериментальной системы Пролог превратился в один из наиболее признанных во всем мире языков и систем для решения задач ИИ. Пережив эйфорию первых десятилетий, Пролог стал, несмотря на отличную от тра- диционных алгоритмических языков логическую основу, универсальным языком
140 Часть II. Логическое программирование и искусственный интеллект программирования (в особенности это относится к Turbo Prolog, а теперь к Visual Prolog). Хотя по-прежнему область задач, относящихся к ИИ, является главной об- ластью применения логического программирования в целом и языка Пролог в част- ности. В логическом программировании можно выделить следующие важнейшие направ- ления: □ решение задач, требующих логического вывода, эвристическое программирование и создание решателей задач [11, 19, 22, 71]; □ представление знаний и разработка экспертных систем [17, 21, 23, 39, 40, 69]; □ создание новых языков логического программирования, в т. ч. совершенствова- ние языка Пролог [23, 79, 84]; □ создание вычислительных систем новых поколений [6, 84]. Что такое искусственный интеллект Артур Тьюринг, которого многие ученые считают основоположником направления искусственный интеллект [80], в процессе доказательства неразрешимости формаль- ной арифметики (второй проблемы Гильберта) создал, как сказали бы сегодня, вир- туальный компьютер — понятие о вычислительной машине, ставшее одним из ма- тематических определений алгоритма. В 1950 году в английском журнале "Mind", в статье под названием "Computing Machi- nery and Intelligence" (в русском переводе статья вышла под названием "Может ли машина мыслить?"), Артур Тьюринг предложил критерий, позволяющий определить, обладает ли машина (программа) мыслительными способностями. Этот эксперимент получил название теста Тьюринга и заключается в следующем. Испытуемые, человек и машина, при помощи записок (предполагалось использовать телеграф) ведут диалог, а третий человек — судья, находясь в другом месте, должен определить по запискам, кому они принадлежат, человеку или машине. Если ему это не удастся, то это будет означать, что машина успешно прошла тест. Пройдут ли компьютерные системы тест Тьюринга к 2029 году? Об этом (на 10 тыс. долларов) поспорили Рэй Курцвайль, изобретатель и основатель фирмы, произво- дящей музыкальные инструменты, он поставил "за", и Митчелл Кейпор, основатель компании Lotus, он поставил "против" (http://www.wired.com/wired/). Под ИИ понимают не робота, наделенного способностями человеческого разума и прошедшего тест Тьюринга: в английской литературе словосочетание artificial intelligence звучит не столь вызывающе, как по-русски, и может переводиться как искусственная разумность. ИИ — область информатики, предметом которой является разработка компьютер- ных систем, обладающих возможностями, традиционно связываемыми со способно- стями естественного интеллекта. Поиски ответа на вопрос о том, что такое естественный интеллект, привели к ряду замечательных открытий в различных областях знаний от математики [8, 48, 51, 72, 75] до медицины [7].
~лава 4, Искусственный интеллект 141 В 1962 году Розенблатом [75] были предложены модели мозга, названные персептро- нами, представляющие собой различного вида сети из искусственных нейронов, з основе которых лежали модели Маккалока-Питгса, разработанные в 1943 году. Изучение персептронов вначале было связано с задачей распознавания образов (ис- кусственного зрения) и привело к ряду математических результатов в области вы- числительной геометрии [56]. Однако в то время практическая реализация этих ме- тодов оказалась невозможной из-за несовершенства вычислительной техники. Зато сегодня в области теории и практики нейронных сетей, используемых в самых раз- личных областях робототехники и информационных технологий, наблюдается настоящий бум. Еще один подход — моделирование с помощью вычислительной машины процесса эволюции — был предложен в 1966 году. Идея моделирования — исследование му- таций и избирательного выживания в течение двух миллиардов лет, приведших к созданию разума Homo Sapiens. По мнению Н. Нильсона [58] "...хотя такой подход л дает возможность свести несколько первых миллионов лет эволюции к несколь- ким дням моделирования на ЭВМ, создается впечатление, что важные средняя и лоздняя стадии эволюции связаны со столь сложными, но еще не являющимися □азумными структурами, что их эволюция уже не может быть ускорена путем моде- лирования на вычислительной машине". Кто знает, возможно, в настоящее время или в недалеком будущем такой метод и приведет к практически важным результа- там. В этой связи интересно направление, основанное на идее логического про- граммирования — мутационные исчисления [47]. Другим способом понимания того, что собой представляет естественный интеллект, чвляется изучение разумного поведения животных и, в особенности, человека. Это направление можно, впрочем, весьма условно (т. к. они пересекаются и взаимно дополняют друг друга) разделить на два. ;. Разработка эвристических методов и решателей задач, т. е. методов, моделирую- щих деятельность человека в проблемной ситуации, устройств и программных средств, реализующих эти методы [22, 66, 68]. Хорошими моделями изучения поведения человека в проблемной ситуации являются игры, в особенности шах- маты, решение математических задач, в частности доказательство теорем [51, 58, 65, 72]. 2. Разработка методов и средств для создания экспертных систем, т. е. систем, основанных на знаниях человека — эксперта, выполняющего определенную про- фессиональную деятельность [17, 21]. Замечание Термин artificial intelligence был впервые применен в 1956 году на семинаре в одном из американских высших учебных заведений (по одним данным в Стенфордском университете, по другим в Дартсутском колледже). В Советском Союзе начало развития этого направления связывают с семинаром профессора А. А. Ляпунова в МГУ — "Мышление и автоматы", который начал работу в 1954 году. К специфическим особенностям человека (и не только человека), отличающим его от машины, обычно относят: способность распознавать сложные зрительные и слу- ховые образы, понимать (естественный) язык, способность обучаться, рассуждать и
142 Часть II. Логическое программирование и искусственный интеллект делать логические выводы. Многое из перечисленного в той или иной степени уже стало "по плечу" и компьютеру, благодаря современным вычислительным системам и их программным средствам. За пятьдесят с лишним лет после того, как Тьюринг предложил свой тест, произошли фантастические перемены. Можем ли мы предпо- ложить, какими станут еще через пять десятков лет. компьютеры будущего, какие будут у них возможности и смогут ли они пройти тест Тьюринга? Информация к размышлению Группе ученых из Вейцмановского института (Weizmann Institute) в Израиле удалось создать первый в мире компьютер, все обрабатываемые данные и компоненты ко- торого, включая "железо”, программы и систему ввода/вывода, умещаются в одной стеклянной пробирке. Вместо традиционных кремниевых чипов и металлических проводников новый компьютер состоит из набора биомолекул — ДНК, РНК и некото- рых ферментов. При этом ферменты (или, по-другому, энзимы) выступают в роли "железа”, а программы и данные зашифрованы парами молекул, формирующих цепочки ДНК. Биокомпьютер может решать лишь самые простые задачи, выдавая всего два типа ответов: "истина" или "ложь". В одной пробирке помещается одно- временно до триллиона элементарных вычислительных модулей, которые могут выполнять до миллиарда операций в секунду. Для проведения вычислений необхо- димо смешать в пробирке вещества, соответствующие "железу", "программному обеспечению" и "исходным данным", в результате чего образуется молекула, со- держащая в зашифрованном виде результат вычислений. Логико-лингвистические модели в системах управления В Советском Союзе наука со столь вызывающим названием официальное признание получила в 1974 году, когда был создан при президиуме АН СССР Научный совет по проблеме "Искусственный интеллект". Первые положительные результаты были получены в области теории управления — одной из наиболее хорошо развитых об- ластей прикладной математики. В конце 70-х—начале 80-х годов прошлого века бы- ло осознано, что в этой области имеется ряд задач, для решения которых традици- онные методы не пригодны. Так возникли логико-лингвистические модели в системах управления [68]. Необходимость создания таких моделей объяснялась тем, что в сфе- ру автоматизации оказались вовлеченными объекты столь сложной структуры, что традиционные методы теории управления для них оказались либо мало эффективны, либо непригодны: О не все цели управления объектом могут быть выражены в виде количественных соотношений; О между рядом параметров, оказывающих влияние на процесс управления, не уда- ется установить точных количественных зависимостей; □ в многошаговых процессах управления содержание каждого шага не может быть заранее однозначно определено; О существующие способы описания объектов и протекающих в них процессов при- водят к столь громоздким конструкциям, что их практическое использование не представляется возможным.
Гпава 4. Искусственный интеллект 143 Если под объектом управления понимать экономические или социальные объекты, то к этому добавляются еще три причины: 3 цель существования самого объекта не может быть строго формализована; 3 в результате эволюции меняется структура и функции самого объекта, что долж- но отражаться и на эволюции процесса управления; □ элементы, входящие в структуру управляемого объекта, могут иметь активную природу: их поведение может противоречить целям управления. В результате для построения моделей таких объектов управления приходится отка- зываться от классических методов теории управления и переходить к подобным мо- делям, в которых решающее значение имеют тексты на естественном языке, т. е. к логико-лингвистическим моделям. К истории вопроса Управление самолетом, безусловно, одна из наиболее важных областей автомати- зации в системах управления. В майском номере журнала "Wired" за 2002 год опубликовано несколько публичных пари, заключенных между достаточно известными деятелями ИТ-рынка. Предмета- ми споров служат предсказания на самые разные темы. Например, высказано пред- положение, что коммерческие авиакомпании к 2030 году перейдут на беспилотные самолеты. Ставка — тысяча долларов. Крэйг Манди, технологический директор кор- порации Microsoft, ставит на то, что так оно и будет. Он считает, что компьютерные системы будут настолько совершенны к тому времени, что даже диспетчерские службы будут полностью автоматизированы. Против этого ставит исполнительный директор компании Google Эрик Шмидт. По его мнению, полностью беспилотные транспортные самолеты станут реальностью го- раздо позже, а может быть, и не станут вообще. В любом случае, невозможно обой- тись без, как минимум, одного пилота, который бы наблюдал за работой автомата, и в случае чего, перехватил бы у него управление. В связи с этим вспоминается, не имеющий аналогов в истории освоения космоса, единственный беспилотный "рейс" нашего космического корабля многоразового ис- пользования "Буран". Он стартовал 15 ноября 1989 года с самолета, поднявшего его в стратосферу. Отделившись от самолета, корабль поднялся в космос, сделал два витка вокруг Земли и приземлился на обычном аэродроме. Как рассказывают оче- видцы события, все происходило исключительно "штатно" до того момента, когда "Буран", сопровождаемый истребителями, при заходе на посадку стал делать не- ожиданный вираж, отклоняясь от нужной траектории посадки. Волнение оказалось напрасным, т. к. он вскоре выправился и сел так, как если бы им управлял опытный пилот. Когда стали анализировать причину неожиданного виража, то оказалось, что в систему управления было заложено правило захода на посадку в случае порывов бокового ветра. Как выяснилось, именно такой порыв ветра и имел место, и, если бы не соответствующая "поправка", приземление оказалось бы неудачным. В 1987 году в одном из американских журналов было приведено описание эксперт- ной системы, предназначенной для помощи пилоту во время посадки бомбарди- ровщика. Необходимость наличия на борту консультационной системы, помогающей пилоту при посадке, и системы (аварийной) автоматической посадки там обосновы- валась большим риском, связанным с посадкой самолета, на борту которого нахо- дится ядерное оружие, а также возможностью ситуаций, когда экипаж не в состоя- нии справиться с этой задачей самостоятельно.
144 Часть II. Логическое программирование и искусственный интеллект Под логико-лингвистической моделью управления понимается такая модель управ- ления сложным объектом, в которой используется семантическая (смысловая, каче- ственная) информация. Многие задачи управления сложными объектами без труда решаются человеком, но в силу различных обстоятельств (опасность для человека выполнения тех или иных функций, недостаточные надежность или быстродействие их выполнения) требуют автоматизации. В таких случаях опыт человека-специалиста (человека-оператора) может быть выражен в виде текста на естественном языке. Этот опыт и требуется использовать в системе искусственного интеллекта. Зачастую этот опыт не может быть представлен в виде алгоритма деятельности, который мог бы быть записан в память машины. Состояние объекта характеризуется столь боль- шим числом параметров и может зависеть от столь большого количества ситуаций, что невозможно заранее определить содержание каждого шага управления. Именно в этих случаях вместо алгоритма, предписывающего на каждом шаге его реализации некоторое однозначное решение, можно использовать совокупность указаний, пред- ставленных в виде некоторого исчисления. Языком этого исчисления выбирается язык, называемый языком представления зна- ний, в качестве аксиом исчисления служит описание объекта управления, среды, начальных состояний. В качестве правил вывода служат правила перехода из одного состояния объекта управления и среды в другое, в качестве теорем — промежуточ- ные и конечные состояния системы (рис. 4.1). Рис. 4.1. Общая структура логико-лингвистической (семиотической) системы управления Если в качестве объекта управления рассматривать некоего робота, осуществляюще- го определенную деятельность — перемещение, выполнение операций, то под сре- дой можно понимать всевозможные условия, в которых осуществляется функциони- рование. Модель знаний М содержит описание объекта управления Mq и описание среды объ- екта управления Л/у При этом существуют несколько уровней этого знания: от фун-
Гпава 4. Искусственный интеллект 145 даментальных (неизменных) знаний до знаний, которые зависят от данной конкрет- ной ситуации. Механизм порождения решений F анализирует состояние (ситуацию), в которой находятся объект управления и среда, и вырабатывает некоторое решение. Существенным отличием семиотической (знаковой или логико-лингвистической) модели системы управления от традиционной являются два момента. Во-первых, наличие модели знаний. Модель знаний отделена от механизма порож- дения решений, следствием чего является существенное упрощение описания систе- мы управления и ее функционирования. Такой способ представления знаний в об- ласти интеллектуальных систем носит название декларативного представления знаний в отличие от процедурного способа представления знаний в виде алгоритмов управ- ления. Удобство разделения блоков F и М связывают также и с тем, что заменить инфор- мацию в М гораздо легче, чем написать новые процедуры для блока F. Во-вторых, наличие интерпретатора /. Это блок, отражающий изменения блока М, содержимое которого меняется в процессе функционирования объекта управления: обновляется, уточняется и пополняется. Первым случаем практического использования логико-лингвистических моделей в управлении было ситуационное управление [29, 67, 68], с помощью которого была автоматизирована в 1967 году диспетчерская служба на шлюзованных участках вод- ных путей [68]. В основу ситуационного управления были заложены следующие принципы. Управ- ление системой рассматривается как процесс, включающий в себя использование семиотической (знаковой) модели функционирования. Эта модель содержит описа- ние системы, связи между элементами системы и внешней средой, правила функ- ционирования системы, на основании которых можно предвидеть последствия при- нятия тех или иных решений и выбирать наилучшие. Такой подход используется в тех многочисленных случаях, когда из-за сложности законов управления система управления не может быть описана методами классической теории управления. А также в тех случаях, когда невозможна или нецелесообразна формализация про- цесса управления в виде систем математических уравнений, но при этом имеется описание этого процесса на естественном языке (в лингвистической форме). Искусственный интеллект и теория поиска вывода 'Математическая логика долгое время была одной из самых абстрактных ветвей ма- тематики. Задачи, которые она ставила перед собой и решала, носили чисто внут- ренний математический характер, важный лишь для этой науки. Основной целью математической логики было подвести прочный и непротиворечивый фундамент под здание математики... Давно стало трюизмом утверждение о том, что любая сколь угодно абстрактная ветвь математики со временем становится практически важной и необходимой. Так было с топологией, так было с теорией групп, так случилось и с математической логикой" [51].
146 Часть II. Логическое программирование и искусственный интеллект Моделирование рассуждений человека, осуществление логического вывода (поиск вывода) с помощью вычислительной машины — одно из важнейших направлений работ по ИИ. И здесь результаты, полученные в математической логике, оказались исключительно важными для решения задач ИИ. В 1961 году из участников семинара по математической логике и конструктивной математике, проводимого Н. А. Шаниным на математико-механическом факультете Ленинградского университета, образовалась группа математической логики Ленин- градского отделения Математического института им. В. А. Стеклова АН СССР (ЛОМИ). Эта группа занялась поиском естественного логического вывода, макси- мально близкого по форме к "человеческому". Работа группы началась с построения алгорифма, который выдавал бы достаточно "хороший" и естественный вывод данного утверждения из данных аксиом. Такая постановка задачи была связана с тем, что известные алгоритмы, дающие для каж- дой выводимой формулы какой-то ее вывод, могли выдавать излишне длинный и трудно воспринимаемый вывод. Первоначально рассматривался вывод в классиче- ском исчислении предикатов. Исследования завершились разработкой Алгорифма Поиска Естественного Вывода (АЛПЕВ), который был запрограммирован и обладал по тем временам выдающейся производительностью [82]. В этой программе впервые в мире был смоделирован "человеческий" логический вывод при поиске доказатель- ства утверждений. АЛПЕВ применим для логики высказываний, для исчисления предикатов требова- лись новые идеи. Идея "метапеременных", предложенная Н. А. Шаниным, дала импульс для создания в 1964 году "обратного метода", разработанного С. Ю. Масло- вым [43]. В отличие от созданного в 1965 году метода резолюций Дж. Робинсона [88], обрат- ный метод С. Ю. Маслова нацелен как на машинную реализацию, так и на получе- ние результатов в самой математической логике. Он позволил не только создать программу поиска вывода в исчислении предикатов, но и решить задачу системати- ческой трактовки разрешимых классов формул исчисления предикатов, поставлен- ную Гильбертом [55]. Опыт, накопленный при создании метода поиска вывода в исчислении предикатов, послужил отправной точкой для работ в области общей теории дедуктивных систем [46, 48, 51]. Здесь речь идет о том, что процесс решения человеком творческих задач может моделироваться на основе теории машинного вывода в исчислениях общего типа (исчисления Э. Л. Поста). Этимология слова intellego (inter и lego) — "выбираю среди". Большинство задач, обычно относящихся к творческим, от решения кроссвордов и головоломок до на- писания стихов и доказательства теорем, связаны с перебором вариантов. Тем не менее особенностью человека является способность рационально уменьшить этот перебор. До тех пор, пока машина не обладала достаточным быстродействием, она часто проигрывала в шахматы даже мастеру спорта. Как только быстродействие дос- тигло определенного уровня, она смогла обыграть даже чемпиона мира: в 1994 году ПК на базе процессора Intel Pentium со смехотворной, по нынешним временам, час- тотой 90 МГц обыграл в серии турниров по шахматам нескольких сильнейших гроссмейстеров мира, включая действующего чемпиона мира — Гарри Каспарова. Под термином "творческая задача" [51] можно понимать выполнение некоторой по- следовательности элементарных действий (или ходов), ведущих к решению основной
Гпава 4. Искусственный интеллект 147 задачи. Примерами таких задач являются и игра в шахматы, и нахождение интегра- ла, и решение задачи на построение циркулем и линейкой. Наилучшей модельной задачей такого творчества, которое может быть сведено к интеллектуальному пере- бору в конечном и четко очерченном множестве (решение кроссвордов, игра в шах- маты), а не к выбору и уточнению объекта из неопределенно-неограниченного поля объектов (отгадывание загадок, сочинение стихов), является доказательство теорем в фиксированной и полностью формализованной формальной теории. А такая зада- ча относится к области теории поиска вывода, определяемой как область математи- ческой логики, занимающаяся выявлением по гипотезе структуры ее возможных доказательств, по исчислению и объекту, представленному на языке исчисления, структуры возможных выводов этого объекта. В отличие от классической логики, для которой главное — "что считать истиной", центральным вопросом теории поиска вывода является — "как найти истину". Если говорить о математике в целом, то лишь в редких работах [65] рассказывается о том, как был получен тот или иной результат. Важно только, чтобы он был пра- вильным. Этап же проб и ошибок, который предшествовал нахождению истины, остается за скобками рассмотрения. Теорию поиска вывода, являющуюся ветвью математической логики, интересует именно процесс нахождения решения: формальный вывод в теории поиска вывода. Правило вывода называется допустимым, если его добавление не расширяет множе- ство выводимых элементов. Для понимания творческого процесса и разработки систем ИИ важными являются взаимосвязанные задачи [51]: □ выработка критериев отбора допустимых правил; 3 выявление механизмов их формирования. Первая из этих задач решается путем накопления опыта, выявления и формализации знаний экспертов. Вторая, более сложная, в общем виде не решалась. Поиск решений в ИИ (в англоязычной литературе — problem-solving methods) за- ключается в нахождении эвристик, используемых человеком в процессе решения задач, т. е. механизмов, позволяющих избежать полного перебора вариантов. Таким образом, задача теории поиска вывода в математической логике и эвристического программирования в ИИ общая, с той лишь разницей, что отправной точкой мате- матического рассмотрения является формальная или дедуктивная система (теория, исчисление), а исходная точка ИИ — сам процесс решения задачи человеком. Пусть имеется некоторый класс Р творческих, в выше описанном смысле, задач (см. примеры разд. 'Аксиоматический метод и формальные теории” гл. 1). В качестве аксиом исчисления ТР возьмем задачи, решение которых известно. В ка- честве правил вывода возьмем правила вида: 5*1, ..., Sn |— означающие, что задача 50 будет решена в случае, если решены задачи ..., Sn. Таким образом, некоторая задача из Р имеет решение в случае, если она выводима в построенном исчислении. Найти решение задачи — то же самое, что найти один из ее выводов в построенном исчислении, причем лучшим будет решение, соответ- ствующее более короткому выводу. Исчисление при "удачном" построении может
148 Часть IL Логическое программирование и искусственный интеллект быть полным, т. е. задача из Р будет иметь решение только в том случае, когда она выводима в Тр. Таким образом, решение творческой задачи сводится к поиску вывода в формальной системе. Так как обратный метод С. Ю. Маслова пригоден не только для логических исчислений, но и для произвольных исчислений со свойствами подформульности, автоматизация вывода в этом исчислении приведет и к автоматизации решения ис- ходной задачи. Но задача может решаться по-разному: опыт и мастерство перворазрядника не идут ни в какое сравнение с опытом мастера. Именно переход от одного уровня знания к другому, более высокому, ассоциируется у нас с творчеством в полном смысле этого слова. С точки зрения аппарата исчислений можно говорить о переходе от одного исчисления к другому, более совершенному, в котором количество решаемых (выводимых) задач больше, а время, требуемое на их решение, меньше. Приобрете- ние опыта, таким образом, можно ассоциировать с совершенствованием дедуктив- ной системы, с помощью которой мы решаем задачи данного класса, а переход с одного уровня знания на другой — с "башней исчислений" [51]. По этому поводу Маслов пишет: ’’...машинный поиск найдет решение лишь про- стейших задач. Как же поступит специалист по искусственному интеллекту? Путем теоретических рассмотрений и изучения опыта мастеров он найдет вариант исчисле- ния существенно лучше приспособленный к организации поиска вывода. Другими словами, он поднимется на следующий этаж башни исчислений и втащит с собой свою машину. Вот теперь-то ЭВМ начнет обыгрывать людей с ограниченным опы- том. И будет обыгрывать до тех пор, пока человек, сам умеющий модифицировать свои исчисления, не влезет на следующий этаж... Современные машины тысяче- кратно превышают человеческие возможности к механическому поиску на своем этаже, но лишь способность к усовершенствованию исчислений человек склонен считать творческой. По крайней мере, пока. Пока машинное моделирование этой способности находится в зачаточном состоянии. Надеюсь, когда-нибудь человек научит машину моделировать эту способность. Надеюсь, у него и тогда останется много оснований для высокой самооценки". Современное состояние искусственного интеллекта Технологии с использованием ИИ применяются сегодня во многих прикладных об- ластях1. В последние годы ИТ-технологии совершили резкий скачок вперед, в ос- новном за счет повышения производительности процессоров и стремительного уде- шевления памяти. Это привело к появлению приложений, в которых воплощены серьезные теоретические наработки ИИ. При этом можно отметить две тенденции: "военную" и "гражданскую". Военное научное агентство DARPA — крупнейший в мире финансист исследований по ИИ, особенно по робототехнике. Современное оружие немыслимо без подходов 1 На основе материалов С. Бобровского "Досье искусственного интеллекта" www.computcr-museum.re /frgnhist/ail.htm; www.computer-museum.ru/frgnhist/ai2.htm;www.computer-museum.ru/frgnhist i /ai4.htm, [9]. j
Глава 4. Искусственный интеллект 149 ИИ (преимущественно нейронных технологий, нечетких экспертных систем и ин- теллектуальных решателей), позволяющих с помощью относительно малых ресурсов получать достаточно точные результаты, для нахождения которых классическими методами численной математики потребовались бы мощности суперкомпьютеров. Например, реализация режима автономного полета на небольшой высоте в плохих погодных условиях без использования заранее подготовленной компьютерной базы рельефа требует применения высокоэффективных механизмов синхронизации дви- жения с данными, получаемыми от системы навигации GPS, видеокамер, радаров и других датчиков. В связи с этим состояние наработок в определенных направлениях ИИ закрыто от посторонних глаз. С другой стороны, рынок бытовых роботов и интеллектуальных домашних устройств уже сегодня приносит немалую прибыль, а в перспективе это будет огромный биз- нес, поэтому коммерческие компании занимаются исследованиями по ИИ своими силами, в чем-то дублируя DARPA и другие подобные организации. Надо отметить, что основная часть реально используемых систем обычно создается с привлечением различных технологий ИИ, что позволяет существенно повысить качество конечного результата. Подробную информацию по ИИ можно найти в Internet. Большая подборка мате- риалов находится по адресу: http://dir.yahoo.coni/Science/Computer_Science/ArtificiaI_InteIIigence/. Экспертные системы Создание экспертных систем (ЭС) традиционно считается классическим занятием для специалистов по ИИ. ЭС многократно хоронились, признавались тупиковым направлением, тем не менее компьютеры научились давать советы в конкретных областях человеческой деятельности на уровне хороших экспертов (см. приложе- ние 3). Основной акцент в современных ЭС делается на принятии оперативных решений в реальном масштабе времени. Это объясняется нуждами современного бизнеса. Ком- мерческие ЭС контролируют крупные промышленные процессы, принимают реше- ния по результатам показаний сотен периферийных устройств, управляют большими сетями, распределенными СУБД, подсказывая оператору, как поступить в сложной обстановке, а в критических ситуациях, требующих немедленного решения, берут управление на себя. Достаточно активно развивается и такое направление, как авто- матическое накопление знаний, снимающее с человека рутинную работу по качест- венному анализу различных процессов. Практически все современные системы по- иска данных представляют собой ЭС, В коммерческих продуктах применяются более гибкие подходы — нечеткие техноло- гии (когда вместо обычной двоичной логики "да-нет" используется логика с беско- нечным числом состояний), Bayesian-сети, учитывающие вероятности перехода уз- лов в другие состояния, и т. д. Система MOBAL дает возможность строить модель понятия в терминах логических правил и фактов, позволяя с помощью языка поиска похожих объектов Query-by-
150 Часть IL Логическое программирование и искусственный интеллект Similarity в сочетании с нечеткой логикой создавать очень гибкие системы хранения и обработки информации. Интеллектуальный решатель C-PRS (Procedural Reasoning System in С), написанный на стандарте ANSI С, используется NASA, в авиапромышленности, в системах управления перевозками и мобильными роботами. Он переносим, очень компактен и с помощью кроссплатформных компиляторов допускает встраивание практически в любое оборудование. В России осуществлено несколько внедрений крупной ЭС контроля, управления и моделирования сложных процессов Gensym G2. Группа адаптивных систем корпорации Microsoft трудится над совершенствованием технологии Office Assistant, которая позволяет отслеживать поведение пользователя и подсказывать ему правильные действия в запутанных ситуациях. В открытый проект AIOS (http://blkbox.com/~cravey/aios) приглашаются все желаю- щие. Он посвящен созданию операционной системы с элементами ИИ. В нее будут встроены технологии обработки естественной речи, автоматического решения задач и самообучения. Коммерческие системы имеют довольно большой ценовой диапазон, и если простые настольные системы стоят несколько сотен долларов, то цена промышленных при- ложений возрастает в тысячи раз, что свидетельствует об их несомненной востребо- ванности. Робототехника При создании домашних автономных устройств возникает не меньше проблем, чем при создании военных и космических роботов: требования максимальной безопас- ности значительно усложняют задачи разработчикам. Развивается рынок автономных домашних пылесосов. Модели оборудованы навига- ционной системой и всевозможными периферийными датчиками. Роботы-пылесосы перемещаются по квартире по случайным траекториям, собирая мусор, объезжая статические предметы, и "удирают" от движущихся объектов (людей и животных). "Умные" пылесосы способны самостоятельно возвращаться на свое "место жительст- ва" для подзарядки. Другой перспективный рынок — автономные газонокосилки. Например, фирма Electrolux выпускает косилки, способные подзаряжаться от солнечной батареи, запа- саться энергией на ночь и работать практически круглосуточно. Более совершенные модели интеллектуальных бытовых устройств, помимо уборки мусора, способны выполнять множество дополнительных функций, например под- носить напитки и тапочки. Робот Суе фирмы Probotics, постоянно подключенный к ПК, дистанционно управляется заложенной в компьютер программой. С помощью удобного визуального инструмента пользователь может, используя план комнат, оп- ределить для Суе траектории передвижения, доступные и запрещенные области в квартире. Общение с роботом выполняется по протоколу, содержащему 35 команд и 20 ответных сообщений от робота. Немаловажно, что программное обеспечение Суе открыто для совершенствования, позволяет расширять базовые возможности систе- мы и создавать на его основе собственные программы управления роботом. В буду-
Гпава 4. Искусственный интеллект 151 щих версиях Суе будет поддерживаться навигационная система GPS, и он сможет передвигаться не только по комнатам, но и на приусадебном участке. Спрос на подобные устройства растет, и известная компания NEC уже представила модель Personal Robot R100, который поступил в продажу в 2001 году. Робот высотой 44 см и весом 8 кг самостоятельно передвигается, способен произносить 300 фраз, понимать сотни команд и различать 10 лиц. R100 может приносить мелкие вещи, вынимать почту из ящика, включать и выклю- чать телевизор и кондиционер, записывать видеосообщения и передавать их по на- значению. Он подключен к ПК и имеет встроенный процессор Intel 486 DX4. Сотрудники лаборатории ИИ Массачусетского института считают, что робот обяза- тельно должен взаимодействовать с окружающим миром и выполнять социально значимые функции. Исходя из этой посылки, они разрабатывают робота Cog, своим внешним видом и даже отчасти устройством напоминающего человека. Система управления робота Cog представляет собой сложную иерархию устройств. В большинство узлов Cog встроены процессоры Motorola 68 332 16 МГц, на которых выполняется интерпретатор L (версия Common Lisp). Интенсивная обработка ин- формации происходит в сети промышленных 200 МГц процессоров в ОС реального времени QNX. Университет Северной Каролины разрабатывает роботов, способных перемещаться в завалах и спасать людей, оказавшихся под развалинами в результате различных ката- строф. Робот Moccasin II, напоминающий сегментированного червяка, может про- ползать в туннелях диаметром 20 см и поворачивать на 90 градусов в любых направ- лениях, анализируя информацию от видеокамеры с подсветкой и датчиков давления, с помощью которых он "ощущает" стены и их изгибы. Следующие модели робота можно будет без опаски применять при обследовании крупных технологических конструкций (танкеров, самолетов). NASA создала робота величиной с небольшой мячик. Он понимает голосовые коман- ды, снабжен видеокамерой, датчиками температуры, давления и газовыми анализа- торами и способен самостоятельно путешествовать внутри космических станций, выполняя мониторинг их состояния. Военная лаборатория США Jet Propulsion Laboratory, совместно с японским институ- том аэрокосмических исследований, наметила первую в истории человечества вы- садку робота на астероид Nereus. Он должен будет собрать образцы грунта и вер- нуться обратно на корабль. В перспективе для исследования Марса, астероидов и комет этой же командой будут создаваться микророботы весом от 100 до 10 граммов. Американская Ассоциация по ИИ на Национальной конференции 1999 г. организо- вала турнир, по условиям которого роботы должны были самостоятельно добраться до зала заседаний. Им требовалось отстоять очередь на регистрацию, подняться по эскалатору, получить баджет, выйти (или выползти) на сцену, в течение двух минут рассказать о себе и попробовать ответить на простейшие вопросы. В первом турнире роботы двух университетских команд, добираясь до конференц-зала, пробовали "хитрить" — один просил окружающих подсказать ему правильное направление дви- жения, а второй легонько подталкивал людей, обращаясь к ним с просьбой отнести его в зал. Японские ученые, традиционно склонные к миниатюризации, создали робот длиной 1 см и массой в полграмма. Он предназначен для обслуживания АЭС и ТЭС. Робот
152 Часть II. Логическое программирование и искусственный интеллект способен проползать по тончайшим трубкам, проникать в поврежденные зоны и закрывать "пробоины" своим телом. В медицинском центре Государственного университета штата Огайо создан робот- хирург, который имеет видеокамеру и две небольшие руки-манипуляторы, а управ- ляет им человек с помощью компьютера. Через небольшие разрезы на теле устрой- ство проникает в сердце, после чего, на основании полученной от него видеоин- формации, компьютер формирует трехмерный образ органа, что позволяет выпол- нять операцию значительно эффективнее. Хирурги единодушно признали пользу такого устройства. Военные медики США создали робот для проведения операций в полевых условиях. Им можно дистанционно управлять с любого расстояния. Похожие работы проводятся в российском Научном центре сердечно-сосудистой хирургии имени Бакулева РАМН. Используемый там робот также снабжен несколь- кими манипуляторами, способными держать различные инструменты (скальпель, пинцет и т. д.). Благодаря повышенному числу степеней свободы он может работать в самых неудобных, недоступных для человека положениях. Врач за монитором сле- дит за зоной операции и управляет манипуляторами, подавая через компьютер голо- совые команды. Военных в робототехнике интересуют прежде всего автономные летающие устройст- ва для сбора разведывательной информации. Агентство DARPA готово финансиро- вать выпуск беспилотных самолетов, в течение 20—60 минут полета преодолевающих 30—60 км и имеющих при этом массу не более 1 кг, а длину 20 см. Фирма AeroVironment разработала снабженный различными датчиками самолет дли- ной 7,5 см, который может быть послан на разведку в очень сложных условиях — например, в городе. Пока главной проблемой в этих разработках остается посадка такого самолета, обычно не обходящаяся без поломок. В США ежегодно проводится множество состязаний между роботами, цель которых одна — любой ценой уничтожить противника. Тем, кто не имеет возможности создать своего робота и отправиться с ним в США, можно предложить виртуальный турнир, проводимый на сайте www.robotwar3d.com. В июне 2002 года в Японии прошли сразу два мировых чемпионата по футболу: вто- рой — среди собак-роботов (www.ananova.com). Это уже шестой по счету чемпионат RoboCup 2002, в нем приняли участие сборные команды роботов из Франции, Ита- лии, Швеции, Германии, Португалии и Англии. Продолжительность матча — 20 ми- нут. Автономные агенты Технологии автономных агентов привлекательны прежде всего тем, что позволяют разработчику, не знающему точного способа решения задачи или оптимальных па- раметров управления процессами, обойтись минимумом усилий, создав только один прототип агента, содружество которых затем запускается в компьютерную среду (на- пример, в распределенную сеть) и достаточно эффективно выполняет черновую пе- реработку информации, адаптируясь к окружающей среде и постепенно достигая поставленной цели.
Гпава 4. Искусственный интеллект 153 В Колумбийском университете создана система JAM, предназначенная для анализа безопасности проведения электронных транзакций. Центральная самообучающаяся система, первоначально использующая набор шаблонов аномальных ситуаций, сле- дит за изменением содержимого больших и быстро растущих распределенных БД в реальном масштабе времени, выявляя типичное для конкретной организации "пове- дение" транзакций. За возможными неисправностями и отклонениями в работе сле- дят локальные агенты, также способные к обучению. Они передают накопленные сведения в главную систему, принимающую стратегические решения. Агенты могут объединяться в группы и превращаться в метаагентов, синтезирующих конкретные модели обучения своих подопечных. Продолжаются работы над технологией Microsoft Agent, вводящей в Windows интерак- тивные персонажи, с которыми можно общаться и советоваться. Этот подход пред- полагает принципиально новый интерфейс взаимодействия человека и компьютера (http://msdn.microsoft.com/workshop/imedia/agent/). Но наиболее перспективным сегодня считается применение интеллектуальных по- мощников для выполнения всевозможных операций поиска и сбора информации в Internet — пока это всевозможные сетевые агенты (пауки, Web-роботы), индекси- рующие данные в Internet. Однако стремительный и хаотический рост Web- пространства требует новых подходов для отбора нужных сведений. Новые поколе- ния интеллектуальных автономных агентов подходят для этого как нельзя лучше — они способны самообучаться, эффективно взаимодействовать друг с другом и прояв- лять определенную самостоятельность при общении с клиентом. Некоторые из разработчиков считают, что агент должен делать за пользователя в Internet все — надо просто послать его за нужной информацией или файлом, и он принесет их "на блюдечке". В более отдаленной перспективе предполагается перене- сти на агентов и работу по электронной коммерции, когда продавцами и ассистен- тами будут выступать программы, а людям останется только подсчитывать прибыль. Идея хорошая, но пока для ее реализации нет подходящих ресурсов. Нужны мощ- ные суперкомпьютеры, распределенные хранилища данных, эффективные низко- уровневые технологии поиска и ОС, поддерживающие выполнение мобильного кода. Определенные шаги в этом направлении уже сделаны. Когда появилась технология Java, группа OMG разработала Mobile Agent System Interoperability Facility — требо- вания к протоколу общения агентов. Развивает эти идеи, в частности, и японское отделение IBM, создавшее библиотеку разработчика Java-агентов (аглетов) Java Aglets SDK (www.trl.ibm.co.jp/aglets), в основу которой положен переделанный из Sun слоган: "пишется однажды, передвигается везде”. Немалую активность в создании агентов для Internet проявляет DARPA. Оно разра- ботало протокол обмена знаниями и основанный на нем язык общения агентов Knowledge Query and Manipulation Language (KQML), также пригодный для приме- нения в интеллектуальных решателях. Интересное направление в области автономных агентов родилось с развитием языка XML. На его основе сегодня создаются XML-агенты, способные по запросу предос- тавлять информацию из произвольных источников данных. Немаловажно, что в от- личие от Java-технологии, когда для выполнения байт-кода требуется JVM-машина, XML-агенты описываются единственным тегом <agent> и используют всего три типа объектов —- ввод, вывод и ошибка. Они содержат набор "рецепторов" для общения со своими "коллегами", могут применять любой доступный язык сценариев (или специ-
154 Часть II. Логическое программирование и искусственный интеллект ально разработанный XML Script), а также CGI-программы — в зависимости от возможностей сервера, на котором они выполняются. Главное, чтобы локальные программы стандартно обрабатывали три вышеперечисленных типа объектов. Если классические агенты "путешествуют' по Сети сами, то XML-агенты лишь об- мениваются сообщениями и данными с другими агентами с помощью НТТР- запросов, что позволяет создавать сколь угодно сложные структуры автоматического хранения и управления данными. Мозг как аналого-цифровое устройство Группа швейцарских и американских ученых из Института нейроинформатики (Цю- рих) и Манчестерского технологического института утверждает, что им удалось соз- дать технологию, полностью повторяющую механизм функционирования человече- ского мозга, который обрабатывает одновременно цифровую и аналоговую инфор- мацию. Для этого задействуются специфические возможности межнейронных каналов по быстрому распространению в нейронной сети важных сигналов и подав- лению и затуханию слабых сигналов, что настраивает эту сеть на решение наиболее актуальной на данный момент задачи. Новая технология открывает путь к появле- нию специализированных высокопроизводительных компьютеров (например, для распознавания объектов). Уместно вспомнить схожие работы академика Амосова двадцатилетней давности, в которых активно развивалась подобная идея системы усиления торможения (СУТ) в нейронной сети. Искусственная жизнь Вопросы создания кибернетических устройств — полностью электронных или по- строенных на базе живых существ и способных эффективно выполнять присущие человеку действия, как интеллектуальные, так и двигательные, — все больше при- влекают внимание разработчиков. До недавнего времени в этой области главенствовал подход, основанный на созда- нии управляющих алгоритмов для конкретного применения, что позволило, напри- мер, наладить массовый выпуск настраиваемых роботов-сборщиков. Современный подход опирается на теории адаптивных систем и эволюционного развития. В соот- ветствии с ним предполагается, что устройства управления должны самостоятельно мутировать и развиваться: менять число и положение различных датчиков, свою форму, размеры и т. п. Один из финансируемых DARPA-проектов — система сборки конструкций из куби- ков Лего, состоящая из манипулятора, видеокамеры и компьютера. Исходно в нее заложены только элементарные правила стыковки кубиков и цель — конечное со- оружение, после чего система начинает пробовать различные комбинации кубиков, экспериментально определяя прочность и стабильность собираемых структур, вы- полняя несложную оптимизацию процессов сборки. Пока за один день системе уда- ется самостоятельно собирать конструкции типа двухметровых игрушечных мостов и кранов, способных поднимать груз весом в полкилограмма. Самое главное, что эти конструкции отвечают всем инженерным требованиям по надежности, о которых
Глава 4. Искусственный интеллект 155 система и не подозревает. Следующая цель разработчиков — подобным путем авто- матизировать сборку системой себе подобных роботов. Мичиганский университет создал программу, моделирующую процессы эволюции организмов. Экспериментируя с ней, ученые пришли к выводу, что мутации в ряде случаев приводят к резкому повышению жизнестойкости виртуальных существ, при- чем она достигается не просто сложением качеств. Более жизнестойкими оказались существа, способные сопротивляться множественным мутациям. Удивление иссле- дователей вызвало и то, что выживают более простые организмы. Тем не менее множество экспериментов над цифровыми насекомыми помогло не только понять детали механизма мутаций, но и определить правильные подходы к воздействию на этот процесс, что весьма актуально, в частности, для генной ин- женерии. Например, программа Avida, моделирующая процессы в чашках Петри, позволила в ряде задач по выделению генотипов повысить их эффективность в мил- лионы раз. Финансирует эти исследования корпорация Microsoft и Национальный научный фонд США. Другой проект Microsoft — Microsoft Ball — предназначен для моделирования эмо- циональных состояний личности. В соответствии с ним программа должна опреде- лять эмоциональное состояние пользователя и менять способ общения с человеком. Microsoft Ball основан на результатах экспериментов, показывающих, что люди, по- лучающие эмоциональный отклик и реакцию на их действия от программы, испы- тывают больший комфорт при работе с компьютером, чем те, кто использует "мол- чаливые” приложения. Чат-роботы На сайте www.mindpixel.coin все желающие могут пообщаться с искусственным соз- нанием Generic Artificial Consciousness (GAC) и задать ему вопросы, требующие про- стого ответа "да" или "нет". Создатель GAC, компьютерный фанат Крис Мак-Кинли, написавший в 12 лет шахматную программу для микрокомпьютера TRS-80, рассчи- тывает в ближайшие 10 лет собрать миллиард фактов для GAC, что должно сделать искусственное сознание по уровню интеллекта неотличимым от среднего человека. С другой разработкой подобного плана, ALICE, можно познакомиться на сайте www.alicebot.org. Эти разработки обычно называют чат-роботами, поскольку они способны через Internet поддерживать разговор на любые темы неограниченно дол- го. Профессор Ричард Воллэс разработал первую версию своего чат-робота в 1995 г. с помощью языка программирования SETL, основанного на принципах теории множеств и математической логики. В 1998 г. вышла Java-версия программы, в том числе и WinALICE для Windows. ALICE распространяется в исходных текстах. К ней приложен своеобразный конст- руктор знаний AIML, позволяющий расширять сферу познаний ALICE простым добавлением новой информации, описанной в текстовом виде (на XML). Общение с человеком на естественном языке Эта задача — одна из первых задач ИИ. 30 лет тому назад была разработана про- грамма Eliza объемом 240 строк на Лиспе, позже — аналогичная программа на Про- логе. Она выделяла ключевые слова во фразах, строила на их основе простые во про-
156 Часть II. Логическое программирование и искусственный интеллект сы и создавала неплохую иллюзию реального диалога. Большинство сегодняшних разработок действуют по тому же принципу — определяют ключевые слова и их комбинации без понимания смысла. Однако имеются программы на основе иных принципов. Так, компания Artificial Life (www.artificial-life.com) выпускает набор продуктов, использующих технологии ИИ. Сервер приложений Klone Server поддерживает работу автономных агентов (интерак- тивных персонажей), которые общаются с посетителями сайта на естественном языке. Пока они способны отвечать на 20% вопросов, что обеспечивает 80% всех потребностей посетителей в информации. Агенты могут решать различные проблемы общения, основываясь на системе логиче- ского вывода, имеющей несколько уровней синтаксического анализа фраз, слежения за контекстом разговора и понимания отдельных слов. Способность поддерживать беседу и понимать, куда клонится разговор, очень важна — она позволяет вносить в процесс общения эмоциональные составляющие, например, при реакции агентов на трудные вопросы. Агенты могут также определить предпочтения посетителя, обра- щаясь к базе знаний о структуре сайта. Клиент-серверная версия Klone Server на 50 одновременно работающих с агентами посетителей стоит $2499. Модуль Messenger выполняет автоматический анализ и обработку поступающей элек- тронной почты и способен самостоятельно отвечать на письма или перенаправлять их на другие адреса в зависимости от смыслового наполнения. Модуль Knowledge Manager формирует смысловую карту корпоративных документов и семантические взаимосвязи между ними. Модуль SalesRep составляет пользовательские профили, анализируя траектории пу- тешествия людей по сайту и результаты общения с агентами. Эти профили предна- значены для применения в системах электронной коммерции. Модуль CallCenterAgent позволяет создавать интеллектуальные центры обслуживания, способные в автономном режиме без участия человека выдавать быстрый ответ на обращение по телефону или электронной почте. Модуль Portfolio Manage г определяет типичные интересы человека и предпочтитель- ные для него способы общения (телефон, пейджер, почта), а также вероятность про- дажи ему того или иного продукта. Перспективы и тенденции развития искусственного интеллекта Переход от первых исследований и экспериментальных программ по ИИ к реаль- ным системам ИИ оказался длительным процессом, более длительным, чем предпо- лагали создатели этого направления информатики. Анализ показал, что практически все проблемы связаны с нехваткой ресурсов двух типов: компьютерных — вычисли- тельной мощности, емкости оперативной и внешней памяти и людских — наукоем- кая разработка интеллектуальных программ требует привлечения ведущих специали- стов из различных областей знаний. Кроме того, определенные проблемы создает и организация долгосрочных исследовательских проектов.
Глава 4. Искусственный интеллект 157 Рейтинг перспективных технологий С. Бобровский [9] проанализировал тематику европейских и американских конфе- ренций по ИИ за последние несколько лет (ежемесячно в мире проходят десятки таких конференций) и на основе этого анализа составил общую картину развития различных направлений ИИ. Наибольшее число конференций посвящено нейрон- ным сетям и эволюционным вычислениям. Нейронные сети Продолжается совершенствование алгоритмов обучения и классификации в масшта- бе реального времени, обработки естественных языков, распознавания изображений, речи, сигналов, а также создание моделей интеллектуального интерфейса, подстраи- вающегося под пользователя. Среди основных прикладных задач, решаемых с по- мощью нейронных сетей, — финансовое прогнозирование, поиск и шифрование данных, диагностика систем, контроль за деятельностью сетей, создание эффектив- ных методов синхронизации работы нейронных сетей на параллельных устройствах. Эволюционные вычисления Автономное и адаптивное поведение компьютерных приложений и робототехниче- ских устройств затрагивают практические проблемы самосборки, самоконфигуриро- вания и самовосстановления систем, состоящих из множества одновременно функ- ционирующих узлов. Другой аспект эволюционных вычислений — использование для решения повсе- дневных задач автономных агентов в качестве персональных секретарей, управляю- щих личными счетами, ассистентов, выбирающих нужные сведения в сетях с по- мощью поисковых алгоритмов третьего поколения, планировщиков работ, личных учителей, виртуальных продавцов и т. д. Сюда же относится робототехника и все связанные с ней области. Основные направления развития — выработка стандартов, открытых архитектур, интеллектуальных оболочек, языков сценариев/запросов, методологий эффективного взаимодействия программ и людей. Традиционные направления В ряду традиционных направлений развития ИИ отметим следующие: □ нечеткая логика; □ обработка изображений; □ экспертные системы; □ интеллектуальные приложения, способные быстро находить оптимальные реше- ния комбинаторных проблем; □ распределенные вычисления; □ операционные системы реального времени (принятие решений в условиях дефи- цита времени);
158 Часть II. Логическое программирование и искусственный интеллект □ программная и интеллектуальная инженерия (анализ исходных текстов и пони- мание их смысла, управление требованиями, выработка спецификаций, проекти- рование, кодогенерация, верификация, тестирование, решение задач на парал- лельных системах; проблемы представления и обработки знаний, способы пре- вращения информации в знания); □ самоорганизующиеся СУБД. Способность гибко подстраиваться под профиль конкретной задачи без администрирования; □ автоматический анализ естественных языков (лексический, морфологический, терминологический, т. е. выявление незнакомых слов, распознавание националь- ных языков, перевод, коррекция ошибок, эффективное использование словарей); □ высокопроизводительный OLAP-анализ и поиск данных, способы визуального задания запросов; □ медицинские системы, консультирующие врачей в экстренных ситуациях, робо- ты-манипуляторы для выполнения хирургических операций; □ создание полностью автоматизированных киберзаводов, гибкие экономные про- изводства, быстрое прототипирование, планирование работ, синхронизация це- почек снабжения, авторизация финансовых транзакций путем анализа профилей пользователей. В ряду же новых направлений можно отметить моделирование социального поведе- ния, общения, человеческих эмоций и творчества. Военные технологии Исследования в области нейронных сетей, позволяющих получить хорошие (хотя и приближенные) результаты при решении сложных задач управления, часто финан- сирует военное научное агентство DARPA. Пример —- проект Smart Sensor Web, ко- торый предусматривает организацию распределенной сети разнообразных датчиков, синхронно работающих на поле боя. Каждый объект (стоимостью не более $300) в такой сети представляет собой источник данных: визуальных, электромагнитных, цифровых, инфракрасных, химических и т. п. Проект требует новых математических методов решения многомерных задач оптимизации. Ведутся работы по автоматиче- скому распознаванию целей, анализу и предсказанию сбоев техники по отклонени- ям от типовых параметров ее работы (например, по звуку). Операция "Буря в пустыне" стала стимулом к развитию экспертных систем, приме- няемых в области снабжения. На разработках, связанных с технологиями машинно- го зрения, основано все высокоточное оружие. Существует ряд нерешенных научных проблем. Значительные усилия концентриру- ются на исследованиях по распознаванию речи, созданию экспертных и консульта- ционных систем, призванных автоматизировать рутинные работы и снизить нагрузку на пилотов. ЭВМ пятого поколения и ИИ в Японии Япония — страна, предложившая миру двадцать лет тому назад проект вычисли- тельных систем пятого поколения [84]. В основе этого проекта лежала идея логиче- ского программирования. Целью проекта не было создание в течение десяти лет, на
Гпава 4. Искусственный интеллект 159 которые был рассчитан проект, какого-либо коммерческого продукта. Его целью было выполнение исследований, связанных с разработкой новых компьютерных технологий, основанных на идеях ИИ, в частности инженерии знаний и логического программирования. Логическому программированию (и языку Пролог) отводилась роль унифицирующего звена, позволяющего объединить различные направления исследований в науке о компьютерах. Логическому же программированию, пони- маемому как идея использования для программирования логики, например логики предикатов, отводилась основная роль и в исследованиях по ИИ, и в разработке параллельных структур [78]. Проект ЭВМ пятого поколения стимулировал развитие работ по логическому про- граммированию во всем мире. В частности, появилось множество различных реали- заций языка Пролог, идеи и реализации Пролог-машин и вычислительных архитек- тур, отличных от фон-неймановской, идей и архитектур параллельных вычислений на основе Пролога [84]. По анализу, проведенному С. Бобровским [9], профиль японских конференций не сильно отличается от общемирового. Направления, более популярные в Японии по сравнению с европейскими и американскими школами ИИ: создание и моделирова- ние работы е-рынков и е-аукционов, биоинформатика (электронные модели клеток, анализ белковой информации на параллельных компьютерах, ДНК-вычислители); обработка естественных языков (самообучающиеся многоязычные системы распо- знавания и понимания смысла текстов). Internet: интеграция Сети и всевозможных датчиков реального времени в жилых домах, интеллектуальные интерфейсы, автома- тизация рутинных работ на основе формализации прикладных и системных понятий Internet, итерационные технологии выделения нужных сведений из больших объемов данных). Робототехника: машинное обучение, эффективное взаимодействие авто- номных устройств, организация движения, навигация, планирование действий, ин- дексация информации, описывающей движение. Способы представления и обработ- ки знаний: повышение качества знаний, методы получения знаний от специалистов и экспертов, поиск данных, решение на этой основе практических задач — напри- мер, управление документооборотом. Отмечается много работ, посвященных алгоритмам логического вывода, обучению и планированию действий роботов. Перспективы логического программирования в ИИ С точки зрения перспектив логического программирования интересны некоторые из тенденций развития ИИ технологий. □ Возможность применения научных методов на практике за счет роста производи- тельности современных компьютеров в сочетании с повышением качества алго- ритмов. □ Развитие методов простого перебора вариантов, обходящихся крайне упрощен- ным описанием объектов. С помощью такого подхода могут быть решены раз- личные задачи, например, из области криптографии.
160 Часть II. Логическое программирование и искусственный интеллект □ Развитие простых, но ресурсоемких алгоритмов адаптивного поведения автоном- ных устройств. Разработка систем, заменяющих человека и основанных на зна- ниях о том, как человек решает подобные задачи. □ Внедрение формальной логики в прикладные системы представления и обработ- ки знаний. □ Интеграция различных систем логического вывода в единых оболочках с целью более полного отражения реальности. □ Переход от концепции детального представления информации об объектах и приемов манипулирования этой информацией к более абстрактным формальным описаниям и применению универсальных механизмов вывода.
ГЛАВА 5 Экспертные системы Концептуальное знание редко воплощается в форме, дос- тупной для обработки на ЭВМ. А если воплощается, то чаще всего неполно и односторонне. Носителем концеп- туального знания в большинстве случаев остается человек. Это тормозит автоматизацию многих процессов. Создание и широкое использование баз знаний во всех областях применения вычислительной техники — одна из актуаль- ных задач ближайших десятилетий. С. А. Лавров По словам руководителя японской программы ЭВМ пятого поколения, директора специально созданного для этих целей института (Institute for New Generation Com- puter Technology Research Center) К. Фуги [78]: "... исследования по экспертным сис- темам приведут к созданию симбиоза человечества и компьютеров". Для реализации этой идеи Пролог, без сомнения, является одним из наиболее пригодных инстру- ментов. Конечно же, можно создавать экспертные системы и на других языках программи- рования: "знания эксперта являются определяющим фактором высококвалифициро- ванной деятельности, а методы представления этих знаний и методы логического вывода, заложенные в экспертную систему, вторичны и являются лишь механизмом успешного их использования" [69]. Тем не менее искусственный интеллект и экспертные системы — две наиболее пер- спективных области применения логического программирования. Что такое экспертная система Если словосочетание "искусственный интеллект" может восприниматься как нечто парадоксальное, то "экспертная система" воспринимается как вполне понятное, хотя часто неправильно понимаемое, словосочетание. Некоторые, даже вполне ученые мужи, понимают под этим некую систему, осущест- вляющую экспертизу, проверяющую качество того или иного продукта или решения на основе формальных методов, например метода экспертных оценок. 6 Зак. 963
162 Часть II. Логическое программирование и искусственный интеллект На самом деле под экспертной системой (ЭС) понимают систему (программу), помо- гающую специалисту, консультирующую или даже частично заменяющую его на тех или иных стадиях деятельности. ЭС — система, основанная на знаниях экспертов, т. е. людей, умеющих наилучшим способом выполнять свою работу. Так как областью применения ЭС является интеллектуальная деятельность человека, то и методы, используемые при их создании, принадлежат к области ИИ. Первые исследования в области ИИ не претендовали на получение каких-либо практических результатов (автоматизация доказательства теорем, решение игровых задач, программы, играющие в шахматы), важной была принципиальная возмож- ность решения задач такого рода. В дальнейшем основное внимание было уделено именно практическому использованию методов ИИ на поприще создания ЭС. Классик ИИ, Нильс Нильсон [58], более тридцати лет тому назад основной целью развития методов ИИ видел создание интеллектуальных роботов, обладающих теми или иными способностями человека: видеть, слышать, осязать, распознавать и по- нимать естественный язык и, главное, мыслить, осуществлять логические выводы. Именно этому направлению и были посвящены первые разработки. Трудно в это поверить, но уже тридцать лет тому назад была разработана программа, понимаю- щая естественный язык [16], причем не просто язык команд, а язык, помогающий установить контакт с системой: программа-робот выполняла действия по манипули- рованию кубиками и объясняла их. Например, на вопрос: “Почему был перемещен этот, а не другой кубик?'*, система (программа) давала вполне вразумительный ответ: “Потому что он мешал мне взять другой, находящийся под ним". В первых работах по ИИ решались фундаментальные задачи, не зависящие от пред- метной области: разработка методов представления и извлечения знаний, осуществ- ление логических выводов и принятие решений на основе знаний, доказательство теорем, перевод с одного языка на другой и т. д. В дальнейшем стало понятно, что в каждой конкретной области знаний, будь то медицина или геология, при управлении космическим аппаратом или атомной элек- тростанцией существуют конкретные, не формализуемые математически, знания, навыки, умения специалистов, от которых и зависит успешное решение той или иной задачи. В связи с этим обстоятельством в отдельную задачу выделилась задача получения (извлечения) знаний или инженерия знаний-, выявление знаний эксперта-специа- листа, их четкая формулировка и внесение в базу знаний компьютера. Отсюда цен- тральными проблемами ЭС как систем, основанных на знаниях экспертов, являются проблемы извлечения и представления знаний, манипулирования ими (добавления, удаления, обновления), а также извлечение ответов на поставленные вопросы. Области создания и применения ЭС Приложения для ЭС можно классифицировать следующим образом: О интерпретация — построение описаний ситуаций по наблюдаемым данным: рас- познавание, понимание речи, анализ изображений, определение химической структуры, интерпретация сигналов, анализ разного рода информации;
Гпава 5. Экспертные системы 163 О прогноз — вывод вероятных следствий из заданных ситуаций, предсказание пого- ды, урожая, прогнозы в военных операциях; О диагностика — заключение о нарушениях в системе, исходя из наблюдений, ди- агностика в медицине, в системах программного обеспечения, в технических уст- ройствах; О проектирование — построение конфигурации объектов при заданных ограничени- ях, синтез возможных проектных решений и их анализ с точки зрения ограни- чений; О планирование — проектирование плана действий: планирование действий робота, планирование военных операций, автоматизация программирования; О мониторинг — сравнение наблюдаемого и желаемого поведения системы, предот- вращение опасных ситуаций: при управлении атомной электростанцией, воздуш- ным движением, финансами, ходом лечения заболевания; а системы обучения — определение уровня знаний и обучение; О управление — интерпретация, прогноз, мониторинг, не формализуемые задачи управления и задачи, не поддающиеся решению традиционными математически- ми методами. Общие принципы построения и функционирования ЭС ЭС представляет собой человеко-машинную систему, которая, в простейшей форме, может быть описана следующим образом. Основу ЭС представляет база знаний и данных, содержащая модель предметной об- ласти. Различные экспертные системы отличаются средствами представления знаний. Часть базы знаний содержит фундаментальные знания и данные о предметной об- ласти. Эти знания могут изменяться и пополняться. Например, для медицинской ЭС такими знаниями будут знания о болезнях, методах их лечения, лекарственных сред- ствах (см. приложение 3). Другая часть базы знаний содержит знания, которые со временем изменяются более интенсивно: данные и знания о больных, ходе лечения, применяемых средствах лечения и т. п. Для ответа на поставленные пользователем вопросы требуется система извлечения ответов, называемая машиной логического вывода или интерпретатором. ЭС, кроме ответа на предъявляемые пользователем вопросы, должна объяснить при- чины, по которым дается тот или иной ответ, то или иное решение. Объяснение осуществляется соответствующей системой пояснения решений. Взаимодействие пользователя с ЭС осуществляется посредством оболочки, пред- ставляющей пользовательский интерфейс двух категорий: для разработчиков ЭС — программистов и для инженеров по знаниям. Программисты осуществляют разра- ботку и тестирование ЭС как программы, разработанной на том или ином языке программирования. Инженеры по знаниям осуществляют взаимодействие между экспертами и ЭС, пополняя и корректируя базу знаний.
164 Часть II. Логическое программирование и искусственный интеллект Примеры ЭС Система MYCIN была разработана для оказания помощи врачу для постановки диаг- ноза и лечения бактериальных заражений крови. Консультация ведется на естест- венном языке, ЭС способна ответить на вопросы, поставленные на обычном языке и объяснить свои ответы. Эта система, разработка которой началась в 1974 году, по- стоянно совершенствуется и с успехом эксплуатируется все эти годы. В ней каждое правило имеет форму "продукции”: ЕСЛИ и Р2, и и Рл, ТО F\, и 7*2, и ..., и Fk В дальнейшем на этой основе была разработана предметно-независимая версия EMYCIN (Empty MYCIN), являющаяся экспертной оболочкой, т. е. ’’скелетом” для создания консультационных систем различного назначения. Многие программные средства и решения, впервые реализованные в EMYCIN, при- менялись для создания других оболочек экспертных систем. Интерфейс пользователя в EMYCIN в значительной степени реализован в виде от- дельного компонента — редактора знаний TEIRESIAS. Кроме задачи ввода и коррек- тировки продукционных правил (ЕСЛИ..., ТО...), эта подсистема осуществляет кон- троль непротиворечивости и полноты: проверяет, не противоречит ли вновь вводи- мое правило уже существующим и не является ли оно их следствием. В течение 15 лет компания Сусогр разрабатывает базу знаний Сус (en-Cyc-lopedia, www.cyc.com), на создание которой уже потрачено 50 млн. На сегодня Сус — лучшая в мире экспертно-справочная система, охватывающая все области знаний и способ- ная делать логические выводы. Только в ядро Сус заложен 1 миллион утверждений. Сус состоит из основного ядра, базы знаний, интерфейсной части, подключаемых модулей и языка представления информации CycL, который работает с предикатами второго порядка. Пообщаться с Сус можно через Internet на сайте www.e-cyc.com. Можно также при- обрести сервер Сус Knowledge Server и среду создания порталов знаний е~Сус Portal Toolkit для разработки собственных приложений на основе Сус для UNIX и Windows NT. Фирма Zabaware (www.zabaware.com) предлагает компьютерного секретаря — Win- dows-программу Ultra Hal Assistant, которая представляет собой виртуальный персо- наж, способный после установки на компьютере запоминать различную информа- цию (деловое расписание "начальника”, телефоны, дни рождения, адреса). Этот сек- ретарь может выполнить несложные поручения (послать письмо в назначенное время, напомнить о встрече), подсказать, как правильно работать с Windows, раз- личными офисными программами и даже пошутить, чтобы снять напряжение. Общаться с секретарем можно голосом — в Ultra Hal Assistant встроен блок распо- знавания английской речи; сам он общается с пользователем тоже голосом, а не текстовыми сообщениями. Программа способна поддерживать разговор благодаря наличию большой базы зна- ний и обучаться, анализируя предпочтения хозяина. Более полную информацию по экспертным системам можно найти, например, в [17, 21, 39, 69]. В приложении 3 приведено описание реальной медицинской экспертной системы, написанной на языке Visual Prolog (А. А. Темиров, В. А. Юхтенко, И. Е. Белов: http://www.prolog-soft.com/MedAssistRu/Default.htm).
Основы Пролога Глава 6. Введение в Пролог Глава 7. Примеры решения задач на языке Пролог
ГЛАВА 6 Введение в Пролог В этой части мы рассмотрим логические принципы построения и выполнения про- граммы, записанной на языке логики, или, другими словами, компьютерную интер- претацию логики. Идея логического программирования заключается в использовании компьютера для получения выводов из декларативного описания предметной области. Основой этой идеи является раздел математической логики, получивший название автоматизация доказательства теорем. Другой важной особенностью логического программирования является достаточно простая возможность перехода от высказываний естественного языка к предложени- ям (программе) на ограниченном языке логики предикатов первого порядка. С дру- гой стороны, программа, записанная в виде предложений языка логики, может быть достаточно легко переведена на естественный язык. Любая формула исчисления предикатов первого порядка может быть представлена в виде конъюнкции дизъюнкций положительных или отрицательных литералов: L] v Z,2 v v Lk где каждое Z, представляет собой атомарную формулу Fj (rb t2, ..., tm) или со знаком отрицания Г2, —, {т)- Здесь tj — термы: индивидная константа, переменная или результат применения функции к терму. Например, 0 — обозначение индивидной константы — нуля, X — переменная, обо- значающая некоторое натуральное число или ноль, ДЛ) — функция, имеющая зна- чение, равное Х + 1, ХХХО))) — число 3. Дизъюнкция вида Л] v Л2 v ... v v "| /?| v "I v v "I Вт (6.1) может быть записана в виде импликации (при этом принято посылку импликации писать справа, а заключение — слева от знака операции) А\ v Л2 v ... v Ап <- В[ & В2 & ... & В,„ (6.2)
168 Часть III. Основы Пролога То же самое записывают, опуская знаки логических операций, в виде Ах, А2, А„ <- Вх, В2, ..., Bm (n>Q,m> 0) (6.3) и называют клаузой (или клозом). При этом если клауза содержит переменные Хх, Х2, Хг, то предполагается наличие квантора всеобщности по каждой из этих переменных ЧХхЧХ2 ... УХДЛ, V Л2 V ... V А„ <- В] & В2& ... & В,„) (6.4) То есть: для всех Хх, Х2, ..., Хг выполняется Ах, или А2, или ..., или Ап, если выполне- ны В], и В2, и ..., и Вт. Для того чтобы понять смысл этих формул при п = 0 и т = 0, перепишем формулу в следующем эквивалентном виде, не меняющем ее значения: false v А\ м А2 v ... v Ап <— Вх & В2 & ... & Вт & true (6.5) При т = 0 получим формулу: false v А\ v А2 v ... v Ап <— true (6.6) что эквивалентно дизъюнкции Ах v А2 v ... v Ап (6.7) или с учетом кванторов: VXj, Х2, ..., Xr(Ax v А2 v ... v^) (6.8) При п = 0 получим формулу: false <— В| & В2 & ... & Вт & true (6.9) которая эквивалентна формуле: 1 (В, & В2 & ... & Вт) (6.10) С учетом подразумеваемых кванторов: VX,, Х2, ..., Хг~] (В, & В2 & ... & Вт) (6.11) или 1 ЗХ,, Х2, ..., Хг(Вх & В2 & ... & Вт) (6.12) Такая формула представляет собой утверждение, добавляемое к исходному множест- ву формул (составляющих программу), являющееся отрицанием для формулы: ЗХх, Х2, Хг{Вх &. В2 Вт} (6.13) которое требуется доказать (методом от противного): найти такие значения предмет- ных переменных Хх, Х2, ..., Х„ при которых формула истинна. Метод доказательства от противного заключается в выводе противоречия (обозна- чаемого !J или 1), тождественно ложной формулы, получаемой из формулы (6.5) при п — 0 и т — 0: false true (6.14)
Гпава 6. Введение в Пролог 169 С целью повышения эффективности метода резолюций, используемого в качестве метода автоматизации доказательства теорем, берутся лишь формулы вида (6.3) при п< 1. За основу декларативного описания предметной области принимается специ- альная форма представления в виде фактов и правил. При п = 1 из (6.3) получается формула, называемая клаузом Хорна (или хорновским предложением, хорновским клозом, хорновским дизъюнктом, в англоязычной лите- ратуре используется также название definite clause, которое можно перевести как од- нозначное предложение): А Bi, В2, ..., Вт (6.15) При т>0 эту формулу называют правилом, при т = 0 — фактом (при этом знак им- пликации иногда опускают), (6.16) в случае, когда левая часть (следствие) пуста, т. е. при п = 0, т > 0 — запросом, целью или целевым утверждением. Вх, В2, ..„ Вт (6.17) Множество фактов, правил вместе с запросом представляют собой логическую про- грамму (definite program). Факты задают отношения, имеющие место между объектами данной предметной области. Правила определяют одни отношения через другие. Запрос представляет собой целевое утверждение, которое нужно доказать, исходя из множества фактов и правил программы, процесс доказательства представляет собой выполнение программы. Для примера рассмотрим следующие предложения на естественном языке. "Маше нравятся цветы". (6.18) "Аня любит сына ". (6.19) "Сын Ани любит Машу". (6.20) "Оля любит всех, кого любит Аня". (6.21) "Нам нравится все, что нравится человеку, которого мы любим". (6.22) Эти предложения могут быть записаны следующим образом. Факты: нравится(маша, цветы) (6.23) любит(аня, сын(аня)) (6.24) любит(сын(аня), маша) (6.25) Здесь нравится и любит — имена предикатов, маша, цветы, аня — индивидные кон- станты, сын — имя функции. Замечание ) Обозначения предикатных и функциональных символов, предметных констант и предметных переменных имеют в языке Пролог вид последовательностей латинских
170 Часть III. Основы Пролога символов (возможно с цифрами, начинающимися с символа), при этом предметные константы именуют словами, начинающимися со строчной буквы, а предметные пе- ременные — с прописной. Четвертое предложение (6.21) задает правило: "Для любого X, если любит(аня, X), то из этого следует, что любит(рля, X)". Запишем это правило, используя квантор всеобщности и посылку справа, а следст- вие слева от знака импликации, как это принято в логическом программировании: V Х(любит(рля, X) <- любит(аня, X)) (6.26) В виде дизъюнкта: Х(любит(оля, X) v 1 любит(аня, Л)) (6.27) Снимем квантор, полагая, что квантор всеобщности "по умолчанию" относится ко всем переменным, входящим в формулу (как говорят, формула "универсально замк- нута"): люб)ит(оля. X) v "I любит(аня, X) (6.28) или в виде импликации: любит(оля, X) <- любит(аня, X) (6.29) Пятое предложение (6.22) задает правило: "Для любых X, У, Z, если любит(Х, У) и нравится (У, Z), то из этого следует, что нравится(Х, Z)": \/Xi Yv Х(нравится(Х, Z) <- любит(Х, У) & нравится(У, Z)) (6.30) Перепишем, предполагая кванторы всеобщности по умолчанию: нравится{Х, Z) <- любит(Х,У) & нравится{У, Z) (6.31) Формулы, которые мы получили (6.23)—(6.25), (6.29), (6.31) имеют вид фактов и правил вида: Л) <- F\ & F> & ... & F„ (6.32) где Fj при i > 0, атомарные формулы, в которых все входящие в них переменные, если они есть, находятся в области действия квантора всеобщности или, другими словами, универсально замкнуты. При п > 0 получим правша, при п = 0 получим Fo <- или просто Fq, что соответству- ет факту. ( Замечание ) Формула Fo<- true получается при п = 0 из правила Fq <- F\ & F2 & ... & Fn & true, эквивалентна формуле"]true v Fq false v fl;» Jo- Таким образом, получится следующее множество фактов и правил: нравится(маша, цветы) <- (6.33) нрав11тся(Х, Z) <- любит(Х, У), нравится{У, Z) (6.34) любит(аня, сын(аня)) <- (6.35)
Гпава 6. Введение в Пролог 171 любит(,сын(,аня), маша) <— любит(оля, X) «- любит{аня, X) (6.36) (6.37) Запросами (целевыми клаузами) к множеству полученных предложений (клауз) мо- гут быть, например: < - нравится(сын(аня), цветы) (6.38) < — любит(аня, маша) (6.39) < - нравится(Х, цветы) (6.40) «- любит^оля, X (6-41) «- любит{Х, маша), нравится{Х, цветы) (6.42) < — нравится(аня, цветы) (6.43) Запрос Z1 (6.38) логически эквивалентен формуле: 1 нравитсяЯсынЯаня), цветы), (6.44) "сыну Ани не нравятся цветы", которая добавляется к множеству фактов и правил программы (6.33)—(6.37). Если формула 1 Z1: нравится{сын(аня), цветы), (6.45) "сыну Ани нравятся цветы" логически следует из (6.33)—(6.37), то множество фор- мул (6.33)—(6.38) должно быть несовместно (противоречиво). Первый, второй и шестой запросы (6.38), (6.39), (6.43) подразумевают ответ "да" или "нет". В третьем, четвертом и пятом запросах (6.40)—(6.42) требуется найти значение предметной переменной X, при котором будут выполнены соответствующие отно- шения. В третьем требуется найти того (тех), кто любит цветы, в пятом — тех, кого любит Оля. В пятом запросе нужно найти, если существует, такого индивидуума X (или таких), который любит (которые любят) Машу и которому (которым) нравятся цветы. Рассмотрим механизм логического вывода, являющийся основой большинства сис- тем логического программирования. Рассматриваемый здесь метод, носящий назва- ние SLD-резолюции, оказался наиболее подходящим правилом вывода с точки зрения его реализации. Выполнение логической программы (6.33)—(6.38) представляет собой проверку за- проса: <— нравится(сын(аня), цветы) (6.46) или доказательство того, что из клауз (6.33)—(6.37), составляющих факты и правила Р логической программы, следует 1 Z1: P=>1Z1 или что множество клауз Р u Z1 противоречиво. Осуществим такое доказательство.
/72 Часть III. Основы Пролога Z1 о <- нравится(сын(аня), цветы) о 1 нравится{сын(аня), цветы) нравится(Х, Z) <- любит(Х, Y), нравится(¥, Z) о "I нравится(Х, Z) -> 1 (любит(Х, У) & нравится(У, Z)) о \/XrfYr/Z(\ нравится(Х, Z) -> "I (любит(Х, У) & нравится(¥, Z))). По правилам вывода исчисления предикатов первого порядка, правилу подстановки при подстановке X = сын(аня) и Z = цветы: VAVyVZ(l нравится(Х, Z) -> 1 (любит(Х, У) & нравится{У, Z))) 1 нравится(сын)аня), цветы) —> ~\ (любит(сын(аня), У) & нравится(¥, цветы)) По правилу modus ponens выводим: 1 нравится(сын(аня), цветы) 1 нравится(сын(аня), цветы) -> 1 (любит(сын(аня), У) & нравится(¥. цветы)) "I (любит(сын(аня), У) & нравится(У, цветы)) и получаем новый запрос, который эквивалентен тому, что: 1 ВУ(любит(сын(аня), У) & нравится(¥. цветы)), не существует такого индивидуума, которого любит сын Ани и которому, в свою оче- редь, нравятся цветы. 1 Э¥(любит(сын(аня), У) & нравится(¥. цветы)) о VK"I (любит(сын(аня), У) & нравится{¥, цветы)) о 1 любит(сын)аня), У) v 1 нравится(У, цветы)) о любит(сын(аня), У) -> "I нравится{¥, цветы)). Унифицируя левую часть импликации при подстановке Y= маша с (6.37) и снова используя правило заключения, получим: любит(сын(аня), маша); любит)сын(аня). У) -> 1 нравится(У, цветы)) 1 нравится(маша, цветы) Таким образом, получаем новый запрос: 1 нравится(маша, цветы) о <— нравится(маша, цветы) о нравится(маша, цветы) -> false По правилу заключения, используя (6.33), получаем противоречие: нравится(маша, цветы); нравится(маша, цветы) -> false false Если рассматривать этот процесс менее формально, то он выглядит следующим об- разом. По запросу ищется самое первое правило или факт, которые могут быть унифици- рованы с запросом. Запрос <- нравится(сын(аня), цветы)
Гпава 6. Введение в Пролог 173 может быть унифицирован с правилом нравится(Х. Z) <- любит(Х, Y), нравится^ Y, Z) при подстановке Х= сын(аня) и Z= цветы, в результате чего получается новый за- прос: <- любит(сын(аня), Y), нравится^, цветы), который, в свою очередь, может быть унифицирован с фактом: любит(сын(аня), маша) <- При подстановке Y = маша получится новый запрос: <— нравится(маша, цветы), который дает противоречие с фактом: нравится(маша, цветы) <— . Процесс, называемый опровержением, изображают в виде дерева (рис. 6.1). Противо- речие обозначено знаком „. Ответом на данный запрос будет "да", хотя формально запрос представлен в виде отрицания того утверждения, которое должно быть выведено: "сыну Ани нравятся цветы". <- нравится(сын(аня), цветы) X сын(аня) Нравится(Х, 2) любит(Х, Y), нравится^ Y, Z) Z= цветы <— любит(сын(аня), Y), HpaeumcH(Y, цветы) Y= маша любит(сын(аня), маша) <— <— нравится(маша, цветы) нравится(маша, цветы) <— □ Рис. 6.1. Дерево "опровержения", реализующее первый запрос: "Нравятся ли сыну Ани цветы?"
174 Часть III. Основы Пролога любит(аня, сын(аня)) <- X = сын(аня) □ Рис. 6.2. Дерево, реализующее ответ на запрос: "Кого любит Оля?" <— любит(Х, маша), нравится(Х, цветы) любит(сын(аня), маша) <— X = сын(аня) <— нравится(сын)аня), цветы) нравится(Х, Z) <- любит(Х, Y), нравится(У, Z) X = сын(аня) Z= цветы любит(сын(аня), У), нравится(У, цветы) У= маша <— нравится(маша. цветы) □ любит(сын(аня), маша) <- нравится(маша, цветы) Рис. 6.3. Дерево для пятого запроса: определение того, кто любит Машу и кому нравятся цветы
Гпава 6. Введение в Пролог 175 <- нравится(аня, цветы) X = аня Z = цветы нравится(Х, Z) <- любит(Х, У), нравится^ Y, Z) <-любит(аня, Y), нравшпся(У, цветы) <- любит(сын(аня), Y), нравится^ Y, цветы) нравится{маша, цветы) □ Рис. 6.4. Дерево "опровержения" для шестого запроса: нравятся ли Ане цветы? Ответ на запрос Z2 (6.39) будет "нет", т. к. в программе (среди фактов и правил (6.33)—(6.37) нет ни одного факта или правила, которое бы унифицировалось с за- просом любит{аня, маша). Ответ на запрос Z3 (6.40) будет Х= маша, т. к. запрос <- нравится(Х, цветы) унифи- цируется с фактом нравится(маша, цветы) <— . Мы знаем из первого запроса, что, кроме Маши, цветы любит и сын Ани, но чтобы получить из программы всех любящих цветы, а не одного, "первого встретившегося”, нужно запрос усовершенствовать, о чем будет рассказано позже. Ответ на запрос ZA (6.41) будет X = сын(лня), дерево логического вывода приведено на рис. 6.2.
176 Часть III. Основы Пролога Ответом на запрос Z5 (6.42) будет Х= сын(аня), часть дерева вывода после сведения вывода к запросу <- нравится(сын(аня), цветы) совпадает с деревом вывода запроса Z1 (рис. 6.3). Последний запрос Z6 (6.43) также сводится к Z1, дерево вывода показано на рис. 6.4. Пример, который мы рассмотрели, по существу, является базой данных и знаний с соответствующими запросами. Особенностью такой дедуктивной базы является то, что имеется множество фактов, которые явно не присутствуют в базе данных, но могут быть логически выведены из знаний, представленных в виде правил Пролога. В следующей главе, в качестве первого примера программы на Прологе, рассмотрим только что разобранную задачу.
ГЛАВА 7 Примеры решения задач на языке Пролог Программированию на языке Пролог посвящена обширная литература [5, 11, 13, 23—25, 27, 28, 32, 40, 42, 57, 76, 86]. В нашей стране первые серьезные публикации по логическому программированию и языку Пролог появились в 1986—1988 гг. [1, 10, 20]. Начиная со следующей главы, читатель окунется в среду и мир программирования на современной реализации Пролога — Visual Prolog. Здесь же рассмотрим некото- рые достаточно простые примеры.1 Не будем утверждать, что Пролог самый лучший язык программирования. Постара- емся рассмотреть этот язык, исходя из ответа на вопрос о том, какой язык програм- мирования лучше: тот, который ближе к естественному и позволяет формулировать постановку задачи, а не описывать алгоритм ее решения. Хотя и описание алгоритма не возбраняется, Пролог объединяет два подхода: логический и процедурный [17, 19, 21, 38, 68, 69]. Язык идеально приспособлен для создания баз данных и знаний, экспертных систем, основанных на этих базах [1, 10, И, 21, 23, 27, 28, 38, 40, 69, 76, 79, 84, 86, 89]. Итак, необходимо описать постановку задачи на естественном языке. Причем же здесь все-таки логика? Любой естественный язык обладает неоднозначностью и множеством других недостатков, делающих его неудобным для постановки задачи компьютеру. Удобным для этой цели оказался формализм исчисления предикатов первого порядка, с помощью которого "можно описать почти все, что относится к математике и очень многое из того, что выражено на естественном языке" [58]. В основе Пролога, как мы знаем, лежит представление задачи с использованием специальной формы языка исчисления предикатов первого порядка, с помощью клауз Хорна (предложений хорновского типа). Программа на Прологе включает в себя постановку задачи в виде множества формул хорновского типа (раздел CLAUSES) и описание цели (GOAL), — формулировку тео- ремы, которую нужно доказать, исходя из множества правил и фактов, содержащих- ся в этой постановке. Процесс поиска доказательства на основе метода линейной резолюции и составляет суть выполнения программы на Прологе. См. также приложение 2.
178 Часть III. Основы Пролога ( Замечание ) Предшественником и ближайшим "родственником" Visual Prolog является наиболее известная и популярная реализация Пролога, язык Turbo Prolog (после образования фирмы PDC следующие версии Turbo Prolog назывались PDC Prolog, развитием ко- торых и стал Visual Prolog), про который 15 лет назад писали следующее. "В на- стоящий момент на рынке программного обеспечения появилось несколько реали- заций Пролога, но они не идут ни в какое сравнение с Turbo Prolog компании Borland International, характеризующимся высокой скоростью работы и низкой себестои- мостью. Хотя официального стандарта Пролога не существует, в этой книге приве- ден тот его вариант, который считается неофициальным стандартом ... он является очень современным, полноценным и гибким языком программирования" [23]. Рассмотрим в качестве первого примера задачу, анализ решения которой на языке логики предикатов и на основе метода линейной резолюции рассматривался в пре- дыдущей главе. Первая строка программы — комментарий. /*Кто что и кого любит*/ В разделе программы domains описываются области данных для предикатных пере- менных. В данном случае предикатная переменная Name может принимать одно из перечисленных имен или значение функции son (Name). DOMAINS Name=anna;masha;olya;flowers;son(Name) В разделе программы predicates представлены все далее применяемые предикаты. Так как имена предикатов могут использовать только латинские буквы, то предика- ты нравится и любит (6.23)—(6.31) переведены на английский язык. Предикат window здесь служит для организации окна вывода, предикаты Zl, Z2, ..., Z6 соответствуют целевым клаузам (запросам) (6.38)—(6.43). PREDICATES like(Name,Name) love(Name,Name) window Zl Z2 Z3 Z4 Z5 Z6 Описание фактов и правил располагается в разделе clauses и полностью соответст- вует (6.33)—(6.37). CLAUSES like(masha,flowers). like(X,Z) :-love(X,Y) ,like(Y,Z) .
Гпава 7. Примеры решения задач на языке Пролог 179 love(anna,son(anna)). love(son(anna),masha). love(olya,X) :-love (anna,X) . Запрос Zl соответствует формуле (6.38). Его выполнение заключается в следующем: переход на новую строку в окне вывода (nl означает new line), проверка выполнения (истинности) факта like (son(anna), flowers); если это так, то будет выведено со- общение: Z1: да, в противном случае будет выполняться вариант, записанный в следующей строке, в окно вывода будет выведено: Z1: нет. Zl:-nl,like(son(anna),flowers),write("Z1: да"). Z1:-nl,write("Zl: нет"). В данном примере будет выведено: Z1: да, процесс поиска решения изображен на рис. 6.1. Запрос Z2 соответствует (6.39), организован аналогично, только ответом на запрос будет: Z2: нет. Z2:-nl,love(anna,masha),write("Z2: да"). Z2:-nl,write("Z2: нет"). Запрос Z3, соответствующий формуле (6.40), имеет особенность. После того как будет найдено и выведено первое значение х (это будет masha) такое, что выполнен предикат like (X, flowers), из-за fail, который всегда заканчивается неудачей, произойдет откат, т. е. возврат и поиск нового варианта х, удовлетворяющего пре- дикату like (X, flowers). Следующим значением X будет anna, затем son (anna), потом olga. Так как других подходящих значений X больше не найти, то произойдет переход к очередному варианту Z3, располагающемуся в следующей строке. Z3:-nl,write("Z3: "),like(X,flowers),write(X,", "),fail Z3:-write("больше нет") В результате будет выведено: Z3: masha, anna, son(anna), olga, больше нет. Запрос Z4 (формула 6.41) организован аналогично Z3. В данном случае будет най- дено лишь одно значение X. Z4:-nl,write("Z4: "),love(olya,X),write(X,", "),fail. Z4:-write("больше нет"). Будет выведено: Z4: son (anna), больше нет, процесс поиска решения изображен на рис. 6.2. Запрос Z5 соответствует формуле (6.42), дерево поиска вывода, т. е. процесс выпол- нения запроса, изображено на рис. 6.3. Z5:—nl,write("Z5: "),love(X,masha),like(X,flowers), write(X,", "),fail. Z5:-write("больше нет"). Результатом выполнения запроса будет: Z5: son (anna), больше нет.
180 Часть III. Основы Пролога Запрос Z6 аналогичен Z1 и является частью запроса Z3, ответом на запрос будет: Z6: да. Z6:-nl,like(anna,flowers),write("Z6: да"). Z6:-nl,write("Z6: нет"). Предикат window это всего лишь обращение к стандартному предикату (процедуре с параметрами) makewindow, определяющему размеры, заголовок и цвета для окна вывода. window:-makewindow(1,10,9,"Кто что и кого любит",0,0,25,80). В разделе программы goal через запятую записаны предикаты, которые надо после- довательно проверить (выполнить): в данном случае это открытие окна и выполне- ние запросов Z1, ..., Z6, после выполнения каждого из которых происходит переход на новую строку. GOAL window, Zl,nl, Z2,nl,Z3,nl,Z4,nl,Z5,nl,Z6. Рассмотрим другой пример, предложенный в книге [58] еще до изобретения Проло- га, в качестве упражнения по автоматизации логического вывода методом резо- люций. Формулировка задачи Тони, Майкл и Джон — члены альпинклуба. Каждый член альпинклуба или горно- лыжник, или скалолаз, или и то и другое. Никто из скалолазов не любит дождь. Все горнолыжники любят снег. Майкл любит все, что не любит Тони, и не любит все, что любит Тони. Тони любит снег и дождь. Есть ли член альпинклуба, который является скалолазом и не является горнолыжником. И кто он? Для интерпретации формул исчисления предикатов требуется задать области интер- претации предметных переменных, что и делается в разделе DOMAINS. Естественным для этих областей является перечисление значений, которые могут принимать пред- метные переменные Name, Weather, Spec. DOMAINS /*3адание областей данных*/ Name = tony,-mikle;john /* Область Name это или tony, или mikle, или john*/ Weather = rain;snow /* Определение области Wheather */ Spec = skalolaz;gornolyshnik ^Определение области Spec*/ Описание предикатов включает в себя имя предиката и перечисление имен пред- метных переменных — имен областей из раздела domains. Интерпретация предика- тов приведена в комментариях (между знаками /* и */). PREDICATES /*Определение предикатов*/ club(Name)/* Name является членом клуба*/ nlike(Name, Weather) /* Name не нравится Wheather*/ like(Name, Weather) /* Name нравится Wheather*/ is(Name, Spec) /* Name по специализации Spec */ question /* Теорема, которую нужно доказать*/
Гпава 7. Примеры решения задач на языке Пролог 181 Факты и правила. CLAUSES /*Эти факты перечисляют всех членов клуба.*/ club (tony), club(mikle). club(john). /*Эти факты описывают то, что любят члены клуба.*/ like(tony, snow), like(tony, rain), likefjohn, snow). Правило, определяющее, что из того, что tony не любит х, следует, что это любит mikle. Или ПО другому. Майкл любит все то, что не любит Тони. like (mikle, X):— not (like (tony, X)). Для того чтобы определить, что Майкл не любит все то, что любит Тони, надо опре- делить сначала предикат "не любит" (nlike) через предикат "любит" (like) и опера- цию отрицания (not). nlike(X, Y)not(like(X, Y) ) . nlike(mikle, Y)like(tony, Y). Определение того, что некто Xявляется горнолыжником: is(X, gornolyshnik)club(X), like(X, rain). Определение того, что некто Xявляется скалолазом: is(X, skalolaz)club(X), not(like(X, snow)). Определение question содержит встроенные предикаты (readchar, nl, cursor, write, fail), организующие запрос и выводящие результат. При этом первое определение выясняет Name того, кто является скалолазом (skalolaz) и не является горнолыжни- ком (gornolyshnik). Предикат "неудачи" — fail, заставляет перейти к следующему определению question. question:— readchar(_) , nl, is(Name, skalolaz), not(is(Name, gornolyshnik)), cursor(10, 20), write("Скалолаз, но не горнолыжник: ”), write(Name), readchar(_), nl, fail. Определение question для того, кто является и скалолазом, и горнолыжником. question:— nl, cursor(12, 20), writefH скалолаз, и горнолыжник: "), is(Name, skalolaz), is(Name, gornolyshnik), write(Name), readchar(_), nl, fail. Определение question для определения специализации Тони: question:— nl, is(tony, Spec), cursor(14, 20), write("tony — ", Spec), readchar(_), nl, fail.
182 Часть III. Основы Пролога Определение question для того, кто является горнолыжником, но не является ска- лолазом: question:— nl, is(Name, gornolyshnik), not(is(Name, skalolaz)), cursor(16, 20), write("He скалолаз, но горнолыжник: "), write(Name), readchar(_), nl, fail. Определение, завершающее вывод результатов question: question:— nl, cursor(20, 1), write("ЭТО BCE!"). Раздел программы, в котором определяется запрос, в данном случае question: GOAL question. В этой программе дополнительно к заданию ищутся те, кто является и скалолазом, и горнолыжником, определяется специализация tony, определяется, кто является горнолыжником, но не скалолазом. Процесс доказательства включает в себя как поиск нужных подстановок, которые делают истинными утверждения из question, так и выполнение встроенных преди- катов, по существу, операторов. Например, cursor (NX, NY) устанавливает курсор в позицию NX, ny, write () — выводит текст, nl — переход на новую строку, readchar (_) — ввод произвольного символа, not — встроенный предикат отрицания. Каждое предложение раздела clauses представляет собой формулу вида: D <- А & В & ... & С, где D — левая часть предложения, символ :- вместо стрелки, А, В, ..., С — формулы правой части, в программе разделены запятой. Например, предложение: is(X, gornolyshnik) :- club(X), like(X, rain). представляет собой формулу: club(X) & like (X, rain) —> is(X, gornolyshnik) с той разницей, что в логической формуле порядок следования элементов конъюнк- ции не имеет значения, а в программе он определяет последовательность процесса доказательства по методу резолюций и в некоторых случаях может иметь очень важ- ное значение. Встроенный предикат fail — всегда заканчивается неудачно и является механизмом для осуществления перебора: после попытки доказательства первого question про- изойдет переход к доказательству второго, затем третьего варианта вопроса. В качестве второго примера рассмотрим такую задачу [20]. Задача Король думает, что королева думает, что она не в своем уме. В своем ли уме король? О
Гпава 7. Примеры решения задач на языке Пролог 183 Решим эту задачу, размышляя следующим образом. Предполагается, что тот (и только тот), кто "в своем уме" адекватно воспринима- ет действительность, т. е. думает так, как есть на самом деле. В отличие от того, кто "не в своем уме". Если королева в своем уме, то она так и думает, если коро- лева не в своем уме, то она должна думать, что она в своем уме; т. е. она незави- симо от своего состояния всегда думает одинаково. Следовательно, король думает не то, что есть на самом деле, следовательно, он не может быть в своем уме. Программа осуществит проверку логической непротиворечивости различных вариан- тов значений умственных способностей короля и королевы и придет к выводу, что в любом случае (независимо от королевы) король не в своем уме (листинг 7.1). , " .............................................................;.......... | Листинг ТЛ У ПрЬграмМ^решающая задачу про умственные способности короля DOMAINS /*Описание областей данных*/ Swoystwo = wume;newume /* Swoystwo может принимать значение wume или newume*/ Mysly - imeet_mesto(Swoystwo, Swoystwo);dumaet(Swoystwo, Mysly) /* Mysly — это то, о чем можно думать */ PREDICATES /*Описание предикатов*/ mogetbyty(Swoystwo) /*mogetbyty — свойство, принимающее значение из области Swoystwo */ imeet_mesto(Swoystwo, Swoystwo) dumaet(Swoystwo, Mysly) wopros CLAUSES /*Описание предметной области*/ mogetbyty(wume). mogetbyty(newume). /* можно быть или "в своем уме” или "не в своем уме" */ dumaet(wume, imeet_mesto(X, Y)):—imeet_mesto(X, Y). /* "в своем уме” думает то, что есть на самом деле*/ dumaet(newume, imeet_mesto(X, Y)):—not(imeet_mesto(X, Y)). /*"не в своем уме" думает то, чего нет на самом деле*/ dumaet(wume, dumaet(X, imeet_mesto(Y, Z)));—dumaet(X, imeet_mesto(Y, Z)). /*"в своем уме" думает то, что есть на самом деле*/ dumaet(newume, dumaet(X, imeet_mesto(Y, Z))) not(dumaet(X, imeet_mesto(Y, Z))). /*"не в своем уме" думает то, чего нет на самом деле*/ imeet_mesto(X, X). /* на самом деле есть то, что есть*/ wopros:-mogetbyty(KL), mogetbyty(KWA), /♦write("Король — ", KL, " Королева — ", KWA), */ dumaet(KL, dumaet(KWA, imeet_mesto(KWA, newume))), nl, write("Королева — ", KWA, " король — ", KL), nl, fail. /♦fail заставляет программу работать не до первого успеха, а просмотреть все варианты*/
184 Часть III, Основы Пролога GOAL /*3апрос или теорема, которую нужно доказать*/ makewindow(1, 11, 21, "Про глупого короля”, 5, 0, 10, 79) /♦Создание окна для вывода результата*/, wopros /*Найти непротиворечивые значения для переменных KL(обозначение короля) и KWA (обозначение королевы)*/ Следующая задача [20] гораздо труднее для естественного интеллекта. Задача "Тройка" думает, что "Туз" не в своем уме. "Четверка" думает, что "Тройка" и "Двойка" обе не могут быть не в своем уме. "Пятерка" думает, что "Туз" и "Чет- верка" либо оба не в своем уме, либо оба в своем уме. "Шестерка" думает, что "Туз" и "Двойка" оба в своем уме. "Семерка" думает, что "Пятерка” не в своем уме. "Ва- лет" думает, что "Шестерка" и “Семерка" обе не могут быть не в своем уме. В сво- ем ли уме "Валет "? Запрограммируем задачу следующим образом (листинг 7.2). Листинг 7.2. Программа, решающая задачу про умственные способности "Валета" DOMAINS S = symbol М = is (S, S) ;d(S, M) PREDICATES mb(S) is(S, S) d(S, M) di(S, S, s) d2(S, S, S) d3(s, s, s> d4(S, S, S) q CLAUSES mb (wume) . mb(newume). dfwume, is (X, Y) ) :— is(X, Y) . d(newume, is(X, Y)):—not(is(X, Y)). dfwume, d(X, is(Y, Z) ) ) : —d(X, is(Y, Z)) . dfnewume, d(X, is(Y, Z))):—not(d(X, is(Y, Z))). is (X, X) . /♦Дополнительные определения dl, d2, d3, d4 введены для облегчения записи условия*/ dl(wume, S2, S3):-is(S2, wume);is(S3, wume). /* "точка с запятой"; означает логическое "или" */ dl(newume, S2, S3):—is(S2, newume), is(S3, newume). d2(wume, S2, S3):-d3(wume, S2, S3);d4(wume, S2, S3). d2(newume, S2, S3):-is(wume, S2), is(newume, S3);is(newume, S2), is(wume, S3).
Гпава 7. Примеры решения задач на языке Пролог 185 d3(wume, S2, S3):—is(S2, newume), is(S3, newume). d3(newume, S2, S3):—is(S2, wume);is(S3, wume). d4(wume, S2, S3):—is(S2, wume), is(S3, wume). d4(newume, S2, S3):—is(S2, newume);is(S3, newume). /^Обозначения карт: K2 — двойка, ..., К7 — семерка, KJ — валет, КА — туз*/ q:-mb(K2), mb(K3), mb(K4), mb(K5), mb(K6), mb(K7), mb(KJ), mb(KA), d(K3, is(КА, newume)), dl(K4, КЗ, K2), d2(K5, КА, К4), d4(Кб, КА, К2), d(K7, is(K5, newume)), dl(KJ, Кб, K7), write("Валет:", KJ), nl, fail. GOAL q Рассмотрим ставшую уже классической задачу [30] в качестве примера поиска пути в пространстве состояний (см. аналогичную задачу: пример 2 о раскрое материала из гл. 1). Задача Имеется два ведра, в которых помещается ровно 7 литров и ровно 5 литров, можно переливать из ведра в ведро, выливать воду из ведер, набирать неограниченное число раз (третьего ведра нет, воды сколько угодно). Нужно получить ровно 4 литра в од- ном из ведер. ... Если обозначить предикатом S(X, Y) состояние, при котором в семилитровом ведре ровно 1,ав пятилитровом ровно У, то алгоритм решения задачи (последователь- ность заполнения ведер) будет такой: ДО, 0), Д7, 0), Д2, 5), Д2, 0), ДО, 2), Д7, 2), Д4, 5), Д4, 0). Программа, которая решает эту задачу, представлена в листинге 7.3. Листинг 7.3. Программа про два ведра: поиск пути в пространстве состояний PREDICATES s(INTEGER, INTEGER) /* s(X, Y) состояние, при котором в семилитровом ведре ровно X литров, в пятилитровом ровно Y литров*/ CLAUSES з(0, 0):- nl, write(" НАЧАЛЬНОЕ СОСТОЯНИЕ: ОБА ВЕДРА- ПУСТЫЕ ", "S(0, 0)"), !. 3(7, X) :—з(0, X) , ! , write("\n* НАПОЛНИТЬ 7-ЛИТРОВОЕ ВЕДРО, S(7, ", X, ")"). s(0, X): — Х<6, /*НАЛИТЬ в 5—ЛИТРОВОЕ МОЖНО НЕ БОЛЬШЕ 5*/ s (X, 0), !, nl, write(" ПЕРЕЛИТЬ ИЗ 7-ЛИТРОВОГО В 5-ЛИТРОВОЕ", X, " ЛИТРОВ, "), write("S(0, ", X, ")"). з(Х, 0) : — s (X, 5), 1, nl, writer ОПУСТОШИТЬ 5-ЛИТРОВОЕ ВЕДРО, S(", X, ", 0)").
186 Часть III. Основы Пролога з(Х, 5).— Х>1, ЛЕСЛИ ОТЛИВАТЬ ИЗ 7-ЛИТРОВОГО В 5-ЛИТРОВОЕ ДО 5 ЛИТРОВ, ТО В НЕМ ОСТАНЕТСЯ НЕ МЕНЬШЕ 2-Х ЛИТРОВ */ Y = X—2, /*Y = 5—(7—X), X = 7—(5—Y)*/ s(7, Y), !, nl, write(" ОТЛИТЬ ИЗ 7-ЛИТРОВОГО В 5-ЛИТРОВОЕ ДО 5 ЛИТРОВ, "), write("ОСТАТОК - ", X, "ЛИТРОВ, S(", X, ", 5)"). s(X, Y) .— Z = 5+Х, з (Z, Y) , ! , П1, write(" (Получить X литров в 7—литровом ведре то же самое, что получить 5+Х литров, S (”, Z, ”, ", Y, . GOAL S(4, 0) . Для того чтобы получить ровно по пять литров в каждом из ведер, достаточно лишь записать в раздел goal новую цель: 5(5, 5). На Прологе, хотя это не является основной областью его использования, с успехом, применяя процедурную семантику, могут решаться и обычные алгоритмические за- дачи, требующие вычислений. При этом каждый из предикатов рассматривается как процедура, переменные, входящие в предикаты, — как формальные параметры, опи- сание предикатов, — как последовательность вызовов процедур, которые необходи- мо осуществить для выполнения данной. В качестве простого примера приведем программу решения квадратного уравнения (листинг 7.4). Листинг 7.4. Программа решения квадратного уравнения Я % Решение квадратного уравнения а*х*х+Ь*х+с = 0 PREDICATES zapros % Ввод исх. данных, вызов основной процедуры povtor % Нужен для организации повторения запроса kvur(real, real, real) % kvur(А, В, С) — решение для а=А, Ь = В, с=С CLAUSES kvur (А, В, С):-В*В-4*А*С>0, XI = (-B+sqrt(В*В-4*А*С))/2/А, Х2 = (-B-sqrt(В*В-4*А*С))/2/А, write("xl = ”), write(XI), write(" х2 = "), write(Х2), nl. kvur(А, В, С):-В*В-4*А*С = 0, X = (-В) /2/А, write("X = "), write(X), nl. kvur(А, В, С):-В*В-4*А*С<0, write("Корней нет.").
Гпава 7. Примеры решения задач на ямке Пролог 187 zapros: - makewindow(l, 23, 28, "Введите коэффициенты кв. ур. а*х*х+Ь*х+с = 0 ", 0, 0, 25, 80), povtor, write("a = "), readreal(A), А<>0, nl, write("b = "), readreal(B), nl, write("c = "), readreal(C), nl, kvur(A, В, C), write("Для выхода нажмите символ q, продолжение — любая клавиша"), nl, readchar (S) ,. S = 'q', I. povtor. povtor:-povtor. GOAL zapros В алгоритмических языках программирования часто приходится использовать струк- туры данных в виде списков. Правда, организация таких списков — удел достаточно опытных программистов. Рассмотрим примеры использования списков на Прологе. Ниже приведена учебная программа (листинг 7.5), содержащая функции работы со списками: добавление элементов в начало или в конец списка, удаление элемента из списка, удаление всех элементов списка, сортировка элементов списка, построение списка в обратном порядке. Листинг 7.5. Программа, реализующая функции для работы со списком DOMAINS list = string* order = inc; dec PREDICATES addfront (list, string, list) % Добавить элемент в начало списка addend(list, string, list) % Добавить элемент в конец списка addsort(list, string, list, order) % Добавить элемент в порядке сортировки delete(list, string, list) % Удалить элемент из списка sort(list, list, order) % Отсортировать список в порядке order strge(string, string)% Определение порядка: больше или равно writelist(list) % Вывести список reverse(list, list) % Получить список в обратном порядке runmenu % Целевой предикат, организация окон, меню... main(list)% Получение списка oper(list, integer)
188 Часть III. Основы Пролога % Выполнение с данным списком операции с данным номером readitem(string)% Ввод элемента dosort(list, integer) % Определение типа сортировки CLAUSES addfront(LI, S, [S|L1]). addend( [], S, [S] ) . addend([HILI], S, [H|L2]) addend(LI, S, L2). addsort ([], S, [S], _) . addsort([HIT], S, [S|[HIT]], inc) :-strge(H, S) , I. addsort([H|T], S, [SI[H|T]], dec) :-strge(S, H), I. addsort([HILI], S, [HIL2], 0) addsort(LI, S, L2, 0). delete ( [SIT], S, T). delete([HILI], S, [H|L2]) :-delete(Ll, S, L2). strge(G, L) G>L. strge(G, L) G = L. sort ( [] , [],_). sort([H|L], L2, 0) sort(L, LI, 0), addsort(LI, H, L2, 0). writelist([]) write('.'). writelist([S]) write(S, '.')• writelist([H|T]) write(H, ", "), writelist(T). reverse([], []). reverse ([HIT], L) reverse(T, Tl), addend(Tl, H, L). /*В меню перечислены возможности этой программы*/ runmenu makewindow(3, 12, 12, " List tools ", 0, 0, 25, 80), makewindow(1, 30, 31, " Current list ", 2, 2, 6, 76), makewindow(2, 30, 31, " Menu ", 9, 20, 10, 40), write (" 0. ExitXn"), write (" 1. Добавить элемент списка в начало\п"), write(" 2. Добавить элемент списка в конецХп"), write(" 3. Удалить элемент спискаХп"), write(" 4. Удалить все элементы спискаХп"), write(" 5. Отсортировать элементы спискаХп"), write (" 6. Построить список в обратном порядкеХп"), write(" Ваш выбор:"), main ( []) . /*Предикат main(L) читает номер пункта меню и запускает соответствующую операцию работы со списком oper(L, С)*/ main(L) gotowindow(1), clearwindow, writelist(L), gotowindow(2),
Гпава 7. Примеры решения задач на языке Пролог 189 readchar(С), oper(L, С) . oper(L, '0') oper(L, '1') oper(L, '2') oper(L, '3') oper (L, ' 4 ' ) oper (L, ' 5' ) makewindow(4, — exit. — readitem(I), addfront(L, I, Li), main(Li). — readitem(I), addend(L, I, LI), main(LI). — readitem(I), delete(L, I, LI), main(Li). - main( [] ) . 81, 81, "Выберите направление сортировки", 13, 25, 4, 30) write(" 1. По возрастаникДп"), write(" 2. По убыванию"), readchar(С), dosort(L, С). oper(L, '6') reverse(L, LI), main(Ll). oper(L, _) main(L) . readitem(I) makewindow(4, 30, 30, "Введите элемент списка", 20, 2, 3, 76), readln(I), removewindow. dosort(L, '!') removewindow, sort(L, LI, inc), main(Ll). dosort(L, '2') removewindow, sort(L, LI, dec), main(LI). dosort(L, _) beep, removewindow, main(L). GOAL runmenu На основе использования списков решается, например, задача Леонарда Эйлера о Кенигсбергских мостах [20]. Задача Пройти по всем мостам города, не проходя дважды по одному и тому же мосту; в более строгой формулировке: найти путь, проходящий по разу по всем дугам графа; или в "детской" формулировке: нарисовать домик, не отрывая карандаша от бумаги и проводя каждую линию только один раз. ... Листинг 7.6. Программа, решающая задачу о Кенигсбергских мостах (вариант 1) DOMAINS Duga = symbol % Имя дуги LIST = Duga* % Список дуг Wershina, Dlina = integer PREDICATES way(Wershina, LIST) /* путь, начинающийся в данной вершине и проходящий по разу по всем дугам списка */
190 Часть III. Основы Пролога output(LIST) % Вывод списка length(LIST, Dlina) % Длина списка (пути) notelement(Duga, LIST) % Проверка того, что дуга не принадлежит списку duga(Duga, Wershina, Wershina) % дуга, соединяющая вершины perebor(Wershina) % решение задачи, все пути из данной вершины CLAUSES % Вывод элементов списка: output( [ ] ) . output([Н|Т]output(Т), write(H, "—>"). % Определение длины списка: length( [], 0) . length([HIT], N):—length(Т, M), N = М+1. % He быть элементом списка дуг: notelement (I, []) . notelement (I, [FIT]) IoF, notelement (I, T) . % Описание графа ("домик"): duga(a, 1, 2) . % По дуге а можно перейти из вершины 1 в вершину 2 duga(а, 2, 1). % По дуге а можно перейти из вершины 2 в вершину 1 duga(b, 2, 3). duga (b, 3, 2).% 5 duga(c, 3, 5). duga(c, 5, 3) .% d c duga (d, 2, 5). duga (d, 5, 2).% 2 b 3 duga(e, 2, 4). duga(e, 4, 2).% a f duga(f, 3, 4). duga(f, 4, 3).% 1 h 4 duga(g, 3, 1). duga(g, 1, 3). duga(h, 1, 4). duga(h, 4, 1). % Путь длины 8, проходящий no разу no всем дугам графа. way(X, L):—length(L, 8), output(L). way(X, L):—duga(D, X, Y), notelement(D, L), way(Y, [D|L]). % Все варианты решения задачи для заданной вершины. perebor(X):—way(X, []), nl, readchar(_), fail. perebor(_):—nl, write("Других путей нет"). GOAL makewindowfl, 23, 32, 0, 0, 24, 79), clearwindow, write("Введите начальную вершину (номер от 1 до 5) "), readint(X), perebor(X). Следующая программа — другой вариант решения той же задачи, кроме того, в лис- тинге 7.7 предусмотрена возможность ввода начальной, конечной вершины и длины пути, — количества дуг, ведущих из начальной вершины в конечную. Листинг 7.7. Программа, решающая задачу о Кенигсбергских мостах (вариант 2) .....3 /* Нахождение путей графа, задаваемой длины, ведущих из задаваемой начальной вершины в задаваемую конечную вершину */ DOMAINS Arc Name = Char
Гпава 7. Примеры решения задач на языке Пролог 191 Arc_List = Arc_Name* Peak = Integer PREDICATES arch(Arc_Name, Peak, Peak) addend(Arc_List, Arc_Name, Arc_List) el_arclist(Arc_List, Arc_Name) find_next(Peak, Peak, Arc_List, Arc_List) question(Peak, Peak) length(Arc_List, Integer) picture run CLAUSES /* Дуги графа: дуга ’а’ между ребрами 1 и 2 или 2 и 1, ... */ arch('a', 1, 2) . arch('а', 2, 1) . arch('b' , 2, 3) . . archf'b', 3, 2) arch('c', 3, 5) . arch('с', 5, 3) . arch('d' , 2, 5) . . arch('d', 5, 2) arch('e', 2, 4) • archСе', 4, 2) . arch('f' , 3, 4) . . arch('f', 4, 3) arch('g', 3, 1) • arch('д', 1, 3) . arch('h' , 1, 4) . . arch('h', 4, 1) /* Добавление дуги в список дуг */ addend([], Arcname, [Arcname]), addend([Head|Tail], Arcname, [HeadITaill]):—addend(Tail, Arcname, Taill). /* Проверка принадлежности дуги списку дуг*/ el_arclist([Head|_], Head):—!. el_arclist([_|Tail], E)el_arclist(Tail, E). /* Нахождение списка дуг EList, ведущих из вершины Begin в вершину End, получаемого добавлением’ дуг в список BList */ find_next(Begin, Begin, BList, BList). find_next(Begin, End, BList, EList)arch(Name, Begin, B), not(el_arclist(BList, Name)), addend(BList, Name, Z), find_next(B, End, Z, EList). /* Запрос на нахождение и вывод на экран списка дуг X, заданной длины L, ведущих из вершины N1 в вершину N2 */ question(Nl, N2):—write("Длина пути из ", N1, " в ", N2, " = "), readint(L) , find_next(Nl, N2, [], X), length(X, L), write(X), readchar(_), nl, fail. question(_, _):—write("Это все!"). /* Вычисление длины списка */ length([], 0). length ([_|Y], N) .-—length (Y, M), N = M+l.
192 Часть III. Основы Пролога /* Запуск программы на выполнение: открытие окна, ввод начальной и конечной вершин */ run:-makewindow(5, $2Е, $20, "Все пути в графе из одной вершины в другую", 0, 0, 25, 80), write("Введите начальную вершину: "), readint(VI), write("Введите конечную вершину: "), readint(V2), question(VI, V2). GOAL run Еще одна программа, демонстрирующая работу со списками (листинг 7.8), в случае ее усовершенствования, может быть полезной: находит маршрут от одной станции до другой с учетом возможной пересадки. "Метро" содержит две линии и одну стан- цию пересадки. Но количество линий и пересадок может быть без труда увеличено. j Листинг 7.8. Нахождение пути с учетом пересадки /* Как проехать в метро? Ввести начальную и конечную станции, получить маршрут */ DOMAINS element = symbol list = element* PREDICATES appendflist, list, list) mline(integer, list) member(element, list) prelist(list, element, list) postlist(list, element, list) deletel(list, list, list) betweenl(element, element, list, list) numb(element, list, integer) change_line(integer, integer, element) ql(element, element) q2 CLAUSES % Проверка принадлежности элемента списку member(Н, [Н|_]). member (Н, [_|Т]):—member(Н, Т) . % К первому списку добавляется второй список, результат в третьем списке append([], L, L) . append) [HILI], L2, [НIL3]) .'-append(LI, L2, L3) .
Гпава 7. Примеры решения задач на языке Пролог 193 % Список элементов L1 до элемента А в списке L prelist(LI, A, L)append(LI, [Al_], L). % Удаление списка L1 из списка L, результат — список L2 deletel(L, LI, L2):-append(LI, L2, L) . deletel(L, LI, L2):-append(L2, LI, L). % Список LI — часть списка L после элемента A postlist(LI, A, L):-prelist(L2, A, L), deletel(L, L2, L3), deletel(L3, [A], LI). % Номер элемента X в списке numb (X, [X , 1) . numb(X, [_|Y], N) : — numb(X, Y, Nl), N=N1+1. % Список ABL — часть списка L между элементами А и В, включая А и В. betweenl(А, В, L, ABL) numb (A, L, Nl) , numb (В, L, N2) , Nl< = N2, prelist(LI, A, L), postlist(L2, В, L), deletel(L, LI, L3), deletel(L3, L2, ABL). between!(A, B, L, BL):— prelist(LI, B, L), postlist(L2, A, L), deletel(L, LI, L3), deletel(L3, L2, BL). % Список станций линии 1, а, Ь, с, р, d, е, f — имена станций mline(l, [a, b, с, р, d, е, f]) % Список станций линии 2, имена станций: g, h, с, р, к, 1, m, п mline (2, [g, h, с, р, к, 1, т, п] ) . % Станцией пересадки с линии 1 на линию 2 является станция р change_line(1, 2, р). % Нахождение маршрута между станциями S1 и S2 ql(Sl, S2):—mline(1, X), nl, write("Станции первой линии: ", X), nl, member(SI, X), member(S2, X), betweenl(SI, S2, X, V), write("Ваш маршрут 1—й линии: ", V). ql(Sl, S2) :—mline(2, X), nl, write("Станции второй линии: ", X), nl, member(SI, X), member(S2, X), betweenl(SI, S2, X, V), write("Ваш маршрут на 2—й линии: ", V). ql(Sl, S2):-mline(1, X), mline(2, Y), change_line(1, 2, S3), nl, write("Пересадка на станции ", S3), nl, member(SI, X), member(S2, Y), betweenl(SI, S3, X, VI), write("ехать по 1-й линии: ", VI), nl, betweenl(S3, S2, Y, V2) , write("ехать no 2—й линии: ", V2) . ql(Sl, S2) :— mline(1, X), mline(2, Y), member(SI, Y), member(S2, X), change_line(1, 2, S3), write("Пересадка на станции ", S3), nl, betweenl(SI, S3, Y, VI), write("ехать no 2—й линии: ", VI), nl, betweenl(S3, S2, X, V2), write("ехать по 1—й линии: ", V2) . % Создание окна, ввод исходных данных, вызов основной процедуры q2:—makewindow(1, 62, 241, "Метро", 0, 0, 25, 80), clearwindow, 7 Зак. 963
194 Часть III. Основы Пролог? write("Введите, откуда ехать?”), readln(Sl), writefKyfla? "), readln(S2), ql(Sl, S2). GOAL q2 На этом завершим краткий обзор примеров решения задач на Прологе. Другие при- меры программ на Прологе вы найдете в приложении 2.
ЧАСТЬ IV Программирование на Visual Prolog Глава 8. Установка и начало работы в Visual Prolog Глава 9. Возможности Visual Prolog Глава 10. Основы языка Visual Prolog Глава 11. Унификация и поиск с возвратом Глава 12. Простые и составные объекты Глава 13. Повтор и рекурсия Глава 14. Списки и рекурсия Глава 15. Внутренняя база фактов Visual Prolog Глава 16. Арифметические вычисления и сравнения Глава 17. Более сложные приемы программирования Глава 18. Классы и объекты Глава 19. Запись, чтение и файлы Глава 20. Обработка строк в Visual Prolog Глава 21. Внешние базы данных в Visual Prolog Глава 22. Программирование на системном уровне Глава 23. Систематический обзор языка Visual Prolog Глава 24. Интерфейс с другими языками
ГЛАВА 8 Установка и начало работы в Visual Prolog Эта глава описывает процесс установки и запуск Visual Prolog на компьютере. Рас- сматриваются некоторые функции интегрированной среды визуальной разработки (VDE, Visual Development Environment) Visual Prolog, позволяющие выполнять пред- ставленные в книге примеры. Мы полагаем, что вы имеете опыт использования GUI (Graphical User Interface, графический интерфейс пользователя) оконных систем. Это могут быть Windows (95/98/Millennium и NT/2000). Если вы новичок и в Прологе, и в ОС MS Windows, возможно, вам будет трудно совмещать изучение языка Пролог со сложностью создания Windows-приложений, обработкой событий и всеми прочими возможностями Windows. Но код в примерах, используемых в книге, не зависит от платформы: они могут выполняться в режиме DOS или в оконной среде, подобной MS Windows, при небольшом увеличении раз- мера кода для реализации пользовательского интерфейса. Системные требования Для запуска визуальной среды разработки необходимы: 23 операционная система Microsoft Windows версии 3.1 или более поздней, Windows for Workgroups, любая 32-разрядная ОС Windows (95/98/МЕ/2000); 21 персональный компьютер, оборудованный, как минимум, микропроцессором Pentium; 2) 32 Мбайт и более оперативной памяти; 21 жесткий диск с 15 Мбайт свободного пространства для варианта минималь- ной установки, и около 200 Мбайт — для полной установки профессиональной версии; 2! дисплей — Super VGA (SVGA) или более высокого разрешения.
198 Часть IV. Программирование на Visual Prolog Процедура инсталляции Процедура инсталляции практически одинакова для всех версий. Поместите диск с дистрибутивом в CD-ROM. В большинстве случаев утилита AUTORUN автоматически инициализирует процедуру инсталляции Visual Prolog: появится окно Internet-браузера, в котором будет отображена страница Visual Prolog. На этой странице нажмите кнопку Install Visual Prolog, тем самым инициализируя утилиту SETUP. Иначе это можно сделать вручную, запустив файл SETUP.EXE из каталога <CDROM>:\INSTALL на прилагаемом CD-диске. Если необходимо инсталлировать Linux-версию Visual Prolog, то обратитесь к подка- талогу <CDROM>:\LINUX. Если на компьютере установлена не одна операционная система, и вы хотите, чтобы Visual Prolog запускался под некоторыми из них, то может потребоваться несколько запусков процедуры инсталляции. Утилита SETUP проведет вас через ряд диалоговых окон настройки опций, каждое из которых имеет кнопки Back (кроме первого) и Next, Cancel и Help. При необхо- димости можно вернуться назад и просмотреть ранее сделанные установки. Возник- шие вопросы или проблемы на этапе инсталляции можно решить, обратившись к интерактивной справке. Окно Space Indicator при помощи гистограммы динамически показывает необходи- мый для инсталляции объем памяти на жестком диске в килобайтах и процентах: если места на диске достаточно — гистограмма зеленого цвета, в противном слу- чае — красного. В начале инсталляции по умолчанию задается некоторый стандарт- ный набор предполагаемых компонентов, и окно индикатора пространства отобра- жает эти начальные установки. Последовательно будут появляться диалоговые окна выбора компонентов с указани- ем требуемого размера дискового пространства для каждого из них. Эти диалоговые окна перечислены ниже. Ни один выбранный компонент не будет установлен до тех пор, пока не закончится процесс выбора. 1. Диалоговое окно Directory. В этом окне нужно выбрать диск и путь, куда будет установлен Visual Prolog. Используйте кнопку Browse для поиска подходящего места в дереве каталогов на диске. Затем измените путь в окне редактирования, чтобы задать пустой каталог. Если какие-либо каталоги в заданном пути не суще- ствуют, они будут созданы. 2. Диалоговое окно Compilers. В этом окне появятся две группы. В первой вы може- те выбрать версию интегрированной среды разработки (VDE). Во второй группе укажите, какой из компиляторов командной строки нужно устанавливать. 3. Диалоговое окно Libraries. В этом окне необходимо указать, какие библиотеки (целевые платформы) должны быть установлены. Выбрать можно 16- и 32-раз- рядный Windows, DOS, PharLap DOS Extended. 4. Диалоговое окно Bindings. В этом окне устанавливают, нужно ли инсталлировать средства поддержки интерфейса визуального программирования (VP1) и примеры VP1. Кроме того, здесь можно указать необходимость инсталляции пакетов, обес- печивающих интерфейс Visual Prolog с: API "родного" 16-разрядного Windows;
Гпава 8. Установка и начало работы в Visual Prolog 199 API переносимых баз данных SQL; API ODBC драйверов и с переносимым ин- терфейсом сокетов, обеспечивающим программирование сетей TCP/IP. 5. Диалоговое окно Utilities. Это окно позволяет выбрать, какие утилиты устанавли- вать. Можно установить: оболочку экспертной системы ESTA; компоненты про- граммирования на разных языках (Mixed Language Programming) и примеры; ис- ходный код интегрированной среды разработки; поддержку Web-программиро- вания и примеры (WWW), а также пакеты DOCTOOL для преобразований между форматами HTML, RTF и 1PF; PIE — исходные коды для интерпретатора Пролог; утилиту UPGRADE, требуемую для обновления проектов, разработанных в Visual Prolog версии 4.x. 6. Диалоговое окно Documentation. Это окно определяет, нужно ли инсталлировать электронные версии (файлы формата MS Word) документации Visual Prolog. Вы можете выбрать установку примеров, используемых в руководствах Visual Prolog. 7. Диалоговое окно Final. Это окно запрашивает, нужно ли создавать группу в Windows для Visual Prolog и ассоциировать интегрированную среду разработки 32-разрядного Windows с файловыми расширениями prj и vpr. Здесь также указы- вается необходимое дисковое пространство для установки выбранных компонен- тов Visual Prolog и доступное дисковое пространство. В этом диалоговом окне расположена кнопка Install. 8. Диалоговое окно Installation in Progress. Это окно имеет кнопку' Cancel, при по- мощи которой можно остановить инсталляцию, но нельзя удалить уже установ- ленные каталоги и файлы. Обратите внимание, что Visual Prolog не устанавливает никаких DLL, не изменяет существовавшие ранее INI-файлы и не изменяет системный реестр (если вы выби- раете опцию: не создавать группу Visual Prolog и не связывать PRJ- и VPR-файлы). Фактически, вы можете удалить Visual Prolog с вашего компьютера, удалив лишь каталог, где он установлен. Программа инсталляции не создает управляющий файл инициализации (vip32_52.ini в каталоге WINDOWS) для интегрированной среды разработки Visual Prolog. Он соз- дается автоматически при первом запуске интегрированной среды разработки. После того как инсталляция закончена, отображается файл, содержащий последнюю информацию. Мы рекомендуем внимательно его прочитать, прежде чем начинать работать с Visual Prolog. Итак, Visual Prolog установлен, и может быть запушен двойным щелчком мыши на пиктограмме V1P. Рекомендации по установке Visual Prolog Для того чтобы запускать и тестировать примеры, описанные в этой части книги, во время инсталляции Visual Prolog необходимо выполнить следующие действия: 1. В диалоговом окне Compilers выберите установку Visual Development Environment (VDE). Мы рекомендуем выбрать версию Win32.
200 Часть IV. Программирование на Visual Prolog 2. В диалоговом окне Libraries отметьте установку библиотек, относящихся к вы- бранной платформе для VDE. 3. В диалоговом окне Documentation выберите установки Answers (Language Tutorial) и Examples (Language Tutorial). Мы рекомендуем также включить установку всех элементов, перечисленных в диалоговом окне Documentation. 4. В диалоговом окне Final рекомендуем выбрать флажок Associate 32-bit VDE with Project File Extensions PRJ & VPR. Запуск Visual Prolog c CD-ROM Полноценная среда разработки Visual Prolog может быть запущена прямо с дистри- бутивного CD-ROM. В этом случае никакой инсталляционный процесс не нужен, и VDE может быть вызван немедленно запуском файла <CDROM>:\RUN\B1N \<OperatingSystem>\\'ip.cxc, где <OperatingSystem> может быть WIN\32 или WIN\16. Обновление версии Visual Prolog В случае обновления предыдущей версии Visual Prolog не следует устанавливать но- вую версию в тот же самый каталог. Если у вас уже есть проекты, разработанные в предыдущей версии Visual Prolog, — внимательно прочитайте разд. "Обновление ваших программ в Visual Prolog" в интер- активной справке. Если есть проекты, разработанные в Visual Prolog v.4.x, вам, кроме того, придется запустить утилиту UPGRADE, чтобы привести файлы <project>.VPR к правильной структуре и изменить исходные файлы проекта. Утили- та UPGRADE будет установлена, если во время инсталляции Visual Prolog устано- вить флажок Upgrade from Previous Versions. Запуск Visual Prolog Программа установки инсталлирует группу программ с пиктограммой, которая обычно используется для запуска среды визуальной разработки Visual Prolog. Впро- чем, существует множество других вариантов запуска приложения в Windows, на- пример запуск исполняемого файла VIP.EXE из папки B1N\W1N\32 (32-битная вер- сия Windows), находящейся в основном каталоге Visual Prolog. Если во время закрытия среды визуальной разработки был открыт проект (PRJ или VPR файл), то при следующем запуске этот проект откроется автоматически. Если в процессе инсталляции Visual Prolog вы выбрали флажок Associate 32-bit VDE with Project File Extensions PRJ & VPR, то для открытия проекта достаточно дважды щелкнуть на файле с расширением prj или vpr. Среда визуальной разработки запус- кается и загружает выбранный проект. Для начинающих пользователей Если вы никогда ранее не программировали в Visual Prolog (или PDC Prolog), реко- мендуем разобрать первый пример GUI (см. гл. 25).
Гпава 8. Установка и начало работы в Visual Prolog 201 Для продвинутых пользователей Если вы уже программировали на других версиях Пролога и имеете представление об основных принципах его работы, то вам будет полезно обратить внимание на от- личия интерпретируемых версий. Следующим шагом является изучение визуально- го интерфейса программирования (VPI) — гл. 26 и, затем, визуальной среды разра- ботки (VDE) Visual Prolog — гл. 27. Итак, добро пожаловать в Visual Prolog! Для запуска большинства примеров из данного руководства необходимо использо- вать Test Goal — утилиту среды визуальной разработки. Эта утилита может быть ак- тивизирована при помощи команды Project | Test Goal или комбинации клавиш <Ctrl>+<G>. Для корректного выполнения примеров с утилитой Test Goal среда визуальной разработки использует специальные настройки загружаемых проектов. Мы рекомендуем вам создать и всегда использовать следующий специальный TestGoal-проект. Создание TestGoal-проекта для выполнения примеров При использовании утилиты Test Goal для выполнения примеров требуется опреде- лить некоторые (не предопределенные) опции компилятора Visual Prolog. Для этого выполните следующие действия: I. Запустите среду визуальной разработки Visual Prolog. При первом запуске VDE (среда визуальной разработки) проект не будет загру- жен, и вы увидите окно, показанное на рис. 8.1. Также вас проинформируют, что по умолчанию создан инициализационный файл для Visual Prolog VDE. 2. Создайте новый проект. Выберите команду Project | New Project, активизируется диалоговое окно Appli- cation Expert. Рис. 8.1. Запуск среды визуальной разработки
202 Часть IV. Программирование на Visual Prolog 3. Определите базовый каталог и имя проекта. Примеры из данного руководства следует устанавливать в подкаталог \DOC \Examples корневого каталога Visual Prolog. Допустим, что при установке Visual Prolog вы выбрали C:\VIP в качестве корневого каталога Visual Prolog. В этом случае мы рекомендуем задать имя в поле Base Directory следующим образом: С:\VIP\DOC\Examples\TestGoal Это определение очень удобно для будущей загрузки исходных текстов примеров, представленных в данном руководстве. Имя в поле Project Name следует определить как "TestGoal". Также установите флажок Multiprogrammer Mode и щелкните мышью внутри по- ля Name of.PRJ File. Вы увидите, что появится имя файла проекта TestGoal.pij (рис. 8.2). Рис. 8.2. Общие установки диалогового окна Application Expert Определите цель проекта. На вкладке Target мы рекомендуем выбрать параметры, отмеченные на рис. 8.3. Теперь нажмите кнопку Create для того, чтобы создать файлы проекта по умол- чанию. 4. Установите требуемые опции компилятора для созданного TestGoal-проекта. Для активизации диалогового окна Compiler Options выберите команду Options | Project | Compiler Options. Откройте вкладку Warnings. Выполните следующие действия: • установите переключатель Nondeterm. Это нужно для того, чтобы компилятор Visual Prolog принимал по умолчанию, что все определенные пользователем предикаты — недетерминированные (могут породить более одного решения);
Гпава 8. Установка и начало работы в Visual Prolog 203 Application Expert General | Target VPi Options j Other Options | User Info | Help Meker | Create I Cancel ' I Help Рис. 8.3. Установки на вкладке Target диалогового окна Application Expert • снимите флажки Non Quoted Symbols, Strong Type Conversion Check и Check Type of Predicates. Это будет подавлять некоторые возможные предупреждения компилятора, которые не важны для понимания выполнения примеров дан- ного руководства; • нажмите кнопку ОК, чтобы сохранить установки опций компилятора. В результате этих действий диалоговое окно Compiler Options будет выглядеть, как показано на рис. 8.4. Compiler Options Code Generation | Output j Warnings | Miscellaneous | Г* * Suppress All Warnings Г“ TreatWamingsas Errors___ Ma^AllowedWamings • Default Predicate Type-- Nondeterm Г Determ < j C Procedure ! P Duplicated Includes Г“ Non Quoted Symbols (“Strong Type Conversion Check Г* CheckType of Predicates P Unused Variables P Unused Predicates Г“ Unreachable Code OK ' Cancel Help Рис. 8.4. Установки опций компилятора Открытие окна редактора Для создания нового окна редактирования вы можете использовать команду меню File | New. В результате появится новое окно редактирования с именем Noname.
204 Часть IV. Программирование на Visual Prolog Редактор среды визуальной разработки — стандартный текстовый редактор. Можно использовать клавиши управления курсором и мышь так же, как и в других редакто- рах. Он поддерживает команды Cut, Сору и Paste, Undo и Redo, которые находятся в меню Edit. В меню Edit также показаны комбинации "горячих" клавиш для этих дей- ствий. Подробное описание редактора находится в системе помоши VDE (клавиша <F1> в окне редактора). Запуск и тестирование программы Для проверки того, что ваша система настроена должным образом, следует напеча- тать следующий текст в окне: GOAL write("Hello world"),nl. В терминологии языка Пролог это называется GOAL, и этого достаточно для про- граммы, чтобы она могла быть выполнена. Для того чтобы выполнить GOAL, вам следует активировать команду Project | Test Goal или нажать комбинацию клавиш <Ctrl>+<G>. Если ваша система установлена правильно, то экран монитора будет выглядеть, как показано на рис. 8.5. TestGoal.vpr - Visual Prolog Generating 'ObjtgoalSOOO.SYM' Linking Target Object TestGoal is running in C:\Vip\Doc\ExaniplesVTestGoal\Obj directory 21 I Рис. 8.5. Тестовая программа "Hello world" Результат выполнения программы будет расположен вверху в отдельном окне (на рисунке оно называется Inactive C:\Yip\Doc\Examples\TestGoal\Obj\goal$OOO.exe). которое необходимо закрыть перед тем, как тестировать другую GOAL.
Гпава 8. Установка и начало работы в Visual Prolog 205 Тестирование примеров данного руководства По нашей рекомендации вы установили примеры руководства Examples (Language Tutorial) и ответы к упражнениям Answers (Language Tutorial). Примеры данного руководства можно найти в подкаталоге <CDROM>:\RUN\DOC\EXAMPLES, а от- веты к упражнениям — в подкаталоге <CDROM>:\RUN\DOC\ANSWERS на прила- гаемом CD. Примеры названы в соответствии с главой, в которой они появились (в ориги- нальной английской версии этой части книги Visual Prolog Language Tutorial): chCCeNN.pro, где CC — это 02, 03, 04 и т. д., согласно номеру главы. В этой книге вам надо уменьшать номер главы на 7; NN — это номер примера в каждой главе (01, 02, 03 и т. д.). Тестирование примеров в Test Goal Мы советуем вам попробовать сейчас открыть один из примеров в среде визуальной разработки и протестировать его, используя утилиту Test Goal. Для этого выполните следующие шаги: 1. Запустите среду визуальной разработки Visual Prolog. 2. Используйте команду меню Project | Open Project для открытия специального TestGoal-проекта (см. разд. "Создание TestGoal-проекта для выполнения примеров" данной главы). 3. Используйте команду меню File | Open для открытия одного из файлов chCCeNN.pro. 4. Используйте команду меню Project | Test Goal (или нажмите комбинацию кла- виш <Ctrl>+<G>) для тестирования загруженного примера. Test Goal найдет все возможные решения GOAL и покажет значения всех перемен- ных, используемых в GOAL. Комментарии к свойствам утилиты Test Goal Утилита среды визуальной разработки интерпретирует GOAL как специальную про- грамму, которая компилируется, компонуется, генерируется в исполняемый файл и Test Goal запускает его на выполнение. Эта утилита внутренне расширяет заданный код GOAL, чтобы сгенерированная программа находила все возможные решения и показывала значения всех используемых переменных. Утилита Test Goal компилиру- ет этот код с использованием опций компилятора, заданных для открытого проекта (рекомендуемые опции компилятора для TestGoal-проекта мы определили ранее). (Замечание ) Утилита Test Goal компилирует только тот код, который определен в активном окне редактора (код в других открытых редакторах или модулях проектов, если они есть, игнорируется). При компоновке исполняемого файла Test Goal использует стратегию EASYWIN (см. гл. 26). Обратите внимание на то, что нельзя определить какие-либо опции
206 Часть IV. Программирование на Visual Prolog компоновки для Test Goal, т. к. игнорируются любые установки Make Options, за- данные для открытого проекта. Поэтому Test Goal не может использовать никакие глобальные предикаты, определенные в других модулях. Заметим, что утилита имеет ограничение на количество переменных, которые могут быть использованы в GOAL. На данный момент их 12 для 32-разрядной среды визуальной разработки, но это число может быть изменено без дополнительных уведомлений. Тестирование примеров как автономных исполняемых программ Большинство примеров в данном руководстве предназначено для тестирования ути- литой Test Goal, но некоторые примеры, такие как chlle05.pro, предназначены для тестирования как автономные исполняемые программы (standalone executables). Для тестирования таких примеров рекомендуем следующую процедуру: 1. Запустите среду визуальной разработки Visual Prolog и откройте ранее созданный TestGoal-проект. 2. Откройте файл TestGoal.PRO для редактирования (двойной щелчок по пикто- грамме TestGoal в окне проектов). 3. Код языка Пролог в файле TestGoal.PRO начинается (после начального коммен- тария) с директивы include: include "TestGoal.INC" 4. Закомментируйте директиву include и удалите остальной код Пролога. Заметим, что директива include может быть закомментирована для простых примеров из данного руководства, но в более сложных программах она необходима. 5. Включите файл с исходным кодом примера, пусть это будет chi 1е05.рго. Заметим, что имя файла в директиве include должно содержать правильный путь относи- тельно корневого каталога проекта, поэтому рекомендуем использовать команду редактора Insert | FileName: • напечатайте: include • выполните команду меню Edit | Insert | FileName. Откроется диалоговое окно Get & Insert FileName. Найдите нужное имя файла (chi 1е05.рго), выделите его и нажмите кнопку Open. После этого файл TestGoal.PRO должен содержать следующие строки: % include "TestGoal.INC" % Can be commented in simple examples include "C: \\VIP_52\\DOC\\EX7VIPLES\\chlle05 .pro" 6. Теперь можно компилировать исходный код примера chlle05.pro, создавать ис- полняемый файл (в этом случае он будет называться TestGoal.EXE) и запускать его как автономную исполняемую программу. Все эти действия можно выпол- нить одной командой Project | Run или простым нажатием клавиши <F9>.
Гпава 8. Установка и начало работы в Visual Prolog 207 Обработка ошибок Если вы допустили ошибки в программе и пытаетесь скомпилировать ее, то среда визуальной разработки отобразит окно Errors (Warnings), которое будет содержать список обнаруженных ошибок (рис. 8.6). йй TestGoaLprj - Visual prolog Fie Edit Project. Options Window Help I C:\Vip4 Doc 'Exaniples4 TestGoar-TestGoaLprj |SCH02E01.PRO (.A) Module I Testuoal gpTestGo 24П .insert likes(symbol,symbol) Indent 8ЯЙЙ likes (ellen, "tennis) . likes(j ohn,football). likes(tom,baseball). - likes(eric,swimming,6). goal likes(bill,baseball). Рис. 8.6. Обработка ошибок u ¥ Дважды щелкнув на одной из этих ошибок, вы попадете на место ошибки в исход- ном тексте. Можно воспользоваться клавишей <F1> для вывода на экран интерак- тивной справочной системы Visual Prolog. Когда окно помощи откроется, щелкните по кнопке Search, наберите номер ошибки, и на экране появится соответствующее окно помощи с более полной информацией о ней. Подробному рассмотрению основных функций интегрированной среды визуальной разработки VDE Visual Prolog посвящена следующая глава.
ГЛАВА 9 Возможности Visual Prolog Возможности Пролога выходят далеко за границы области искусственного интеллек- та. Высокий уровень абстракции, легкость и простота, с помощью которых пред- ставляются сложные структуры данных, позволяют применять декларативный под- ход к программированию, что приносит пользу любой стратегии решения задач. По этой причине Visual Prolog широко используется для создания административных приложений, управления сложными базами данных, систем календарного планиро- вания, Web-приложений и многих других. Рассмотрим некоторые преимущества Visual Prolog. Визуальная среда разработки (VDE) Визуальная среда разработки (VDE, Visual Development Environment) объединяет компилятор с редактором, комплектом инструментальных средств разработки ресур- сов, экспертами ресурсов и приложений, интерактивной утилитой построения при- ложений и различными средствами просмотра кода. После интерактивного визуального создания компонентов пользовательского интер- фейса автоматически генерируется исполняемая программа. Эксперт приложений автоматически создает все необходимые файлы проекта, а эксперт ресурсов знает, как сгенерировать код Пролога, который будет обеспечивать использование всех выбранных ресурсов. Визуальная среда разработки (рис. 9.1) позволяет легко, удобно и быстро создавать приложения, основанные на высокоуровневой абстракции стандартных пользова- тельских интерфейсов, обеспечиваемых каждой из поддерживаемых операционных систем. Функционально идентичные среды визуальной разработки могут выполнять- ся на всех платформах Windows. Эксперты кода Эксперты кода генерируют и поддерживают Пролог-код, управляющий использова- нием ресурсов.
Гпава 9. Возможности Visual Prolog 209 IgMYPROJ.PRJ -VirualPiping £c6t Project Qpbons Window Help и Ш ВДВ|.|И№1«|6| SBf] a _.L WF'HOWOLAbUIDTauflWYPRIU.HU ДИИ ИВИ lodufe Dialog Wndow Meng Toolbar Piotncl Tree -£§ MYPROJ - Й CLOCK.PRO Jr win_cfock_cieate •- clock_conveillime I- starttimer stoptimer > win_clock_eh + Й CROSS.PRO Ikb DIAI ЛИЯ PRO |MYPROJ.ЕХЕр -•Hew Edit. Delete Attribute joiRLIST.: ]MYPROJ.COh] (hlptopic.coh] ]myproj.ihc| ^PITOOLS. PRO|, ^DIALOGS.: JgMYPflOJ.PRO ] . . X . . XIMCLUDEXERROI ~ ]..Х..Х1НСЬШ>ЕХ10РЕГ н ].. X. . XVPIXIWCLUDEXDIAL Copyright (c) 1984 - 2000 Prolog Develop*- Project: MYPROJ FileName: MYPROJ.PRO Purpose: A little Deiio Written by: Leo Jensen Cottttents: ♦Л******************************************************* include "nyproj.inc" include ”ayproj.con"| include ''hiptopic.con” %BEGIN_WIN Task Window /********************************************«*********** Event handling for Task Window |. X . XVPIXIWCLUDEXTOOL. .WPIXINCbUDEXTREE >| T^TStmjWnJffFTPFTO^YKr' Generating 'O0J32|VPITOOLS.DEB' Linking Project has been built. Рис. 9.1. Визуальная среда разработки (VDE) Dialog and Window Expert г Dialog or Window Selection | P Dialog ।................... i P Window Iww_______________ “ Place Source Code in... ——- - Module . ]tEST.PRO Code Style | win_Cfeate(.) Г7 Automatic Source Update г Event Handling- Event Type E Window ' Menu Scrollbar H Control Key e_Mou$eDbl e_M ouseDown e_Mou$eMove e__Mou$eUp Mouse OwnDraw Mi$c Extent Help J Add Clguse | I’f-'eteCJ’ or I Cancel I Help . Edit Code | Delete Code | Declaration | Toolaa; [ Update code | Рис. 9.2. Эксперт окон и диалоговых окон
210 Часть IV. Программирование на Visual Prolog Одно из несомненных преимуществ Visual Prolog — комбинация инструментальных средств разработки ресурсов и экспертов кода, поддерживающих использование этих ресурсов для создания графического пользовательского интерфейса. Это означает, что вы можете создать новое приложение за несколько минут и затем последова- тельно расширять этот прототип до конечного приложения. Эксперт окон и диалоговых окон (Dialog and Window Expert) изображен на рис. 9.2. Эксперт приложений Эксперт приложений (Application Expert) генерирует и конфигурирует новые проек- ты. Он учитывает комбинации операционных систем, стратегии пользовательского интерфейса (UI, User Interface), особенности используемого компоновщика связей, сопутствующих инструментальных средств, используемых пакетов и т. д. После того как будет сгенерирован новый проект, эксперт приложений автоматически устано- вит все базовые инструментальные средства, такие как файл справки, панели инст- рументов, меню и т. д. Окно эксперта приложений изображено на рис. 9.3. Рис. 9.3. Эксперт приложений Интегрированные редакторы для подготовки ресурсов Эти инструментальные средства позволяют визуально проектировать и изменять пользовательский интерфейс в интерактивном режиме. Элементы управления раз- мещаются в диалоговых окнах при помощи мыши. Щелкнув на элементе кнопкой мыши, вы получите доступ к настройке параметров этого элемента. Ресурсы — это диалоговые окна, растровые изображения, пиктограммы, курсоры и строки. Они необходимы для любого приложения, использующего графический интерфейс поль- зователя (GUI, Graphical User Interface).
Гпава 9. Возможности Visual Prolog 211 Возможность импорта ресурсов Ресурсы могут быть импортированы из динамически связанных библиотек (DLL, Dynamic Link Libraries), приложений, файлов ресурсов и из других проектов Visual Prolog. На рис. 9.4 приведено диалоговое окно импорта ресурсов. Import from G:\OLE\SERVER\SRV32 VPR □Тх i [[DIALOGS] * '• I Host File Transfer < Х] Host File Request Storage '<' Send Message to ( - Exit Fleetwatch/LAt Fleetwatch/LAN Sei Progress Message Connect dialog About dialog [WINDOWS] т Load [ •" Host Йе Request Load all | Cluse | Рис. 9.4. Диалоговое окно импорта ресурсов Текстовый редактор Visual Prolog обладает широким спектром возможностей, присущих любой совре- менной среде разработки, например, мощный редактор исходного кода с цветным выделением ключевых слов и других элементов языка Visual Prolog. Цвета позволяют легко различать имена предикатов, параметры, комментарии и т. д., например, це- лочисленные константы могут отображаться красным цветом. Редактор (рис. 9.5) поддерживает возможность неограниченной отмены и восстановления изменений, поиск и замену, операции вырезания, копирования и вставки, быстрое перемещение блоков кода при помощи мыши. Редактор позволяет также внедрять гипертекстовые ссылки. Более того, как и в предыдущих редакторах PDC, вы можете включать функции редактора в ваши собственные приложения. Уникальной особенностью редактора, используемого в визуальной среде разработки, является то, что он "знает" обо всех предикатах, компонентах пользовательского ин- терфейса, цветах, константах и т. д. Все эти элементы могут быть легко вставлены в исходный код щелчком мыши. Интегрированный создатель подсказок Встроенная система создания подсказок и помощи позволяет очень просто создавать интерактивную подсказку в вашем приложении. Система подсказок основана на гипертекстовой абстрактной машине. В системе подсказок можно вводить текст
212 Часть IV. Программирование на Visual Prolog Рис. 9.5. Рабочее окно редактора подсказки в интерактивном режиме, создавать новые связи и переходить по сущест- вующим связям во время фазы проектирования. Система подсказок (рис 9.6) может генерировать промежуточные файлы в формате RTF (стандартный формат для Windows-компиляторов помощи) для создания собственных справочных систем. Компилятор помощи (например, HCW.EXE) не поставляется вместе с Visual Prolog, но его можно найти в продуктах Visual C++ и Borland C++. Если вам необходима более новая версия компилятора помощи Windows, будет полезным обратиться к каталогу Softlib по адресу ftp.microsoft.com. Import from E:WPRO\SETUP\SETUP.HAM □ Content Preview . About Prolog | About Visual Prolog . Acknowledgments bgibind ____ (РЦГ)ТЬе Bindings Dialog(F) (P) | Close j Help bindings Command Line Compiler compilers Contents directory docs final How Does Prolog Differ Fron- Important notes libraries [B}VPI, The Visual Programming Interface[b[ Libraries and Include Files Examples; Illustrating VP I Feature- Programs; Some Small Application Tool Examples (BJOther Bindings(b} Native Uinl6 API Native OS/2 PH API | LaadAII | 1 P^view | 1* Auto Preview Г Ask Marne for Untitled Topics BGI Graphics TCP/IP Support - to be included in Visual Prolog 4.2 Рис. 9.6. Система помощи Visual Prolog
Гпава 9. Возможности Visual Prolog 213 Браузер исходного кода Компилятор Visual Prolog генерирует информацию для браузера исходного кода, что позволяет просматривать предикаты модуля, все глобальные предикаты проекта, находить определения и объявления предикатов и доменов, а также классов и фактов. Разделение проектов и управление исходным кодом Совместно с Visual Prolog можно использовать системы управления исходным ко- дом, такие как Visual SourceSafe, PVCS и MKS. Visual Prolog позволяет легко разде- лять ресурсы между различными проектами, а также осуществлять совместную рабо- ту нескольких программистов над одним проектом. Интерактивная справка Visual Prolog Эта справочная система (рис. 9.7) предлагает полное руководство по операциям ви- зуальной среды разработки, всю справочную информацию по основам языка Пролог и интерфейсу визуального программирования (VPI, Visual Programming Interface). Рис. 9.7. Интерактивная справка (помощь) Visual Prolog
2М Часть IV. Программирование на Visual Prolog Интерфейс визуального программирования (VPI) Для графических интерфейсов пользователя был определен переносимый интерфейс прикладного программирования (API, Application Programming Interface) Visual Prolog, который является абстракцией возможностей основных оконных операцион- ных систем Windows 3.x, Windows 95/98/МЕ и Windows NT/2000. Этот интерфейс визуального программирования предоставляет программисту GUI API, который од- новременно является и переносимым, и более легким в использовании, чем про- граммирование на "родных" GUI API. Однако, чтобы не ограничивать пользовате- лей, VPI содержит и некоторые непереносимые средства и свойства. Если в про- грамме используются непереносимые средства, то приложение также будет непереносимым. Сделать приложение переносимым поможет условная компиляция, которая обеспечит альтернативное поведение приложения на различных платфор- мах. Имеется возможность программировать непосредственно под API используемой операционной системы, как в подкаталогах W1NBIND или PMBIND. Компоненты (пакеты) GUI высокого уровня Наряду с основным переносимым VPI, некоторые высокоуровневые компоненты GUI были выполнены с использованием VPI. Эти компоненты (пакеты) поставля- ются вместе с исходным кодом, и, конечно, переносимы на все платформы, поддер- живаемые VPI, Эти инструментальные средства включают в себя таблицу, окно де- рева, окно просмотра каталогов, панели инструментов, снабженные вкладками диа- логовые окна, усовершенствованное средство генерации отчетов и т. д. Компилятор Быстрый высокооптимизированный компилятор Visual Prolog генерирует компакт- ный код, который вполне может конкурировать с кодом, генерируемым компилято- рами С и Pascal. Компилятор выполняет несколько различных исследований, начи- ная с общего анализа потоков и проверки детерминизма и заканчивая распределени- ем регистров и оптимизацией хвостовой рекурсии. Compiler Options <Sode Generation | Output j Warnings j Miscellaneous Г* Warnings Г Treat Vinings as Errors Max Mowed Warnings |25 j Defait Predicate Type |- C Hondeterm ! C Determ I grocedure Г* Duplicated Includes Г Non Quoted Symbols . Г* S b ong Type Con vet sion Check p Check Type'of Predicates P Unused Variables Г Unused Predicates Г Unreachable Code Cancel Help Рис. 9.8. Диалоговое окно Compiler Options
Глава 9. Возможности Visual Prolog 215 Кроме генерации эффективного кода, компилятор выполняет множество дополни- тельных проверок для обнаружения возможных проблем во время компиляции. Основные проверки — это контроль соответствия типов, глобальный анализ пото- ков, проверка детерминизма и обнаружение возможных сбоев. Диалоговое окно Compiler Options (Опции компилятора) представлено на рис. 9.8. Проверка соответствия типов компилятором Некоторые версии Пролога являются интерпретаторами без контроля типов, но от- личительная особенность Visual Prolog — это его система типов, которая обеспечива- ет дополнительный уровень защиты при программировании. Объявления типов до- кументируют код и позволяют компилятору указывать на ранних стадиях разработки обычные ошибки несоответствия типов, так же как и более серьезные логические ошибки. Кроме того, являясь связующим звеном между программистом и системой разработки, объявления типов гарантируют целостность программы в течение всего жизненного цикла продукта. Вместе с тем, объявления типов помогают компилятору генерировать более эффективные программы, сохраняющие память во время выпол- нения (листинг 9.1). Листинг 9.1 remains deter = none; deter(string) nounp = nounp(deter,string,relcl) relcl = none; relcl(string,verbp) vp = vp(string,string) np = np(string) sentence = sent(nounp,verbp,; question(vp,np) tokl =string* verbp = verb(string); verbp(string,nounp) predicates append(slist,slist,slist) fillbox(slist) tokl(string,tokl) Отладчик При работе с большими программами на Прологе вам понадобится отладчик Visual Prolog. Отладчик работает с откомпилированным кодом. В исходном коде можно ставить точки останова и выполнять программу по шагам. В режиме пошагового выполнения программы можно просматривать значения переменных и содержимое (.твержденных фактов. Окно отладчика представлено на рис. 9.9.
216 Часть IV. Программирование на Visual Prolog Jmypioi exe - Visual Prolog Debugger for Windows 32 ggfj EZ I' Modules S-ja MYPROJ a-jg MYPROJ.exe i*>®| CLOCK PRO CROSS PRO 2! E DIALOGS PRO iti-Й DIRLIST PRO a S EDWIN.PRO - E MYPROJ PRO -4* dlg_aboul_dialog_create{ window Parent) • (i IP tb_help_6ne_create( window Parent) • (i) «”** lb_proiect_toobar_aeate( window Parent) • ( _PROLOG_Goal ' i long dg_about_daloa_eh( window, event J • I . hJH long task_wri_eh( window, event) • (i,i) $&) PICTURE.PRD &15 sweep pro Ф-S TREE.PRO $ S VP1TOOLS.PRO MYPHUJPFIO 231.1 goal Jnseirt Imtert Readonly ifdef use_Mdi vpi_SetkttrVal(attr_vin_Mdi,b_true), eaddef ifdef use_3dctrl vpi_SetkttrVal(attr_win_3dcontrols,b_true), eaddef ifdef use_tbat vpi_SetkttrVal(attr_vin_tbar,use_tbar), eaddef ifdef use_sbar vpi_3etAttrVal(attr_win_3bar,use_sbar), eaddef vpi_Init(task_vin_riags, ta3k_win_eh,task_uin_Henu,"Byproj",task_win_Title). %END_WIH Task Window ‘In Module: Myproj.exe 'In Module; Myproj.exe jin Modules Myproj.exe Entry point of thread breakpoint is set at Goal (address « 464450), breakpoint is set at vpi_CallEH (address * 47EED3), breakpoint is set at _vpi_BeforeEvent (address • 480E54). (Id: Г0) is: 77F762E8. Рис. 9.9. Окно отладчика Обработка исключительных ситуаций и перехват ошибок Visual Prolog содержит мощные средства для обработки ситуаций, связанных с появ- лением ошибок, а также для управления пользовательскими прерываниями. Можно выбрать несколько различных уровней проверки ошибок и составления отчета ос ошибках. check_diskette(S) trap(disk(S),ExitCode, errorhandler(ExitCode)). Классы и объекты Часто различают объектно-ориентированные и декларативные языки программиро- вания, но в Visual Prolog вы можете использовать возможности обеих парадигм Язык Visual Prolog поддерживает объекты и классы так же, как C++. class aclass predicates testpred(string) check() store() endclass
Гпава 9. Возможности Visual Prolog 217 implement aclass facts f(aclass) clauses store ():- this(ORef), assert(f(ORef)). heck():- f(ORef), ! / ORef:testpred("Check Virtual methods"). testpred(TXT) write(TXT),nl. endclass goal О = aclass::new() , 0:store, 0:check(). Переносимый код Система Visual Prolog работает под разными операционными системами и может генерировать программы для различных платформ. Не считая нескольких специфи- ческих особенностей и ограничений операционных систем, код Пролога переносим на любую из них. Функции, подобные копированию, переименованию и удалению файлов, вызову других программ, получению значений даты и времени и т. п., рабо- тают одинаково на всех платформах. Приложения могут быть созданы для DOS, расширенного DOS, Windows 3.x, Windows 95/98/МЕ, Windows NT/2000 и Linux (VPI не поддерживается под Linux, DOS и расширенным DOS). Открытая платформа Visual Prolog способен взаимодействовать с другими программными средствами. Он может генерировать подпрограммы, вызываемые из других языков, а может и само- стоятельно вызывать подпрограммы, написанные на других языках. Интерфейс является общим и поддерживает все компиляторы, которые генерируют стандартные модули OBJ. Программы Visual Prolog могут как вызывать DLL, так и сами могут быть помещены в DLL. Объявляя глобальные предикаты Visual Prolog, как имеющие соглашения о вызовах language С (или language stdcall), и домены, соответствующие С типам аргумен- тов, и соответствующие потоки ввода/вывода, можно вызывать функции С непо- средственно (как если бы они были написаны на Прологе) без какого-либо специ- пьного связующего кода между Visual Prolog и С. Этот интерфейс работает в обоих направлениях: когда предикаты Visual Prolog объявляются в language с, они могут вызываться прямо из программ, написанных на языке С.
218 Часть IV. Программирование на Visual Prolog global predicates procedure long vpi_LoadDll(string) — (i) language c procedure vpi_FreeDll(long) — (i) language c procedure long vpi_GetDll₽roc(long, string ProcName) — (i,i) language c Интегрированная возможность сборки Интегрированный построитель программ (make facility) управляет всей сложностью компиляции, компоновки, связывания ресурсов и т. д. Построитель программ про- веряет метки времени, чтобы перекомпилировать только необходимые файлы. Зави- симости могут быть отображены в виде дерева для визуализации структуры проекта (рис. 9.10). Project Tree -1П|Х -|тПТ1ДС -|тпти.о|- Е ГОР£0\УРГ|ШСЬ1ЛЖУИ.С 0Я | ^Е5УРКОЦДСЬОРРТУРВВОы| ^EVVPROWPMffCLTODVH.PRx| J Е \VPRQ\ypriJCLUWDIALO CmaAL0GD0bf| -{Ё УУРЕОС LUDEDIAL О СЯОАЬО ОДД | .. -| е '.vpRO'ivmfl сьтогдигроы j J -| е \у№о^уицгс1,тодыд>лаваш»дсвдх] -|гуурйо\ург1дсь1тртооьвла\тооьвла£1оы] (ЕПФьогоииясыгортооьвлацтооьвлагаг] -|тпт.соя| |нитоис.соя] rllBTIffc] -|тптткг| “|уоорхюы] [морхвом] Рис. 9.10. Визуализация дерева проекта Подсистема баз данных Быстрая и чрезвычайно гибкая подсистема баз данных делает Visual Prolog весьма удобным средством для разработки приложений баз данных. Система баз данных поддерживает набор отдельных упорядоченных последовательностей термов Проло- га. Термы баз данных могут быть любой абстракцией, поддерживаемой языком, от простых записей до деревьев или графов. Система баз данных может обращаться прямо к отдельным термам или совершать поиск с возвратом по последовательно- стям термов, чтобы генерировать или подбирать требуемые значения. Термы могут быть сохранены в файле или в памяти. Подсистема баз данных поддерживает также В+ деревья, которые обеспечивают быстрый поиск данных и возможность эффек- тивного изменения порядка термов. При использовании подсистемы баз данных в сетевых приложениях Visual Prolog предоставляет возможность разделения файлов во внешней базе данных. Много-
Гпава 9. Возможности Visual Prolog 219 пользовательский доступ к базе данных обеспечивается при помощи механизма од- новременного доступа к файлам несколькими пользователями или процессами. Клиент-серверная архитектура Visual Prolog — эго мощный инструмент для построения клиент-серверных прило- жений. В настоящее время основным средством для построения таких приложений являются связывания TCP/IP. но можно использовать и NETDDE под Windows. Применяя любое из этих средств, программист может передавать сколь угодно сложные элементы Пролога между различными процессами на одной машине или между программами на разных машинах по сети. Это средство Visual Prolog позволя- ет легко создавать серверы баз данных или логические серверы. ODBC- и SQL-интерфейсы Система внешней базы данных Visual Prolog — это. в большинстве случаев, самый быстрый и наиболее гибкий способ хранения большого количества данных. Однако может быть и так. что данные уже существуют в другой системе баз данных, или приложению необходимо разделять данные с другими приложениями, которые ис- пользуют только специфическую технологию баз данных. В таких случаях важно иметь возможность связываться с системами внешних баз данных, реализованную в Visual Prolog посредством переносимого SQL-интерфейса, основывающегося на ODBC-. OCI-биб.тиогеках Oracle, или на DB2. Visual Prolog содержит также прямой интерфейс с Microsoft ODBC API для платформ Windows. Инструментальное средство обработки документов Инструментальные средства DOC_TOOL компании PDC обеспечивают высокоуров- невую абстракцию для обработки полностью отформатированных документов. Ис- пользование структуры Пролог для представления документа позволяет не зависеть от фактического формам, будь то RTF, HTML или 1PF. Существуют конвертеры из формата элементов Пролог в эти форматы, а также синтаксические анализаторы, позволяющие конвертировать любой из этих форматов в формат элементов Пролог. Эти инструменты открывают много прикладных возможностей, таких как генерация документов Word, создание гипертекстовых документов и т. д. Они находятся в под- каталоге <CDROM>:\RUN\DOC_TOOL и включают пример программы преобразо- вания файлов формата HTML в формат RTF. Исходный код для интерпретатора Пролог В состав пакета Visual Prolog включен механизм логического вывода (PIE, Prolog Inference Engine) (рис. 9.11) — исходный код стандартного интерпретатора Пролог, написанный на Visual Prolog. Этот интерпретатор — важный инструмент для изуче- ния того, как работает Пролог. Изменяя его. можно добавлять возможности мета- языка к своим приложениям, создавая свои собственные специализированные языки
220 Часть IV. Программирование на Visual Prolog логического программирования, механизмы логического вывода, оболочки эксперт- ных систем или программные интерфейсы. Р(X):-write(X),nl. f (X) call(X) . member (X,[X| _] ) . . member (X, [_| L] ) : -member (X, L) . SDialos Ready GOAL = member(X.[1.2,3]), f(GOAL) GOAL= member(1,[1,2,3]), X= 1 GOAL= member(2,[1,2,3]), X= 2 GOAL= member(3.[1.2,3]], X= 3 3 Solutions jReaciy-^ ~~ ". [Stack: |848 'Memory: [S4424E Рис. 9.11. Окно Prolog Inference Engine Использование компилятора Пролог На прилагаемом CD (<CDROM>:\RUN\VPI\TOOLEXAM\Build) находится пример того, как можно интегрировать компилятор и компоновщик Пролог в ваше прило- жение, чтобы компилировать правила "на лету". Вы можете откомпилировать и по- местить измененные правила в DLL, затем вы можете динамически прикомпоноватъ эту DLL к своему приложению. Выполняя эти правила в динамически скомпоно- ванной DLL, вы можете изменять правила, даже не закрывая приложения. Это чрезвычайно мощное средство необходимо, например, для решения задач пла- нирования и составления расписаний. Однако требуется оплатить небольшой взнос компании PDC и подписать соглашение о том, что вы не будете использовать дан- ный продукт для создания приложения, конкурирующего с Visual Prolog. Если у вас есть профессиональная версия Visual Prolog, а ваши клиенты имеют на него лицен- зию, вы можете предлагать им свое приложение. (Для получения более подробно? информации свяжитесь с компанией PDC.)
Гпава 9. Возможности Visual Prolog 221 Исходный код для визуальной среды разработки Если у вас есть профессиональная версия Visual Prolog, то исходный код Пролог для визуальной среды разработки можно найти в подкаталоге VDESRC. При помощи этой информации вы сможете выполнить любую настройку визуальной среды разра- ботки и, изучив то, что сделали мы, использовать аналогичные инструментальные средства и методы в ваших программах. Экспертная система для текстовой анимации В последнюю версию Visual Prolog включена коммерческая оболочка экспертной системы ESTA (рис. 9.12). Она снабжена полным исходным кодом для того, чтобы пользователи могли настраивать и включать ее в свои приложения. Рис. 9.12. Диалоговое окно экспертной системы ESTA Эксперт меток штрихкодов Эксперт меток штрихкодов — небольшое приложение для создания и печати меток штрихкодов, иллюстрирующее возможности инструментальных средств VP1. Комплексные средства разработки программ Internet Visual Prolog содержит большой инструментарий, облегчающий процесс создания Internet-приложений.
222 Часть IV. Программирование на Visual Prolog Интерфейс с сокетами Visual Prolog поддерживает и основные интерфейсы нижнего уровня, и высокоуров- невый интерфейс, делая использование интерфейса сокетов проще и безопасней. Сокеты — это API к протоколу TCP/IP, используемому как для установления связи между программами через Internet внутри сети, так и между двумя программами на одном компьютере. Поддержка FTP Visual Prolog содержит API и примеры программ, которые показывают, как можно использовать протокол передачи файлов Internet для отправки и получения их с сер- вера Internet. Поддержка HTTP Visual Prolog содержит API и примеры программ, которые демонстрируют примене- ние протокола передачи гипертекстовой информации в сети Internet. Это основной протокол, используемый Всемирной паутиной. API может применяться для создания утилит клиента и сервера WWW, а также агентов Internet в Visual Prolog. Окно про- граммы WebAgent представлено на рис. 9.13. IjlJgWebAgent St atus Address - з 4 5 6 7 State: WebAgent starts) . X| Tixaer Settings J* gave List |[ Entry Setting's A LoajUst ] Entry Settings Entry Settings. ' - ' Qp>il>ns I yiew results]- About | ?! Help J Entry Settings Entry Settings’. Entry Settings , Srxry s«eeing«. Egll I Рис. 9.13. Окно программы WebAgent Поддержка CGI CGI-примеры (<CDROM>:\RUN\WWW\CGI\Samples) показывают, как можно соз- давать программы Visual Prolog, генерирующие динамические Web-страницы. Поддержка IS API Средство Visual Prolog для поддержки ISAPI компании Microsoft предоставляет вы- сокоэффективные сценарии на информационном сервере Microsoft или любом дру- гом сервере HTTP, который поддерживает интерфейс ISAPI.
ГЛАВА 1 О Основы языка Visual Prolog Данная глава открывает раздел, состоянии! из нескольких глав, последовательно обучающих основам языка Visual Prolog. Начнем мы с введения в логическое про- граммирование, а затем обсудим некоторые из основных принципов языка Пролог, включая предложения, предика гы, переменные, цели и сопоставления. Программирование в ЛОГике В Прологе (Prolog — PROgrarnming LOGic) вы получаете решение задачи логиче- ским выводом из ранее известных положений. Обычно программа на Прологе не является последовательностью действий, — она представляет собой набор фактов с правилами, обеспечивающими получение заключений на основе этих фактов. По- этому Пролог известен как декларативный язык. Пролог базируется на предложениях Хорна, являющихся подмножеством формаль- ной системы, называемой логикой предикатов. Логика предикатов — это простейший способ объяснить, как "работает” мышление, и она проше, чем арифметика, которой вы давно пользуетесь. Пролог использует упрощенную версию синтаксиса логики предикатов, он прост для понимания и очень близок к есгеегвеиному языку. Пролог включает механизм вывода, который основан на сопоставлении образцов. С помощью подбора ответов на запросы он извлекает хранящуюся (известную) ин- формацию. Пролог пытается проверить истинность гипотезы (другими словами — ответить на вопрос), запрашивая для этого информацию, о которой уже известно, что она истинна. Прологовское знание о мире — это ограниченный набор фактов (и правил), заданных в программе. Одной из важнейших особенностей Пролога является то, что, в дополнение к логи- ческому поиску ответов на поставленные вами вопросы, он может иметь дело с аль- тернативами и находить все возможные решения. Вместо обычной работы от начала программы до ее конца. Пролог может возвращаться назад и просматривать более одного "пути" при решении всех составляющих задачу частей.
224 Часть IV. Программирование на Visual Prolog Логика предикатов была разработана для наиболее простого преобразования прин- ципов логического мышления в записываемую форму. Пролог использует преиму- щества синтаксиса логики для разработки программного языка. В логике предикатов вы, прежде всего, исключаете из своих предложений все несущественные слова. За- тем вы преобразуете эти предложения, ставя в них на первое место отношение, а после него — сгруппированные объекты. В дальнейшем объекты становятся аргу- ментами, между которыми устанавливается это отношение. В качестве примера в табл. 10.1 представлены предложения, преобразованные в соответствии с синтакси- сом логики предикатов. Таблица 10,1, Синтаксис логики предикатов Предложения на естественном языке Синтаксис логики предикатов Машина красивая fun (саг) Роза красная red (rose) Билл любит машину, если машина красивая likes (bill, Car) if fun (Car) Факты и правила Программист на Прологе описывает объекты (objects) и отношения (relations), а за- тем описывает правила (rules), при которых эти отношения являются истинными. Например, предложение Билл любит собак. (Bill likes dogs.) устанавливает отношение между объектами Bill и dogs (Билл и собаки); этим от- ношением является likes (любит). Ниже представлено правило, определяющее, ко- гда предложение "Билл любит собак” является истинным: Билл любит собак, если собаки хорошие. (Bill likes dogs if the dogs are nice.) Факты В Прологе отношение между объектами называется фактом (fact). В естественном языке отношение устанавливается в предложении. В логике предикатов, используе- мой Прологом, отношение соответствует простой фразе (факту), состоящей из име- ни отношения и объекта или объектов, заключенных в круглые скобки. Как и пред- ложение, факт завершается точкой (.). Ниже представлено несколько предложений на естественном языке с отношением "любит” (likes): Билл любит Синди. {Bill likes Cindy) Синди любит Билла. (Cindy likes Bill) Билл любит собак. (Bill likes dogs)
Гпава 10. Основы языка Visual Prolog 225 А теперь перепишем эти же факты, используя синтаксис Пролога: likes(bill, cindy). likes(cindy, bill). likes(bill, dogs). Факты, помимо отношений, могут выражать и свойства. Так, например, предложе- ния естественного языка "Kermit is green" (Кермит зеленый) и "Caitlin is girl" (Кейт- лин — девочка) на Прологе, выражая те же свойства, выглядят следующим образом: green(kermit). girl(caitlin). Правила Правила позволяют вам вывести один факт из других фактов. Другими словами, можно сказать, что правило — это заключение, для которого известно, что оно ис- тинно, если одно или несколько других найденных заключений или фактов являют- ся истинными. Ниже представлены правила, соответствующие связи "любить" (likes): Синди любит все, что любит Билл. (Cindy likes everything that Bill likes) Кейтлин любит все зеленое. (Caitlin likes everything that is green) Используя эти правила, вы можете из предыдущих фактов найти некоторые вещи, которые любят Синди и Кейтлин: Синди любит Синди. (Cindy likes Cindy) Кейтлин любит Кермит. (Caitlin likes Kermit) Чтобы перевести эти правила на Пролог, вам нужно немного изменить синтаксис, подобно этому: likes(cindy, Something)likes(bill, Something). likes(caitlin, Something)green(Something). Символ имеет смысл "если", и служит для разделения двух частей правила: заго- ловка и тела. Вы можете рассматривать правило и как процедуру. Другими словами, эти правила likes(cindy, Something):- likes(bill, Something) likes(caitlin, Something)green(Something). также означают: "Чтобы доказать, что Синди любит что-то, докажите, что Билл лю- бит это" и "Чтобы доказать, что Кейтлин любит что-то, докажите, что это что-то зеленое". С такой "процедурной" точки зрения правила могут "попросить" Пролог выполнить другие действия, отличные от доказательств фактов, — такие как напеча- тать что-нибудь или создать файл. Запросы Однократно дав языку Пролог несколько фактов, мы можем задавать вопросы, ка- сающиеся отношений между ними. Это называется запросом (query) системы языка 8 Зак. 963
226 Часть IV. Программирование на Visual Prolog Пролог. Мы можем задавать Прологу такие же вопросы, которые мы могли бы за- дать вам об этих отношениях. Основываясь на известных, заданных ранее фактах и правилах, вы можете ответить на вопросы об этих отношениях, в точности так же это может сделать Пролог. На естественном языке мы спрашиваем: Does Bill like Cindy? (Билл любит Синди?) По правилам Пролога мы спрашиваем: likes(bill, cindy). Получив такой запрос, Пролог мог бы ответить: yes (да) потому что Пролог имеет факт, подтверждающий, что это так. Немного усложнив вопрос, мы могли бы спросить вас на естественном языке: What does Bill like? (Что любит Билл?) По правилам Пролога мы спрашиваем: likes(bill, What). Заметим, что синтаксис Пролога не изменяется, когда вы задаете вопрос: этот за- прос очень похож на факт. Впрочем, важно отметить, что второй объект — what — начинается с большой буквы, тогда как первый объект — bill — нет. Это происхо- дит потому, что bill — фиксированный, постоянный объект — известная величина, a what — переменная. Переменные всегда начинаются с заглавной буквы или символа подчеркивания. Пролог всегда ищет ответ на запрос, начиная с первого факта, и перебирает все факты, пока они не закончатся. Получив запрос о том, что Билл любит, Пролог от- ветит: What^cindy What=dogs 2 Solutions Так как ему известно, что likes(bill, cindy). и likes(bill, dogs). Надеемся, что вы пришли к такому же выводу. Если бы мы спросили вас (и Пролог): What does Cindy like? (Что любит Синди?) likes(cindy, What). то Пролог ответил бы: What = bill What = cindy
Глава 10. Основы языка Visual Prolog 227 What = dogs 3 solutions поскольку Пролог знает, что Синди любит Билла, и что Синди любит то же, что и Билл, и что Билл любит Синди и собак. Мы могли бы задать Прологу и другие вопросы, которые можно задать человеку. Но вопросы типа "Какую девушку любит Билл?" не получат решения, т. к. Прологу в данном случае не известны факты о девушке, а он не может вывести заключение, основанное на неизвестных данных: в этом примере мы не дали Прологу какого- нибудь отношения или свойства, чтобы определить, являются ли какие-либо объек- ты девушками. Размещение фактов, правил и запросов Предположим, у вас есть следующие факты и правила: Быстрая машина — приятная. (A fast car is fun). Большая машина — красивая. (A big car is nice). Маленькая машина — практичная. (A little car is practical). Биллу нравится машина, если она приятная. (Bill likes a car if the car is fun) . Исследуя эти факты, вы можете сделать вывод, что Биллу нравится быстрый авто- мобиль. В большинстве случаев Пролог придет к подобному решению. Если бы не было фактов о быстрых автомобилях, вы не смогли бы логически вывести, какие автомобили нравятся Биллу. Вы можете делать предположения о том, какой тип машин может быть крепким, но Пролог знает только то, что вы ему скажете; Пролог не строит предположений. Вот пример, демонстрирующий, как Пролог использует правила для ответа на за- просы. Посмотрите на факты и правила в этой части программы ch02e01.pro: likes(ellen, tennis). likes(john, football). likes(tom, baseball), likes(eric, swimming). likes(mark, tennis). likes(bill, Activity):- likes(tom, Activity). Последняя строка в программе ch02e01.pro является правилом: likes(bill, Activity)likes(tom, Activity). Это правило соответствует предложению естественного языка: Биллу нравится занятие, если Тому' нравится это занятие. (Bill likes an activity if Tom likes that activity) В данном правиле заголовок — это likes (bill,. Activity), а тело— likes (tom, Activity). Заметим, что в этом примере нет фактов о том, что Билл лю- бит бейсбол. Чтобы выяснить, любит ли Билл бейсбол, можно дать Прологу такой запрос: likes(bill, baseball).
228 Часть IV. Программирование на Visual Prolog Пытаясь отыскать решение по этому запросу, Пролог будет использовать правило: likes(bill, Activity):- likes(tom, Activity). Загрузите программу ch02e01.pro в среду визуальной разработки Visual Prolog и за- пустите ее утилитой Test Goal (см. разд. "Тестирование примеров данного руководства" гл. 8). predicates likes(symbol,symbol) clauses likes(ellen,tennis) . likes(john,football). likes(tom,baseball). likes(eric,swimming). likes(mark, tennis) . likes(bill,Activity) likes(tom, Activity). goal likes(bill, baseball). Утилита Test Goal ответит в окне приложения: yes (да) Система использовала комбинированное правило likes(bill, Activity)likes(tom, Activity). с фактом likes(tom, baseball). для решения, что likes(bill, baseball). Попробуйте также следующий запрос в GOAL-разделе: likes(bill, tennis). Утилита Test Goal ответит: no (нет) Visual Prolog ответит no на последний запрос ’’Does Bill like tennis?" (Любит ли Билл теннис), поскольку: □ нет фактов, которые говорят, что Билл любит теннис; □ отношение Билла к теннису не может быть логически выведено с использовани- ем данного правила и имеющихся в распоряжении фактов. Вполне возможно, что Билл любит теннис в реальной жизни, но ответ Visual Prolog основан только на фактах и правилах, которые вы дали ему в тексте программы.
Глава 10. Основы языка Visual Prolog 229 Переменные: общее представление В Прологе переменные позволяют вам записывать общие факты и правила и задавать общие вопросы. В естественном языке вы пользуетесь переменными в предложениях постоянно. Обычное предложение на английском языке может быть таким: Sill likes the same thing as Kim. (Билл любит то же, что и Ким) Как мы говорили, при задании переменной в Прологе первый символ имени должен быть заглавной буквой или символом подчеркивания. Например, в следующей стро- ке Thing — это переменная. 2ikes(bill, Thing):- likesfkim, Thing). В предшествующем примере: Likes(cindy, Something):- likes(bill, Something). объект Something начинается с заглавной буквы, т. к. это переменная; он определяет что-то, что Билл любит. С таким же успехом этот объект мог бы называться х или Zorro. Объекты bill и cindy начинаются со строчной буквы, т. к. они не являются пере- менными — это идентификаторы, имеющие постоянное значение. Visual Prolog мо- жет обрабатывать произвольные текстовые строки подобно тому, как мы оперирова- ли символами, упомянутыми выше, если текст заключен в двойные кавычки. Следо- вательно, вместо bill вы могли бы также успешно написать "Bill". Краткий обзор I. Программы на языке Пролог состоят из двух типов фраз: фактов и правил, также называемых предложениями. • Факты — это отношения или свойства, о которых известно, что они имеют значение '‘истина*'. • Правила — это связанные отношения; они позволяют Прологу логически вы- водить одну порцию информации из другой. Правило принимает значение "истина", если доказано, что заданный набор условий является истинным. 2. В Прологе все правила имеют 2 части: заголовок и тело, разделенные специаль- ным знаком • Заголовок — это факт, который был бы истинным, если бы были истинными несколько условий. Это называется выводом или зависимым отношением. • Тело — это ряд условий, которые должны быть истинными, чтобы Пролог мог доказать, что заголовок правила истинен. Как вы уже, наверное, заметили, факты и правила — практически одно и то же, кроме того, что факты не имеют явного тела. Факты ведут себя так, как если бы они имели тело, которое всегда истинно. Пролог всегда ищет решение,, начиная с первого факта и/или правила, и просматри- вает весь список фактов и/или правил до конца.
230 Часть IV. Программирование на Visual Prolog Механизм логического вывода Пролога берет условия из правила (тело правила) и просматривает список известных фактов и правил, пытаясь удовлетворить условиям. Если все условия истинны, то зависимое отношение (заголовок правила) считается истинным. Если все условия не могут быть согласованы с известными фактами, то правило ничего не выводит. Упражнения Переведите следующие предложения на естественный язык (помните, что для ком- пьютера эти факты являются просто порциями информации, которые могут быть использованы для согласования ответов с вопросами). 1. likes(jeff, painting). 2. male(john). 3. building ("Empire State Building", new__york) . 4. person(roslin, jeanie, "1429 East Sutter St.", "Scotts Valley", "CA", 95066). От естественного языка к программам Ранее в этой главе мы говорили о фактах и правилах, отношениях, основных конст- рукциях и запросах. Все эти термины являются частью логики и естественного язы- ка. Сейчас мы будем обсуждать те же понятия, но используя термины Пролога, — предложения, предикаты, переменные и цели. Предложения По сути, есть только два типа фраз, составляющих язык Пролога: фраза может быть либо фактом, либо правилом. Эти фразы в Прологе известны под термином предло- жения (clause). Сердце программ на Прологе состоит из предложений. Подробнее о фактах. Факт представляет либо свойство объекта, либо отношение между объектами. Факт самодостаточен. Прологу не требуется дополнительных све- дений для подтверждения факта, и факт может быть использован как основа для логического вывода. Подробнее о правилах. В Прологе, как и в обычной жизни, можно судить о досто- верности чего-либо, логически выведя это из других фактов. Правило — это конст- рукция Пролога, которая описывает, что можно логически вывести из других дан- ных. Правило — это свойство или отношение, которое достоверно, когда известно, что ряд других отношений достоверен. Синтаксически эти отношения разделены запятыми. Рассмотрим несколько примеров работы с правилами. 1. Пример, иллюстрирующий правило, которое может быть использовано для того, чтобы сделать вывод, подходит ли некоторое блюдо из меню Диане: Диана — вегетарианка и ест только то, что говорит ей ее доктор. (Diane is a vegetarian and eats only what her doctor tells her to eat)
Гпава 10. Основы языка Visual Prolog 231 Зная меню и предыдущее правило, вы можете сделать вывод о том, выберет ли Диана данное блюдо. Чтобы выполнить это, вы должны проверить, соответствует ли блюдо заданному ограничению: • является ли Food_on__menu овощем? • находится ли Food_on__menu в списке доктора? • заключение: если оба ответа положительны, Диана может заказать Fo od_on_me nu. В Прологе подобное отношение должно быть выражено правилом, т. к. вывод основан на фактах. Вот один вариант записи правила: diane_can_eat(Food_on_menu) vegetable(Food_on_menu)t on_doctor_list(Food_on_menu). Обратите внимание, что после vegetable(Food_on_menu) стоит запятая. Она обо- значает конъюнкцию нескольких целей и читается как "и"; оба правила — vegetable (Food_on_menu) И on_ doctor_list (Food_on_menu) — должны быть ИС- ТИННЫ ДЛЯ истинности diane_can_eat (Food_on_menu) . 2. Предположим, что вам хотелось бы записать факт, который истинен, если Personl является родителем Регзоп2. Нужный факт выглядит так: parent(paul, samantha). Этот факт обозначает, что Пол — родитель Саманты. Но, предположим, что в ба- зе данных фактов Visual Prolog есть факты, формулирующие отношение отцовст- ва. Например, "Пол — отец Саманты": father(paul, samantha). Пусть у вас также есть факты, формулирующие отношение материнства, напри- мер, "Джулия — мать Саманты": mother(julie, samantha). Если у вас уже есть несколько фактов, формулирующих отношения отцовст- ва/материнства, то не стоит тратить время на запись факта родства в базу данных фактов для каждого отношения родства. Так как вы знаете, что Personl — родитель Person!, если Personl — отец Person2 или Personl — мать Person2, то почему бы не записать правило, объединяющее эти ограничения? После формулирования этих условий на естественном языке достаточно просто записать их в правило Пролога. parent(Personl, Person2)father(Personl, Person!). parent(Personl, Person2)mother(Personl, Person!) . Эти правила Пролога говорят, что Personl является родителем Person!, если Personl является отцом Person!. И Personl является родителем Регзоп2, если Personl является матерью Personl.
232 Часть IV. Программирование на Visual Prolog 3. Вот другой пример: Человек может купить машину, если машина ему нравится (likes), и если машина продается (for sale). Это отношение может быть переведено на язык Пролог следующим правилом: can_buy(Name, Model):- person(Name)r car(Model), likes(Name, Model), for_sale(Model). Это правило выражает следующие отношения: Name может купить (can_buy) Model, если Name является человеком (person), и Model является машиной (саг), и Name нравится (likes) Model, и Model продается (for_sale). Это правило будет истинным, если истинны все 4 условия в теле правила. 4. В листинге 10.1 представлена программа ch02e02.pro, которая ищет решение про- блемы покупки автомобиля; протестируйте ее, как это описано в разд. "Тес- тирование примеров данного руководства" гл. 8. V1 -Ч м М • *! 11 v.t’! •МЛ?-’?’’ ♦ »£*• ММ Листинг 10.1. Программа ch02e02.pro . .......................... Юг? predicates can_buy(symbol, symbol) person(symbol) car(symbol) likes(symbol, symbol) for_sale(symbol) clauses can_buy(X,Y) person(X), car(Y), likes(X,Y), for_sale (Y). person(kelly). person(judy). person(ellen). person(mark). car(lemon). car(hot_rod). likes(kelly, hot_rod). likes(judy, pizza).
Глава 10. Основы языка Visual Prolog 233 likes(ellen, tennis). likes(mark, tennis). for_sale(pizza). for_sale(lemon). for_sale(hot_rod). Что могут купить Джуди и Келли? Кто может купить hot_rod? Испытайте сле- дующие цели в разделе GOAL: can_buy(Who, What). can_buy (judy, What) . can_buy(kelly, What). can_buy(Who, hot_rod). А теперь проведем такой эксперимент: добавим другие факты и новые правила к этой программе. Протестируйте сами программу теми запросами, что вы соста- вили ранее. Оправдали результаты ваши ожидания или нет? ч Упражнения 1. Напишите предложения на естественном языке, соответствующие следующим правилам Visual Prolog: eats(Who, What):- food(What), likes(Who, What). pass_class(Who)did_homework(Who), good_attendance(Who). owns(Who, What):- bought(Who, What). 2. По данным предложениям составьте правила Visual Prolog: • человек голоден, если его желудок пуст; • каждому нравится работа, если она веселая и хорошо оплачиваемая; • обладает автомобилем тот, кто купил его, платит за него и управляет им. Предикаты Отношение в Прологе называется предикатом. Аргументы — это объекты, которые связываются этим отношением; в факте likes (bill, cindy) отношение likes — это предикат, а объекты bill и cindy — аргументы. Вот несколько примеров предикатов с различным числом аргументов: pred(integer, symbol) person(last, first, gender) run () birthday(firstName, lastName, date) В вышеприведенном примере показано, что предикаты могут вовсе не иметь аргу- ментов, но использование таких предикатов ограничено. Чтобы выяснить имя Mr. Rosemont, можно применить запрос person (rosemont,Name,male) . Но что делать
234 Часть IV. Программирование на Visual Prolog с запросом без аргументов run? Выясним, есть ли в программе предложение run, и если run — это заголовок правила, то можно вычислить данное правило. В некото- рых случаях это оказывается полезным — например, если бы вы захотели создать программу, работающую по-разному в зависимости от того, имеется ли предложение run. Переменные В простом запросе, чтобы найти того, кто любит теннис, можно использовать пере- менные. Например: likes(X, tennis). В этом запросе буква х используется как переменная для нахождения неизвестного человека. Имена переменных в Visual Prolog должны начинаться с заглавной буквы (или с символа подчеркивания), после которой может стоять любое количество букв (заглавных или строчных), цифр или символов подчеркивания. Ниже приведены правильные имена переменных: My_first_correct_variable_name Sales_10__ll_86 следующие три — неправильные: lstattempt second_attempt "disaster" Удобно использовать в названии переменной буквы разного регистра: IncomeAndExpenditureAccount Осмысленный выбор имен переменных делает программу более удобной для чтения. Например: likes(Person, tennis). лучше, чем likes(X, tennis). потому что Person имеет больше смысла, чем х. Теперь испытайте цель: GOAL likes(Person, tennis). Visual Prolog ответит: Person=ellen Person=mark 2 Solutions li т. к. цель может получить два решения, а именно, сопоставляя переменную Person последовательно со значениями ellen и mark.
Глава 10. Основы языка Visual Prolog 235 Инициализация переменных Вы уже могли заметить, что Пролог не имеет оператора присваивания. Это важное отличие Пролога от других языков программирования. Внимание! Переменные в Прологе инициализируются при сопоставлении с константами в фак- тах или правилах. До инициализации переменная свободна; после присвоения ей значения она стано- вится связанной. Переменная остается связанной только то время, которое необхо- димо для получения решения по запросу, затем Пролог освобождает ее и ищет дру- гое решение. Внимание! Нельзя сохранить информацию, присвоив значение переменной. Переменные ис- пользуются как часть процесса поиска решения, а не как хранилище информации. В примере, приведенном в листинге 10.2, программа ch02e03.pro может быть рас- смотрена в качестве иллюстрации того, как и когда переменные получают свои зна- чения. |Листинг10.2.Программа ch02e03.pro 'Й j=С predicates likes(symbol,symbol) clauses likes (ellen, re., ,ing) . likes(john,computers). likes(john,badminton). likes(leonard,badminton). likes(eric,swimming). likes(eric,reading). Рассмотрим запрос: есть ли человек, который любит и чтение, и плавание? likes(Person, reading), likes(Person, swimming). Пролог будет решать обе части запроса посредством поиска фактов с начала и до конца программы. В первой части запроса likes(Person, reading) переменная Person свободна; ее значение неизвестно перед тем, как Пролог попыта- ется найти решение. С другой стороны, второй аргумент, reading, известен. Пролог ищет факт, который соответствует первой части запроса. Первый факт в программе likes(ellen, reading) удовлетворяет первой части запроса (reading в факте соответствует reading в запро- се), значит Пролог связывает свободную переменную Person со значением ellen,
236 Часть IV. Программирование на Visual Prolog соответствующим значению в факте. В то же время Пролог помещает указатель в список фактов, показывающий, как далеко продвинулась процедура поиска. Далее, для полного разрешения запроса (поиск человека, которому нравится и чте- ние, и плавание) должна быть решена вторая часть запроса. Так как Person сейчас связана со значением ellen, Пролог должен искать факт likes(ellen, swimming) Пролог ищет этот факт от начала программы, но совпадений нет (потому что в про- грамме нет такого факта). Вторая часть запроса ложна, если Person имеет значение ellen. Теперь Пролог освободит переменную Person и попытается найти иное решение первой части запроса. Поиск другого факта, удовлетворяющего первой части запро- са, начинается с указателя в списке фактов (такое возвращение к отмеченной пози- ции называется поиском с возвратом, это понятие подробно разъясняется в гл. II). Пролог ищет следующего человека, кто любит чтение и находит факт likes (eric, reading). Переменная Person сейчас связана со значением eric, и Пролог пытается вновь найти соответствие со второй частью запроса посредством поиска в программе факта: likes(eric, swimming) Пролог находит совпадение (последнее предложение в программе), и запрос пол- ностью удовлетворяется. Пролог (Test Goal) возвращает Person=eric. 1 Solution. Анонимные переменные Анонимные переменные позволяют "привести в порядок" наши программы. Если вам нужна только определенная информация запроса, можно использовать аноним- ные переменные для игнорирования ненужных значений. В Прологе анонимные переменные обозначаются символом подчеркивания (_). Следующий "семейный" пример (листинг 10.3) демонстрирует использование ано- нимных переменных. Загрузите программу ch02e04.pro в TestGoal-проект. |Йистий1‘:;:4о;3;ПрЬграмма.;сЬр^О4<-рго:-:;:;;;;" бниЦПН UL.ULt.tLfLLLLUtfLfUtLtOtUULLLtLtLLLLHLtmttrLttttttttLMtttMtM.ttlttLtHtlLHLu.LLtUUUUllltuUllllliuUUltU t t t >>>» L»i>> t a la a • * predicates male(symbol) female(symbol) parent(symbol, symbol) clauses male(bill). male(joe). female(sue). female (tammy) .
Глава 10. Основы языка Visual Prolog 237 parent(bill,joe). parent(sue,joe). parent(joe,tammy). Внимание! Анонимная переменная может быть использована на месте любой другой перемен- ной и ей никогда не присваивается значение. Например, в следующем запросе нам понадобится узнать, какие люди являются ро- дителями, но вам неинтересно, кто их дети. Пролог знает, что каждый раз, когда вы используете символ подчеркивания в запросе, вам не нужна информация о значе- нии, представленном на месте переменной. goal parent(Parent, _). Получив такой запрос, Пролог (Test Goal) отвечает: Parent^bill Parent=sue Parent=joe 3 Solutions В этом случае Пролог находит и выдает трех родителей, но он не выдает значения, связанные со вторым аргументом в предложении parent. Анонимные переменные также можно использовать в фактах. Следующие факты Пролога: owns(_, shoes). eats (_) . могли быть использованы для выражения утверждений на естественном языке: У каждого есть туфли. (Everyone owns shoes) Каждый ест. (Everyone eats) Анонимные переменные сопоставляются с любыми данными. Цели (запросы) До сих пор мы, говоря о вопросах, задаваемых Прологу, употребляли слово "запрос1. Далее мы будем использовать более общее слово "цель". Трактовка запросов как це- лей такова: когда вы даете Прологу запрос, в действительности вы даете ему цель для выполнения. ("Найди ответ на вопрос, если он существует: ...") Цели могут быть или простыми: likes(ellen, swimming). likes(bill, What).
238 Часть IV. Программирование на Visual Prolog или более сложными. В разд. "Переменные" данной главы вы видели цель из двух час- тей: likes(Person, reading), likes(Person, swimming). Цель, состоящая из двух и более частей, называется сложной целью, а каждая часть сложной цели называется подцелью. Часто бывает нужно найти общее решение двух целей. Например, в предыдущем примере о родителях вы могли бы поинтересоваться, которые из родителей являются родителями мужского пола. Искать решение для такого запроса можно, задав слож- ную цель. Загрузите программу ch02e04.pro и задайте следующую сложную цель: Goal parent(Person, _), male(Person). Сначала Пролог попытается решить подцель parent(Person, _) путем поиска соответствующего предложения и последующего связывания перемен- ной Person со значением, возвращенным parent (Person — это родитель). Значение, возвращаемое parent, далее предоставляется второй подцели в качестве значения, с которым будет продолжен поиск: является ли Person — теперь это связанная пере- менная — мужского пола? male(Person) Если цель задана корректно, Пролог (Test Goal) ответит: Person^bill Person-jое 2 Solutions Составные цели: конъюнкция и дизъюнкция Как вы уже видели, составные цели можно использовать для поиска решения, в ко- тором обе подцели А и В истинны (конъюнкция), разделяя подцели запятой. Вы так- же можете искать решения в том случае, если истинна либо подцель А, либо под- цель В (дизъюнкция) , разделяя подцели точкой с запятой. Ниже представлен пример программы ch02e05.pro (листинг 10.4), иллюстрирующей эту идею. ft. ft ft* *<ft * * * !»:’М ‘ J '.4 4 * “ ? “ * ‘ * * 44 * * * ч ••* ’ * ft-* - “ “V * * *• * ft .»•'•-* ? * * 4 *• *• • • * ft • ft• ft.< *:ft T-ft.* ft * * ft* * !.?•’* * 4 * * * *•* *t «♦?; 4 <♦♦« »»»»»»4? <« < * < » J ft ft ft ft ft *ft»•. ft. ft ftft* Л» • ft * <ft * * ?*-ft ftft ft ftft. ft*.** ft ft U ft* > ft* j Листинг 10.4. Программа ch02e05.pro ^«U^*****^ ***• *-« »'•' «« « Л** «4***1 predicates car(symbol,long,integer,symbol,long) truck(symbol,long,integer,symbol,long) vehicle(symbol,long,integer,symbol,long) clauses car(Chrysler,130000,3,red,12000). car(ford,90000,4,gray,25000). car(datsun,8000,1,red,30000) .
Глава 10, Основы языка Visual Prolog 239 truck(ford,80000,6,blue,8000). truck(datsun,50000,5,orange,20000). truck(toyota,25000,2,black,25000). vehicle(Make,Odometer,Age,Color,Price):- car(Make,Odometer,Age,Color,Price) 9 truck(Make,Odometer,Age,Color,Price). Загрузите эту программу в проект TestGoal, а затем добавьте цель: goal car(Make, Odometer, Years_on_road, Body, 25000). Данная цель попытается найти описанную в предложениях машину (саг), которая стоит ровно $25 000. Пролог ответит: Make=ford, 0dometer=90000, Years_on_road=4, Body=gray 1 Solution Однако данная цель несколько неестественна, т. к. скорее всего будет задан вопрос типа: Есть ли в списке машина, стоящая меньше, чем $25000? (Is there a car listed that costs less than $25000?) Для поиска такого решения вы можете задать Visual Prolog следующую составную цель: car(Make, Odometer, Years_on_road, Body, Cost), % подцель A И Cost < 25000. % подцель В Это и является конъюнкцией. Для разрешения этой составной цели Пролог будет пытаться по очереди решать подцели. Вначале он попытается решить: car(Make, Odometer, Years_on_road, Body, Cost). а затем Cost < 25000. с переменной Cost, имеющей идентичное значение в обеих подцелях. Теперь попы- тайтесь проделать все это с Test Goal. ' Замечание 7) Подцель cost < 25 000 соответствует отношению "меньше чем", которое встроено в систему Visual Prolog. Отношение "меньше чем" ничем не отличается от любого другого отношения, использующего два числовых объекта, но правильнее приме- нять для его обозначения символ (<), помещая его между двумя объектами.
240 Часть IV, Программирование на Visual Prolog Посмотрим, является ли истинным следующее выражение, на естественном языке оно звучит так: Есть ли в списке автомобиль, стоимостью меньше $25 000, или грузовик стоимостью меньше $20 000? При задании следующей составной цели Пролог выполнит поиск требуемого ре- шения: car(Make,Odometer,Years_on_road,Body,Cost), Cost < 25000 % подцель A ИЛИ truck(Make,Odometer,Years_on_road,Body,Cost), Cost < 20000. % подцель В Этот тип составной цели является дизъюнкцией. Данная цель установила две аль- тернативные подцели так же, как если бы это были два предложения одного прави- ла. Пролог будет искать все решения, удовлетворяющие обеим подцелям. При разрешении такой составной цели Пролог вначале попытается решить первую подцель ("найти автомобиль..."), состоящую из следующих подцелей: car(Make, Odometer, Years_on_road, Body, Cost) И Cost < 25000. Если автомобиль найдется — цель истинна; если нет — Пролог попытается разре- шить вторую составную цель ("найти грузовик..."), состоящую из подцелей: truck(Make, Odometer, Years_on_road, Body, Cost), И Cost < 20000. Комментарии Хорошим стилем программирования является включение в программу комментари- ев, объясняющих все то, что может быть непонятно кому-то другому (или даже вам, спустя пол го да). Если вы подберете подходящие имена для переменных, предикатов и доменов, то вам понадобится меньше комментариев, т. к. программа будет объяс- нять себя "сама". Многострочные комментарии должны начинаться с символов /* (косая черта, звез- дочка) и завершаться символами */ (звездочка, косая черта). Для установки одно- строчных комментариев можно использовать либо эти же символы, либо начинать комментарий символом процента (%). /* Это первый пример комментария */ % Это второй пример комментария А эти три строчки — пример многострочного комментария -k-k'k-k-k-kir-k-k-k-k-k'fcic-k-k'k'fr-kic'k'k-k-k-k-k-kicic-k'kic-k'kic-k'k'k'kf /*Вы также можете поместить комментарий Visual Prolog /^внутри комментария */ как здесь*/
Глава 10. Основы языка Visual Prolog 241 В Visual Prolog можно использовать комментарий после каждого субдомена в объяв- лении доменов: domains articles = bookfstring Title, string Author); horse(string Name) и в объявлениях предикатов: predicates conv(string Uppercase,string Lowercase) a Слова Title, Author, Name, uppercase и Lowercase будут проигнорированы компиля- тором, но сделают программу более читабельной. Сопоставление В предыдущих разделах вы познакомились с тем, как Пролог "сопоставляет вопросы и ответы", "ищет сопоставление", "сопоставляет условия с фактами", "сопоставляет переменные с константами" и т. д. Ниже рассмотрим, что же понимается под тер- мином сопоставление (matching). В Прологе имеется несколько примеров сопоставления одной вещи с другой. Ясно, что идентичные структуры сопоставимы (сравнимы) друг с другом: parent (joe, tammy) сопоставимо с parent (joe, tammy). Однако сопоставление (сравнение) обычно использует одну или несколько свободных переменных. Например, если х свободна, то parent (joe, х) сопоставимо с par- ent (joe, tammy) и х принимает значение (связывается с) tammy. Если же х уже связана, то она действует так же, как обычная константа. Таким образом, если х связана со значением tammy, то parent (joe, X) сопоставимо cparent(joe, tammy), но parent (joe, X) не сопоставимо c parent (joe, millie). В предыдущем примере сопоставление не выполняется, т. к. если переменная стано- вится связанной, то ее значение не может изменяться. Как может переменная оказаться связанной при попытке Пролога сопоставить ее с чем-либо? Вспомним, что переменные не могут хранить значения, т. к. они стано- вятся связанными только на промежуток времени, необходимый для отыскания (или попытки отыскания) одного решения цели. Поэтому имеется только одна возмож- ность для переменной оказаться связанной — перед попыткой сопоставления, если цель требует больше одного шага, и переменная стала связанной на предыдущем шаге. Например: parent(joe,X), parent(X,jenny) является корректной целью. Она означает: "Найти кого-либо, являющегося ребен- ком Joe и родителем Jenny", Здесь при достижении подцели parent (X, jenny) пере- менная х уже будет связана. Если для подцели parent (х, jenny) нет решений, Про- лог "развяжет" переменную х и вернется назад, пытаясь найти новое решение для parent (joe,X), а затем проверит, будет ли "работать" parent (X, jenny) с новым зна- чением х. Две свободные переменные могут сопоставляться друг с другом. Например, parent (joe, X) сопоставляется с parent (joe, Y), связывая при этом переменные X
242 Часть IV. Программирование на Visual Prolog и Y между собой. С момента "связывания" х и Y трактуются как одна переменная, и любое изменение значения одной из них приводит к немедленному соответствую- щему изменению другой. В случае подобного "связывания" между собой нескольких свободных переменных все они называются совмещенными свободными переменными Некоторые методы программирования специально используют "взаимосвязывание’ свободных переменных, являющихся, на самом деле, различными. В Прологе связывание переменных (со значениями) производится двумя способами; на входе и выходе. Направление, в котором передаются значения, указывается в шаблоне потока параметров (flow pattern). В дальнейшем (для краткости) будем опускать слово "шаблон" и говорить просто "поток параметров". Когда переменная передается в предложение, она считается входным аргументом и обозначается симво- лом (i). Когда же переменная возвращается из предложения, она является выходным аргументом и обозначается символом (о). Программы Visual Prolog Синтаксис .Visual Prolog разработан для того, чтобы отображать знания о свойствах и взаимосвязях. В основном, вы уже видели, как это делается: в данной главе мы рассматривали предложения (факты и правила), предикаты, переменные и цели. В отличие от других версий Пролога, Visual Prolog — компилятор, контролирующий типы: для каждого предиката объявляются типы объектов, которые он может ис- пользовать. Это объявление типов позволяет программам Visual Prolog быть скомпи- лированными непосредственно в машинные коды, при этом, скорость выполнения сравнима, а в некоторых случаях — и превышает скорости аналогичных программ на языках С и Pascal. Теперь обсудим четыре основных раздела программ на Visual Prolog — те, где объяв- ляются и описываются предикаты и типы аргументов, задаются правила и определя- ется цель программы. Далее более подробно рассмотрим синтаксис правил и объяв- лений. И наконец, в заключение кратко опишем другие разделы программ: базы данных, константы, различные глобальные разделы и директивы компилятора. Основные разделы Visual Prolog-программ Обычно программа на Visual Prolog состоит из четырех основных программных раз- делов. К ним относятся: □ раздел clauses (предложений); □ раздел predicates (предикатов); □ раздел domains (доменов); □ раздел goal (целей). Раздел clauses — это сердце Visual Prolog-программы; именно в этот раздел записы- ваются факты и правила, которыми будет оперировать Visual Prolog, пытаясь разре- шить цель программы. Раздел predicates — это тот, в котором объявляются предикаты и домены (типы) их аргументов (вам не нужно объявлять предикаты, встроенные в Visual Prolog).
Глава 10. Основы языка Visual Prolog 243 Раздел domains служит для объявления всех используемых вами доменов, не являю- щихся стандартными доменами Visual Prolog (стандартные домены объявлять не нужно). Раздел goal — это тот, в который вы помещаете цель Visual Prolog-программы. Раздел предложений В раздел clauses (предложений) вы помещаете все факты и правила, составляющие вашу программу. Основное внимание в этой главе было уделено рассмотрению предложений (фактов и правил) программы: что они означают, как их писать и т. д. Если вы поняли, что собой представляют факты и правила и как их записывать в Прологе, то вы знаете, что все предложения для каждого конкретного предиката в разделе clauses должны располагаться вместе. Последовательность предложений, описывающих один предикат, называется процедурой. Пытаясь разрешить цель, Visual Prolog (начиная с первого предложения раздела clauses) будет просматривать каждый факт и каждое правило, стремясь найти со- поставление. По мере продвижения вниз по разделу clauses, он устанавливает внут- ренний указатель на первое предложение, являющееся частью пути, ведущего к ре- шению. Если следующее предложение не является частью этого логического пути, то Visual Prolog возвращается к установленному указателю и ищет очередное подходя- щее сопоставление, перемещая указатель на него (этот процесс называется поиск с возвратом). Раздел предикатов Если в разделе clauses программы на Visual Prolog вы описали собственный предикат, то вы обязаны объявить его в разделе predicates (предикатов); в противном случае Visual Prolog не поймет, о чем вы ему ’’говорите". В результате объявления предиката вы сообщаете, к каким доменам (типам) принадлежат аргументы этого предиката. Visual Prolog поставляется с большим набором встроенных предикатов (их не нужно объявлять), а интерактивное справочное руководство предоставляет полное их опи- сание. Предикаты задают факты и правила. В разделе же predicates все предикаты просто перечисляются с указанием типов (доменов) их аргументов. Эффективность работы Visual Prolog значительно возрастает именно из-за того, что вы объявляете типы объ- ектов (аргументов), с которыми работают ваши факты и правила. Как объявить пользовательский предикат Объявление предиката начинается с имени этого предиката, за которым идет откры- вающая (левая) круглая скобка, после чего следует ноль или больше доменов (типов) аргументов предиката: predicateName(argument_typel OptionalNamel, argument_type2 0ptionalName2, ..., argument typeN 0ptionalName3)
244 Часть IV. Программирование на Visual Prolog После каждого домена (типа) аргумента следует запятая, а после последнего типа аргумента ~ закрывающая (правая) скобка. Отметим, что, в отличие от предложе- ний в разделе clauses, декларация предиката не завершается точкой. Доменами (ти- пами) аргументов предиката могут быть либо стандартные домены, либо домены, объявленные вами в разделе domains. Можно указывать имена аргументов OptionalNameK — это улучшает читаемость программы, и не сказывается на скорости ее исполнения, т. к. компилятор их игнорирует. Имена предикатов Имя предиката должно начинаться с буквы, за которой может располагаться после- довательность букв, цифр и символов подчеркивания. Регистр букв не имеет значе- ния, однако мы не советуем вам использовать заглавные буквы в качестве первой буквы имени предиката (другие версии Пролога не разрешают, чтобы имя предиката начиналось с заглавной буквы, и будущая версия Visual Prolog — версия 6, тоже будет запрещать это). Имя предиката может иметь длину до 250 символов. В именах предикатов запрещается использовать пробел, символ минус, звездочку и другие алфавитно-цифровые символы. Корректные имена Visual Prolog могут включать символы, перечисленные в табл. 10.2. Таблица 10.2. Символы, используемые в именах предикатов в Visual Prolog Название символов Примеры символов Заглавные буквы А, В,. ...» z Строчные буквы а, Ь........z Цифры 0, 1........9 Символ подчеркивания _ Имена предикатов и аргументов могут состоять из любых комбинаций этих симво- лов при условии, что вы подчиняетесь правилам построения соответствующих имен. В табл. 10.3 приведены корректные и некорректные имена предикатов. Таблица 10.3. Имена предикатов в Visual Prolog Корректные имена предикатов Некорректные имена предикатов Fact is_ а has_a PatternCheckList choose_Menu_Item PredicateName f irst_in__10 [fact] *is_a* has/a Pattern-Check-List Choose Menu Item Predicate<Name> >first_in_10
Гпава 10. Основы языка Visual Prolog 245 Аргументы предикатов Аргументы предикатов должны принадлежать доменам, известным Visual Prolog. Эти домены могут быть либо стандартными доменами, либо некоторыми из тех, что вы объявили в разделе доменов. Рассмотрим несколько примеров. 1. Если предикат myjoredicate(symbol, integer) объявлен в разделе predicates следующим образом: predicates my_predicate(symbol, integer) то вам не нужно в разделе domains декларировать домены его аргументов, т. к. symbol и integer — стандартные домены. Однако если этот же предикат вы объ- являете так*. predicates myjoredicate(name, number), то необходимо объявить, что name (символический тип) и number (целый тип) принадлежат к стандартным доменам symbol и integer: domains name = symbol number = integer predicates my_predicate(name, number) 2. Следующий фрагмент программы показывает несколько различных объявлений доменов и предикатов: domains person, activity = symbol car, make, color = symbol mileage, years_on_road, cost = integer predicates likes(person, activity) parent(person, person) can_buy(person, car) car(make, mileage, years_on_road, color, cost) green(symbol) ranking(symbol, integer) Этот фрагмент сообщает следующую информацию об этих предикатах и их аргу- ментах: □ предикат likes имеет два аргумента (person и activity), причем они оба при- надлежат домену symbol (это означает, что их значениями являются идентифика- торы, а не числа);
246 Часть IV. Программирование на Visual Prolog □ предикат parent имеет два аргумента (person), причем каждый из person отно- сится к домену symbol; □ предикат can_buy имеет два аргумента (person и саг), которые относятся к типу symbol; □ предикат саг имеет 5 аргументов: make и color относятся к домену symbol, a mileage, years_on_road и cost — к домену integer; □ предикат green имеет один аргумент типа symbol; нет необходимости деклариро- вать этот тип аргумента, т. к. он относится к стандартному домену symbol; □ предикат ranking имеет два аргумента, каждый из которых принадлежит к стан- дартному домену (symbol и integer), поэтому декларировать типы этих аргумен- тов не требуется. В гл. 12 можно более подробно ознакомиться с декларацией доменов. Раздел доменов В традиционном Прологе есть только один тип — терм. В Visual Prolog мы объявля- ем домены всех аргументов предикатов. Домены позволяют задавать разные имена различным видам данных, которые, в противном случае, будут выглядеть абсолютно одинаково. В программах Visual Prolog объекты в отношениях (аргументы предикатов) принадлежат доменам, причем это могут быть как стандартные, так и описанные вами специальные домены. Раздел domains служит двум полезным целям. Во-первых, вы можете задать доменам осмысленные имена, даже если внутренне эти домены аналогичны уже имеющимся стандартным. Во-вторых, объявление специальных доменов используется для описа- ния структур данных, отсутствующих в стандартных доменах. Иногда очень полезно описать новый домен — особенно, когда вы хотите прояснить отдельные части раздела predicates. Объявление собственных доменов, благодаря присваиванию осмысленных имен типам аргументов, помогает документировать описываемые вами предикаты. Рассмотрим несколько примеров. 1. Данный пример показывает, как объявление доменов помогает документировать предикаты: Франк — мужчина, которому 45 лет. Используя стандартные домены, вы можете так объявить соответствующий пре- ди кат: person(symbol, symbol, integer). В большинстве случаев такое объявление будет работать очень хорошо. Однако предположим, что несколько месяцев спустя после написания программы, вы решили скорректировать ее. Есть опасение, что подобное объявление этого пре- диката абсолютно ничего вам не скажет. И напротив, декларация этого же пре- диката, представленная ниже, поможет вам разобраться в том, что же представ- ляют собой аргументы данного предиката:
Глава 10. Основы языка Visual Prolog 247 domains name, sex = symbol age = integer predicates person(name, sex, age) Одним из главных преимуществ объявления собственных доменов является то, что Visual Prolog может отслеживать ошибки типов, например, такие: same_sex(X, Y) person(X, Sex, , person(Sex, Y, _). Несмотря на то, что и name и sex описываются как symbol, они не эквивалентны друг другу. Это и позволяет Visual Prolog определить ошибку, если вы перепутаете их. Это полезно в тех случаях, когда ваши программы очень велики и сложны. Почему же мы не можем использовать специальные домены для объявления всех аргументов, если они привносят больше смысла в обозначение аргументов? Ответ заключается в том, что аргументы с типами из специальных доменов не могут сме- шиваться между собой, даже если эти домены одинаковы. Именно поэтому, не- смотря на то, что name и sex принадлежат одному домену symbol, они не могут смешиваться. Однако все собственные домены пользователя могут быть сопос- тавлены стандартным доменам. 2. Следующий пример программы ch03e01.pro (листинг 10.5) при его загрузке при- ведет к ошибке типа (см. разд. "Тестирование примеров данного руководства" гл. 8). f ** ** * V-Я*-**-* W***^»*t|»'**!*«i****.!r*ljl»,*M.I»,!y**F*|,**.i •**.•!?•* *•*•*:*» ‘ * * м !Г ; ‘J Мд! м ? "V “;Л**.* ,***.*•,! *'Д-Л " *:*Л?** У'*?' " 1**1Йаа*Ь<Г«*»»*Г***й**6*« **0*?МЮ»*а'*Г»****й*4***»»ГйГ*««<*«***О»***«***М»*«*Ю*М »*• Г» «** * •••••»••••••»••• »•«•••*• *'*«*» 444144»*«*«*** **** domains product,sum = integer predicates add_em_up (*sum, sum, sum) multiply_em(product,product,product) clauses add_em_up (X, Y, Sum) : - Sum=X+Y. multiply_em(X,Y,Product) Product=X*Y. Эта программа выполняет две операции: складывает и умножает. Зададим ей сле- дующую цель: add_em_up(32, 54, Sum).
248 Часть IV. Программирование на Visual Prolog Visual Prolog (Test Goal) ответит: Sum=8 6 1 Solution что является суммой двух целых чисел, которые вы передали в программу. С другой стороны, эта же программа с помощью предиката muitiply_em умножа- ет два аргумента. Поэкспериментируем. Если вам нужно узнать произведение чи- сел 13 и 31, введите цель: multiply_em(31, 13, Product). Visual Prolog вернет вам корректный результат: Product=403 1 Solution Предположим, что вам понадобилась сумма чисел 42 и 17; цель выглядит так: add_em_up(42, 17, Sum). а А теперь, допустим, вы хотите удвоить произведение 31 на 17. Задаете следук>- щую цель: multiply_em(31, 17, Sum) , add_em_up (Sum, Sum, Answer) и ждете, что Visual Prolog (Test Goal) ответит: Sum=527, Answer=1054 1 Solution Однако вместо этого вы получите ошибку типа. Это случилось из-за того, что имела место попытка передать результирующее значение предиката multiplу_еа. которое относится к домену product, в качестве первого и второго аргументов (которые должны относится к домену sum) в предикат add_em_up. Это, собствен- но, и привело к ошибке, т. к. домен product отличается от домена sum. И хота оба эти домена соответствуют типу integer — это различные домены. Поэтому, если переменная в предложении используется более чем в одном предикате, она должна быть одинаково объявлена в каждом из них. Очень важно, чтобы вы поняли концепцию описанной здесь ошибки типа, что позволит избегать сооб- щений об ошибках компиляции. Различные автоматические и явные преобразо- вания типов, предлагаемые Visual Prolog, будут описаны ниже. 3. Как еще можно использовать объявления доменов для отслеживания ошибок типа, поможет увидеть следующий пример программы ch03e02.pro (листинг 10.61 »• Листинг 10.6. Программа ch03e02.pro JW.!»• “ *.М.М.*• •.*лм • - М» v V F * - * А- • • • - • • Ш"". S М м ;**»“ .• •I» •"•*• *Т*• V- •-^.5. ; ; У.-'.'. ?' Z'Ь"’’ ' Н ' '* ' . 'и1''. ,;; :? ! I ! 1 '!/у • • j J,' С. ' --S:J'? / ;* и•• * **, *•it•’• . Л нм г• • •• ** * » *» а. «-о• * ***• * .. ♦• * Л domains brand,color = symbol age ~ byte price, mileage = ulong
Глава 10. Основы языка Visual Prolog 249 predicates car(brand,mileage,age,color,price) clauses car(Chrysler,130000,3,red,12000). car(ford,90000,4,gray,25000). car(datsun,8000,1,black,30000). Здесь предикат car, объявленный в разделе predicates, имеет 5 аргументов. Один из них относится к домену аде типа byte. В семействе процессоров х86 тип byte — это 8-битное беззнаковое целое, которое может принимать значения от 0 до 255, включая границы. Аналогично, домены mileage и price типа ulong, кото- рый представляет собой 32-битные беззнаковые целые, а домены brand и color — символьного типа (symbol). Мы обсудим стандартные домены более детально далее. А сейчас загрузите дан- ную программу в проект TestGoal и попытайтесь вычислить по очереди следую- щие цели: car(renault, 13, 40000, red, 12000). car(ford, 90000, gray, 4, 25000). car(l, red, 30000, 80000, datsun). Каждая из них приведет к ошибке типа. В первом случае это произойдет из-за того, что аде должен быть типа byte. Следовательно, Visual Prolog сможет легко определить, что при вводе этой цели объекты mileage и аде в предикате саг были перепутаны местами. Во втором случае были перепутаны аде и color, а в треть- ем — попытайтесь найти ошибку самостоятельно. Раздел цели По существу, раздел goal (цели) аналогичен телу правила: это просто список подце- лей. Цель отличается от правила лишь следующим: □ за ключевым словом goal не следует : □ при запуске программы Visual Prolog автоматически выполняет цель. Это происходит так, как будто Visual Prolog вызывает goal, запуская тем самым про- грамму, которая пытается разрешить тело правила goal. Если все подцели в разделе goal истинны, — программа завершается успешно. Если же какая-то подцель из раздела goal ложна, то считается, что программа завершается неуспешно (хотя чисто внешне никакой разницы в этих случаях нет, — программа просто завершит свою работу). Декларации и правила В Visual Prolog есть несколько встроенных стандартных доменов. Их можно исполь- зовать при декларации типов аргументов предикатов без описания в разделе domains. Основные стандартные домены перечислены в табл. 10.4.
250 Часть IV. Программирование на Visual Prolog Таблица 10.4. Основные стандартные домены Домен Описание short Короткое, знаковое, количественное ushort Короткое, беззнаковое, количественное Реализация Все платформы 16 бит (-32 768—32 767) Все платформы 16 бит (0—65 535) long Длинное, знаковое, количественное ulong Длинное, беззнаковое, количественное integer Знаковое, количественное, имеет платформо-зависимый размер unsigned Беззнаковое, количественное, имеет платформо-зависимый размер byte word dword Все платформы 32 бит (-2 147 483 648-2 147 483 647) Все платформы 32 бит (0-4 294 967 295) Платформы 16 бит (-32 768-32 767) Платформы 32 бит (-2 147 483 648-2 147 483 647) Платформы 16 бит (0—65 535) Платформы 32 бит (0—4 294 967 295) Все платформы 8 бит (0— 55) Все платформы 16 бит (0—65 535) Все платформы 32 бит (0—4 294 967 295) Синтаксически значение, принадлежащее одному из целочисленных доменов, запи- сывается как последовательность цифр, которой в случае знакового домена может предшествовать не отделенный от нее пробелом знак минус. Имеются также восьме- ричные и шестнадцатеричные синтаксисы для основных доменов (будут рассмотре- ны в гл. 16). Домены типов byte, word и dword наиболее удобны при работе с машинными числа- ми. В основном используются типы integer и unsigned, а также short и long (и ин беззнаковые аналоги) для более специализированных приложений. В объявлениях доменов ключевые слова signed и unsigned могут использоваться вместе со стандартными доменами типов byte, word и dword для построения новы» базовых доменов. Так: domains i8 = signed byte создает новый базовый домен в диапазоне от —128 до + 127. Другие базовые домены показаны в табл. 10.5. Visual Prolog знает и несколько иньж стандартных доменов, но мы расскажем о них в последующих главах, когда вы буж^ те лучше знать основы языка.
Гпава 10. Основы языка Visual Prolog 251 Таблица 10.5. Основные стандартные домены Домен Описание и реализация char Символ, реализуемый как беззнаковый byte. Синтаксически это символ, за- ключенный между двумя одиночными кавычками: ' а' real Число с плавающей запятой, реализуемое как 8 байт в соответствии с согла- шением IEEE; эквивалентен типу double в С. Синтаксически числа с необяза- тельным знаком (+ или -), за которым следует несколько цифр ddddddd, затем необязательная десятичная точка (.) и еще цифры ddddddd, за которыми идет необязательная экспоненциальная часть (е(+ или -)ddd): <+(-> DDDDD <.> DDDDDDD <е <+|-> DDD> Примеры действительных чисел (real): 42705...9999...86.72 9111.929437521 е238...79.83е+21 Здесь 79.83е+21 означает 79.83x1021, как и в других языках. Допустимый диапазон чисел: от 1хЮ“307 до 1x10+3°8 (от 1е-307 до 1е+308). При необходимости, целые автоматически преобразуются в real чт I Bd М** М.*тът** Ь"." HLk 4* *. ^а.а.а.*а.**%Ь%%%%* Ьъъъмъм v»* ЬЪъ\\*т *%%%%%%% ^ч • • .•* Ч **Ч Ч Ч ЬЧА ъЧ*ъ*ъъч <.Ь*а.Ь*.Ч %*%.%.*%%%%%%% • •# * • • * .* ЬЧ Ь*.*.*.Ч *1Ч*ъ4ЬъъъЬ****** М* ► ' **LL**L*4M4<VLb*44M**vb******* 4*******MVb *<4*4«44<A4fc* 4 Ь*****^** *1ЫЪМ<М<<^*******Ш*Ы4< string Последовательность символов, реализуемых как указатель на байтовый мас- сив, завершаемый нулем, как в С. Для строк допускается два формата: 1. Последовательность букв, цифр и символов подчеркивания, причем пер- вый символ должен быть строчной буквой. 2. Последовательность символов, заключенных в двойные кавычки. Примеры строк: telephone_number "railway ticket” "Dorid Inc" Строки, которые вы пишете в программе, могут достигать длины в 255 символов, в то время как строки, которые система Visual Prolog считывает из файла или строит внутри себя, могут достигать (теоретически) до 4 Гбайт на 32-битных платформах . Ч Ч Ч Ч Ч *4 Ч *4 ч ч ч Ч*4 ь » гЧ ь ► Ч Ч *4 Ч I I -** Ч • Ч Ч Ч *М Ч •• *-1 •• 4 *•• 4 •*••••*• *м *4 Ч 4 ЬЧ *4 ЬЧ .•#Ч*...*.4**.к..*»..#.,...0М*4ЧГЧ^.4.^«»«**”«**«Ь««««”*»****ГГГГ*^*’”””»Н«*«««Ив* ч •** ч ч ч ч ч ч ч ч ч ч ч ч ***ч *ч • Ч Ьт ******* гг**** Г**Ь **********►”* к* Ь* ••*•**• ч Ч Ч Ч И Ч Ч Ч *4 Ч И* ******** Ьт IITI р*ч Г*’ *ч *ч *ч *4 ч ч *ч *4 4 *1 symbol Последовательность символов, реализуемых как указатель на вход в таблице идентификаторов, хранящей строки идентификаторов. Синтаксис — как для строк Идентификаторы и строки взаимозаменяемы в вашей программе, однако Visual Prolog хранит их раздельно. Идентификаторы хранятся в таблице идентификаторов, а для представления используются лишь их индексы в этой таблице, но не сами строки идентификаторов. Это означает, что сопоставление идентификаторов выпол- няется очень быстро, а в случае если они встречаются в программе несколько раз, то и хранение их компактно. Строки же не хранятся в поисковой таблице, и при необ- ходимости сопоставления Visual Prolog проверяет их символ за символом. Вы сами должны определять, какой домен лучше использовать в каждой конкретной про- грамме. В табл. 10.6 мы приводим несколько примеров простых объектов, принадлежащих к основным стандартным доменам.
252 Часть IV. Программирование на Visual Prolog symbol или string integer real char Таблица 10,6, Простые объекты Простой объект Домен caitlin, "animal lover", b_l_t -1, 3, 5, О 3.45, 0.01, -30.5, 123.4e+5 'af, fbf, *c', f/f, Задание типов аргументов при декларации предикатов Объявление доменов аргументов в разделе predicates называется заданием типов аргументов. Предположим, имеется следующая связь объектов: Франк — мужчина, которому 45 лет. Факт Пролога, соответствующий этому предложению естественного языка, может быть следующим: person(frank, male, 45). Для того чтобы объявить person (человек), как предикат с этими тремя аргументами, вы можете разместить в разделе predicates следующую строку: person(symbol, symbol, unsigned). Здесь для всех трех аргументов использованы стандартные домены. Отныне всякий раз при работе с предикатом person, вы должны передавать ему три аргумента, при- чем первые два должны быть типа symbol, а третий — типа integer. Если в программе используются только стандартные домены, то нет необходимости использовать раздел domain; вы уже видели несколько программ такого типа. Или, предположим, что вы хотите описать предикат, который сообщал бы позицию буквы в алфавите, т. е. цель alphabetjaosition(Letter, Position) должна вернуть вам Position = ' 1, если Letter a, Position = 2, если Letter = b и т. д. Предложения этого предиката могут выглядеть следующим об- разом : alphabetjaosition (A_character, N). Если при объявлении предиката используются только стандартные домены, то про- грамме не нужен раздел domains. Предположим, что вы хотите описать предикат так, что цель будет истинна, если a character является tV-m символом алфавита. Пред- ложения этого предиката будут такими: alphabe tjaosition (’ а', 1) . alphaber__posit ion (’b', 2) .
Гпава 10. Основы языка Visual Prolog 253 alphabet^position('с’, 3). alphabet^position(' z ', 26). Вы можете объявить данный предикат следующим образом: predicates aIphabet—position(char, unsigned) и тогда вам не будет нужен раздел domains. Если разместить все фрагменты про- граммы вместе, получим: predicates alphabet^position(char, integer) clauses alphabet^position(’a’, 1). alphabet^position('b', 2). alphabet^position('c', 3). % здесь находятся остальные буквы alphabet^position('z’, 26). Ниже представлено несколько простых целей, которые вы можете использовать: alphabet^position('а', 1). alphabet^position(X, 3). alphabet-position(’z', What). Упражнения 1. Программа ch03e04.pro (листинг 10.7) представляет собой законченную програм- му на Visual Prolog, служащую небольшим телефонным справочником. Так как используются только стандартные домены, раздел domains в этой программе не нужен. { Лиетинг 10.7. Программа ch03e04.pro predicates phone_number (symbol, symbol) clauses phone_number("Albert”,"EZY-3665"). phone_number("Betty", ”555-5233") . phone__number ("Carol", "909-1010”) . phone_number("Dorothy","438-8400"). goal Загрузите и запустите программу ch03e04.pro, а затем, по очереди, задайте ей сле- дующие цели: phone_number("Carol”, Number). phone_number(Who, "438-8400").
254 Часть IV. Программирование на Visual Prolog • phone__number("Albert", Number). • phone_number(Who, Number). Теперь измените предложения. Предположим, что Kim и Dorothy имеют один номер телефона. Добавим этот факт в раздел clauses и введем цель: phone_number(Who, "438-8400"). На этот запрос вы должны получить два решения: Who=Dorothy Who=Kim 2 Solutions 2. Для иллюстрации домена char в программе ch03e05.pro (листинг 10.8) описан предикат isletter (является буквой), который в случае задания ему приведенных ниже целей isletter('%'). isletter(’Q'). возвращает значения "No” и "Yes" соответственно. Л >«..<> ь > J, * ь , *• и• • . , м ин....1.. м. -11 77, . и..... н .7.7 7 мн;?* ; -17 V У - - • * Листинг 10.8. Программа ch03e05.pro predicates isletter(char) clauses % Будучи примененным к символам, знак <- обозначает % "предшествование или равенство позиций в алфавите" isletter(Ch):- 'а' <= Ch, Ch <== ’ z ’ . isletter(Ch):~ ’A’ <= Ch, Ch <= 'Z’. Загрузите и запустите программу ch03e05.pro в Test Goal и испытайте, по очере- ди, каждую из этих целей: a) isletter(1 х ‘). b) isletter (’2’). с) isletter("hello"). d) isletter(a). e) isletter(X). Цели (с) и (d) приведут к сообщению об ошибке типа, а цель (е) вернет сообще- ние "Free variable" (несвязанная переменная), т. к. вы не можете проверить стар- шинство незаданного объекта по отношению к а или z.
Глава 10. Основы языка Visual Prolog 255 Арность (размерность) Арность предиката — это количество аргументов, которые он принимает. Вы можете иметь два предиката с одним и тем же именем, но отличающейся арностью. В раз- делах predicates и clauses версии предикатов с одним именем и разной арностью должны собираться вместе; за исключением этого ограничения, различная арность всегда понимается как полное различие предикатов. Проиллюстрируем это примером (листинг 10.9). [Листинг 10.9. Программа сИОЗеОб.pro ............................ »ь«ivььь«ь»ь»bb»b».»l»l tit. « domains person = symbol predicates father(person) % этот person — отец father(person, person) % первый person является отцом другого clauses father(Man) father (Man, J . father(adam,seth). father(abraham, isaac) . Синтаксис правил Правила используются в Прологе в случае, когда какой-либо факт зависит от ис- тинности другого факта или группы фактов. Как мы объясняли ранее в этой главе, в правиле Пролога есть две части: заголовок и тело. Ниже представлен обобщенный синтаксис правила в Visual Prolog: HEAD: — <Subgoal>, <Subgoal>, <Subgoal>. Заголовок: — <Подцель>, <Подцель>, . . ., <Подцель>. Тело правила состоит из одной или более подцелей. Подцели разделяются запятыми, определяя конъюнкцию, а за последней подцелью правила следует точка. Каждая подцель выполняет вызов другого предиката Пролога, который может быть истинным или ложным. После того, как программа осуществила этот вызов, Visual Prolog проверяет истинность вызванного предиката, и если это так, то работа про- должается, но уже со следующей подцелью. Если же в процессе такой работы была достигнута точка, то все правило считается истинным; если хоть одна из подцелей ложна, то все правило ложно. Для успешного разрешения правила Пролог должен разрешить все его подцели и создать последовательный список переменных, должным образом связав их. Если же одна из подцелей ложна, Пролог вернется назад для поиска альтернативы предыду- щей подцели, а затем вновь двинется вперед, но уже с другими значениями пере- менных. Этот процесс называется поиск с возвратом. Подробное изложение данного процесса и того, как Пролог ищет решения, приводится в гл. И.
256 Часть IV. Программирование на Visual Prolog Как упоминалось выше, в качестве разделителя заголовка и тела правила Пролог использует знак : - , который читается как "если” (if). Однако if Пролога отличается от if, написанного в других языках, например в Pascal, где условие, содержащееся в операторе if, должно быть указано перед телом оператора, который может быть выполнен. Другими словами: "if HEAD is true, then BODY is true (or: then do BODY)” (если ЗАГОЛОВОК истинен, тогда ТЕЛО истинно (или: тогда выполнить ТЕЛО) Данный тип оператора известен как условный оператор если/тогда (if/then). Пролог же использует другую форму логики в таких правилах. Вывод об истинности заго- ловка правила Пролога делается, если (после того, как) тело этого правила истинно, например, так: "HEAD is true if BODY is true (or: if BODY can be done)" ЗАГОЛОВОК истинен, если ТЕЛО — истинно (или: если ТЕЛО может быть выполнено). Учитывая вышесказанное, правило Пролога соответствует условной форме тогда/если (then/if). Автоматическое преобразование типов Совсем не обязательно, чтобы при сопоставлении двух Visual Prolog-переменных они принадлежали одному и тому же домену. Переменные могут быть связаны с кон- стантами из различных доменов. Такое (избирательное) смешение допускается, т. к. Visual Prolog автоматически выполняет преобразование типов (из одного домена в другой), но только в следующих случаях: □ между строками (string) и идентификаторами (symbol); □ между целыми, действительными и символами (char). При преобразовании сим- вола в числовое значение этим значением является величина символа в коде ASCII. Аргумент из домена my_dom, который объявлен следующим образом: domains my_dom - <base domain> % <base domain> — это стандартный домен может свободно смешиваться с аргументами из этого основного домена и с аргумен- тами всех совместимых с ним стандартных доменов. Если основной домен — string, то с ним совместимы аргументы из домена symbol; если же основной домен integer, то с ним совместимы домены real, char, word и др. Такое преобразование типов означает, например, что вы можете: □ вызвать предикат с аргументами типа string, задавая ему аргументы типа symbol, и наоборот; □ передавать предикату с аргументами типа real параметры типа integer; □ передавать предикату с аргументами типа char параметры типа integer; □ использовать в выражениях и сравнениях символы без необходимости получения их кодов в ASCII.
Глава 10. Основы языка Visual Prolog 257 Существует набор правил, определяющих, к какому домену принадлежит результат смешивания разных доменов. Эти правила будут детально рассмотрены в гл. 16. Другие разделы программ Теперь, когда вы ознакомились с такими разделами программ Visual Prolog, как clauses, predicates, domains и goal, поговорим о некоторых других, часто исполь- зуемых разделах программ: facts, constants и различных глобальных (global) разде- лах. Это будет только началом нашего разговора. По мере дальнейшей работы с ма- териалом этого руководства, мы более подробно изучим эти разделы и их использо- вание в программах. Раздел фактов Программа на Visual Prolog представляет собой набор фактов и правил. Иногда в процессе работы программы бывает необходимо модифицировать (изменить, удалить или добавить) некоторые из фактов, с которыми она работает. В этом случае факты рассматриваются как динамическая или внутренняя база данных, которая при выпол- нении программы может изменяться. Для объявления фактов программы, рассмат- ривающихся как части динамической (или изменяющейся) базы данных, Visual Prolog включает специальный раздел — facts. Ключевое слово facts объявляет раздел фактов. Именно в этой секции вы объяв- ляете факты, включаемые в динамическую базу данных. Отметим, что в ранних версиях Visual Prolog для объявления раздела фактов использовалось ключевое слово database, т. е. ключевое слово facts — синоним устаревшего ключевого слова database. В Visual Prolog есть несколько встроенных предикатов, облегчающих ис- пользование динамических фактов. Полное описание внутренних баз данных и используемых при работе с ними преди- катов дано в гл. 15. Раздел констант В своих программах на Visual Prolog вы можете объявлять и использовать символи- ческие константы. Раздел для объявления констант обозначается ключевым словом constants, за которым следуют сами объявления, использующие следующий син- таксис: <Id> = <Макроопределение> <id> — имя символической константы, а <Макроопределение> — это то, что вы при- сваиваете этой константе. Каждое <Макроопределение> завершается символом новой строки и, следовательно, на одной строке может быть только одно описание кон- станты. Объявленные таким образом константы могут позже использоваться в про- граммах. Рассмотрим следующий фрагмент программы: constants zero = О 9 Зак. 963
258 Часть IV. Программирование на Visual Prolog one = 1 two "= 2 hundred = (10* (10-1)+10) pi - 3.141592653 ega = 3 slash_fill = 4 red = 4 Перед компиляцией программы Visual Prolog заменит каждую константу на соответ- ствующую ей строку. Например, фрагмент программы А = hundred*34, delay(А), setfillstyle (slash__fill, red), Circumf = pi*Diam, будет обрабатываться компилятором, как следующий фрагмент: А= (10* (10-1)+10)*34, delay(A), setfillstyle(4,4), Circumf = 3.141592653*Diam, 4 • На использование символических констант накладываются следующие ограничения: □ описание константы не может ссылаться само на себя: my_number = 2*my_number/2 % не допускается это приведет к сообщению об ошибке "Recursion in constant definition" (Рекурсия в описании константы); □ в описаниях констант система не различает верхний и нижний регистры. Следо- вательно, при использовании в разделе программы clauses идентификатора типа constants, его первая буква должна быть строчной для того, чтобы избежать пу- таницы между константами и переменными. Поэтому следующий фрагмент про- граммы является допустимой конструкцией: constants Two = 2 goal A=two, write(A). □ в программе может быть несколько разделов constants, однако объявление кон- станты должно производиться перед ее использованием; □ идентификаторы констант являются глобальными и могут объявляться только один раз. Множественное объявление одного и того же идентификатора приведет к сообщению об ошибке "Constant identifier can only be declared once" (Иденти- фикатор константы может объявляться только один раз).
Гпава 10. Основы языка Visual Prolog 259 Глобальные разделы Visual Prolog позволяет объявлять некоторые разделы domains, predicates, clauses глобальными (а не локальными); сделать это вы можете, объявив в своей программе специальные разделы global domains, global predicates и global facts. Подробнее эти вопросы обсудим в гл. 23. Директивы компилятора Visual Prolog поддерживает несколько директив компилятора, которые можно добав- лять в программу для сообщения компилятору специальных инструкций по обработ- ке вашей программы при ее компиляции. Кроме этого, вы можете устанавливать большинство директив компилятора с помошью команды меню среды визуальной разработки Visual Prolog Options | Project | Compiler Options. Директива include Для того чтобы избежать многократного набора повторяющихся процедур, вы може- те использовать директиву include. Ниже приведен пример того, как это делается. 1. Создаете файл (например, MYSTUFF.PRO), в котором объявляете свои наиболее часто используемые предикаты (с помощью разделов domains и predicates) и даете их описание в разделе clauses. 2. Пишете исходный текст программы, которая будет использовать эти процедуры. 3. В "допустимых областях" исходного текста программы размещаете строку: include "mystuff.pro" ^Допустимые области' — это любое место программы, в котором вы можете рас- положить декларацию разделов domains, facts, predicates, clauses или goal). При компиляции исходных текстов программы Visual Prolog вставит содержание файла MYSTUFF.PRO прямо в окончательный текст файла для компиляции. Директиву include можно использовать для включения в исходный текст (практи- чески любого) часто используемого фрагмента. Кроме того, любой включаемый в программу файл может, в свою очередь, включать другой файл (однако каждый файл может быть включен в вашу программу только один раз). Повторим: директива include может располагаться в любых "допустимых областях" программы. Резюме Ниже приведены основные идеи, представленные нами в этой главе. □ Пролог-про грамма состоит из предложений, которые могут быть фразами двух типов: (фактами или правилами): • факты — это связи или свойства, о которых вы (программист) твердо знаете, что они истинны;
260 Часть /И Программирование на Visual Prolog • правила — это зависимые связи (отношения); они позволяют Прологу выво- дить один фрагмент информации из другого. □ Факты имеют общий вид: property(objectl, object2, ...f objectN) или relation(objectl, object2, objectN) где property — это свойство объектов, a relation — отношение между объекта- ми. Различия между этими понятиями несущественны, и, поэтому, в дальнейшем мы будем использовать термин отношение. □ Каждый факт программы задает либо отношение, влияющее на один или более объектов, либо свойство одного или более объектов. Например, в факте Пролога likes(tom, baseball) отношение — это likes (нравится), а объекты — tom и baseball (бейсбол); Тому нравится бейсбол. В другом факте left_handed(benjamin) left-handed (левый крайний) является свойством объекта benjamin; т. е. Бенд- жамин — левый крайний. □ Правила имеют общую форму Заголовок:- Тело, которые выглядят так: relation(object ,object,...,object):- relation(object,...,object), relation(object,...,object). □ Сообразуясь co следующими ограничениями, вы можете устанавливать любые имена для связей и объектов: • имя объекта должно начинаться со строчной буквы, за которой может быть любое число символов. Этими символами могут быть: буквы верхнего и ниж- него регистров, цифры и символы подчеркивания; • имена свойств и связей должны начинаться со строчной буквы, за которой может следовать любая комбинация букв, цифр и символов подчеркивания. □ Предикат — это символическое имя (идентификатор) связи с последователь- ностью аргументов. Программа на Прологе — это последовательность предложений и директив, а процедура — это последовательность предложений, описывающих предикат. Предложения, принадлежащие одному предикату, должны следовать друг за другом. □ Переменные позволяют вам записывать общие факты и правила и задавать общие вопросы: • имя переменной в Visual Prolog должно начинаться с заглавной буквы или символа подчеркивания (_), после которой вы можете использовать любое число букв (верхнего и нижнего регистра), цифр и символов подчеркивания;
Гпава 10. Основы языка Visual Prolog 261 • переменные в Прологе получают свои значения в результате сопоставления с константами в фактах или правилах. До получения значения переменная яв- ляется свободной, после — становится связанной; • вы не можете длительно хранить информацию с помощью "связывания” пере- менной со значением, т. к. переменная является связанной только в пределах предложения. □ Если в запросе вас интересует только определенная информация, то для игнори- рования не нужных вам значений вы можете использовать анонимные перемен- ные. В Прологе анонимные переменные обозначаются одиночным символом подчеркивания (_). Анонимная переменная может быть использована вместо любой другой перемен- ной; она сопоставляется с любыми данными. Анонимная переменная никогда не принимает какого-либо значения. □ Задание вопросов о фактах в программе называется запросами к системе Пролога; более общим термином для запроса является цель (goal). Пролог пытается разре- шить цель (ответить на вопрос), просматривает все факты, начиная с первого до достижения последнего из них. □ Составная цель — это цель, включающая две или более частей; каждая часть со- ставной цели называется подцелью. Составная цель может быть конъюнктивной (подцель А и подцель В) или дизъюнктивной (подцель А или подцель В). □ Комментарии делают вашу программу более удобной для чтения. Вы можете заключать комментарии в разделители или предварять их одним символом про- цента. □ В Прологе имеется несколько способов сопоставления одного объекта с другим: • идентичные структуры сопоставляются друг с другом; • свободная переменная сопоставляется с константой или с ранее связанной переменной (и становится связанной с соответствующим значением); • две свободные переменные могут сопоставляться (и связываться) друг с дру- гом. С момента связывания они трактуются как одна переменная: если одна из них принимает какое-либо значение, то вторая немедленно принимает то же значение. □ Программа на Visual Prolog имеет следующую обобщенную структуру: domains / объявления доменов predicates объявления предикатов * / clauses
262 Часть IV. Программирование на Visual Prolog предложения (правила и факты) ★ / goal подцель _1, подцель _2, и т. д. */ □ В разделе clauses вы размещаете факты и правила, с которыми будет работать Visual Prolog, пытаясь разрешить цель программы. □ В разделе predicates вы объявляете предикаты и домены (типы) аргументов этих предикатов. Имена предикатов должны начинаться с буквы (желательно строч- ной), за которой следует последовательность букв, цифр и символов подчеркива- ния (до 250 знаков). В именах предикатов нельзя использовать символы пробел, минус, звездочка, слэш. Объявление предиката имеет следующую форму: predicates predicateName(argumentTypel OptionalNamel, argumentType2 OptionalName2, < ... >; argumentTypeN OptionalNameN) Здесь argument_type 1, ..., argument_typeN — либо стандартные домены, либо до- мены, объявленные в разделе domains. Объявление домена аргумента и описание типа аргумента — суть одно и то же. Имена аргументов OptionalNamel будут игнорироваться компилятором. □ В разделе domains объявляются любые нестандартные домены, используемые вами для аргументов предикатов. Домены в Прологе являются аналогами типов в других языках. Основные стандартные домены Visual Prolog: char, byte, short, ushort, word, integer, unsigned, long, ulong, dword, real, string И symbol. Основная форма объявления доменов имеет вид: domains myDomainl,..myDomainN = <standardDomain> Форма объявления составных доменов имеет следующий вид: myDomainl,..myDomainN = <compoundDomain_l>; <compoundDomain_2>; < . . • > / <compoundDomain_M> В данной главе не рассматривались составные домены; о них подробнее см. гл. 12. □ В разделе goal вы задаете внутреннюю цель программы; это позволяет програм- ме быть скомпилированной, запускаться и выполняться независимо от среды визуальной разработки (VDE).
Глава 10. Основы языка Visual Prolog 263 О Арность (размерность) предиката — это число принимаемых им аргументов; два предиката с одним именем могут иметь различную арность. Предикаты с отличающимися арностями должны собираться вместе, причем и в разделе predicates, и в разделе clauses, но такие предикаты рассматриваются как абсо- лютно разные. О Правила имеют форму: HEAD: — <Subgoall>, <Subgoal2>, <SubgoalN>. Для разрешения правила Пролог должен разрешить все его подцели, создав при этом соответствующее множество связанных переменных. Если же одна из под- целей ложна, Пролог возвратится назад и просмотрит альтернативные решения предыдущих подцелей, а затем вновь пойдет вперед, но с другими значениями переменных. Этот процесс называется поиск с возвратом. О Оператор Пролога (if) отличается от if, используемых в других языках: пра- вило Пролога работает в соответствии с условной формой тогда/если, тогда как этот оператор в других языках работает в соответствии с условной формой если/тогда.
ГЛАВА 1 1 Унификация и поиск с возвратом Эта глава имеет четыре основных раздела. В первом детально рассматривается про- цесс, который использует Visual Prolog во время попытки сопоставления вызова (из подцели) с предложением (в разделе программы clauses). Этот процесс поиска включает связывание определенного вызова с конкретным предложением — то, что называется унификацией (unification). В Прологе унификация реализует процедуры, которые вам, возможно, известны из других, более традиционных языков, — это такие процедуры, как передача параметра, выбор варианта (case), создание структу- ры, доступ к структуре, присваивание. Во втором разделе показано, как Visual Prolog производит поиск решений целевого утверждения (при помощи поиска с возвратом) и как он управляет поиском. Управ- ление поиском включает в себя методы, позволяющие программе выполнить зада- ние, которое было бы невозможно реализовать иным способом, либо вследствие того, что поиск продолжался бы слишком долго, либо из-за того, что система исто- щит запас свободной памяти. В третьем разделе этой главы определяется предикат, который можно использовать для поддержки поиска с возвратом, и рассказывается более подробно о том, как управлять поиском с возвратом. Также вводится предикат, который можно исполь- зовать для проверки того, удовлетворено ли то или иное ограничение в программе. С целью лучшего освещения предмета, в четвертом разделе повторяется наиболее важный учебный материал, представленный ранее, но уже с "процедурной" точки зрения. Показано, как можно достичь понимания основных аспектов Пролога, — чисто описательного языка, — если посмотреть на них как на процедуры. Сопоставление и унификация Рассмотрим программу ch04e01.pro (листинг 11.1) с точки зрения того, как утилита Test Goal (см. гл. 8) будет отыскивать все решения следующей цели: written_by(X, Y). Пытаясь выполнить целевое утверждение written_by (х, Y), Visual Prolog должен проверить каждое предложение written_by в программе. Сопоставляя аргументы х
Гпава 11. Унификация и поиск с возвратом 265 и Y с аргументами каждого предложения written_by, Visual Prolog выполняет поиск от начала программы до ее конца. Обнаружив предложение, соответствующее целе- вому утверждению, Visual Prolog присваивает значения свободным переменным та- ким образом, что целевое утверждение и предложение становятся идентичными; говорят, что целевое утверждение унифицируется с предложением. Такая операция сопоставления называется унификацией. : Листинг 11.1. Программа chO4eO1.pro domains title,author = symbol pages = unsigned predicates book(title, pages) written_by(author, title) long_novel(title) clauses written_by(fleming, "DR NO"). written_by(melviHe, "MOBY DICK”) . book("MOBY DICK", 250). book("DR NO", 310). long_novel(Title) written_by(_, Title), book(Title, Length), Length > 300. Поскольку x и Y являются свободными переменными в целевом утверждении, а сво- бодная переменная может быть унифицирована с любым другим аргументом (и даже с другой свободной переменной), то целевое утверждение может быть унифицирова- но с первым предложением written by в программе, как показано ниже: written_by( X , Y ). I I written_by(fleming, "DR NO"). Visual Prolog устанавливает соответствие, x становится связанным c fleming, a Y — с dr no. В этот момент Visual Prolog напечатает: X=fleming, Y="DR NO" Поскольку Test Goal ищет все решения для заданной цели, целевое утверждение также будет унифицировано и со вторым предложением written_by: written_by(melville,"MOBY DICK"). Test Goal печатает второе решение: X=melville, Y="MOBY DICK" 2 Solutions
266 Часть IV. Программирование на Visual Prolog Теперь предположим, что вы задали программе целевое утверждение written_by(X, "MOBY DICK”). Visual Prolog произведет сопоставление с первым предложением written_by: written_by(X,"MOBY DICK"). I I written_by(fleming,"DR NO"). Так как "moby dick" и "dr no" не соответствуют друг другу, попытка унификации завершается неудачно. Затем Visual Prolog проверит следующий факт в программе: written_by(melville, "MOBY DICK"). Этот факт действительно унифицируется, и х становится связанным с melville. Рассмотрим, как Visual Prolog выполнит следующее целевое утверждение: long_novel(X). Когда Visual Prolog пытается выполнить целевое утверждение, он проверяет, дейст- вительно ли обращение может соответствовать факту или заголовку правила. В на- шем случае устанавливается соответствие с long_novel(Title) Visual Prolog проверяет предложение для long_novel, пытаясь завершить сопоставле- ние унификацией аргументов. Поскольку в целевом утверждении х — свободная переменная, то она может быть унифицирована с любым другим аргументом. Title также не является связанным в заголовке предложения long novel. Целевое утвер- ждение соответствует заголовку правила, и унификация выполняется. Впоследствии Visual Prolog будет пытаться согласовывать подцели с правилом. long_novel(Title) written_by(_, Title), book(Title, Length), Length>300. Пытаясь выполнить согласование тела правила, Visual Prolog обратится к первой подцели в теле правила — written byTitle). Поскольку авторство книги явля- ется несущественным, на месте аргумента author появляется анонимная переменная (_). Обращение written by (_, Title) становится текущей подцелью, и Пролог ищет решение для этого обращения. Пролог ищет соответствие с данной подцелью от вершины и до конца программы. В результате достигается унификация с первым фактом для written_by, а именно: written_by(_, Title), I I written_by(fleming,"DR NO"). Переменная Title связывается c "DR NO", и к следующей подцели book (Title, Length) обращение выполняется уже с этим значением переменной. Далее Visual Prolog начинает очередной процесс поиска, пытаясь найти соответствие с обращением к book. Так как Title связан с "DR NO", фактическое обращение вы-
Гпава 11. Унификация и поиск с возвратом 267 глядит как book("DR NO", Length). Процесс поиска опять начинается с вершины программы. Заметим, что первая попытка сопоставления с предложением book("moby dick", 250) завершится неудачно, и Visual Prolog перейдет ко второму предложению book в поиске соответствия. Здесь заголовок книги соответствует под- цели, и Visual Prolog связывает переменную Length с величиной 310. Теперь третье предложение в теле long_novel становится текущей подцелью: Length > 300. Visual Prolog выполняет сравнение, завершающееся успешно: 310 больше, чем 300. В этот момент все подцели в теле правила выполнены, и, следовательно, обращение long_novel(X) успешно. Так как х в обращении был унифицирован с переменной Title в правиле, то значение, с которым связывается Title при подтверждении пра- вила, возвращается и унифицируется с переменной х. Переменная Title в случае подтверждения правила имеет значение "dr no", поэтому Visual Prolog выведет: X="DR NO" 1 Solution. В следующих главах будет показано несколько прикладных примеров унификации. Однако прежде ознакомимся с рядом основополагающих понятий. Поиск с возвратом Часто при решении реальной задачи мы придерживаемся определенного пути для ее логического завершения. Если полученный результат не дает искомого ответа, мы должны выбрать другой путь. Так, вам, возможно, приходилось играть в лабиринт. Один из верных способов найти конец лабиринта — это поворачивать налево на каждой развилке лабиринта до тех пор, пока вы не попадете в тупик. Тогда следует вернуться к последней развилке и попробовать свернуть вправо, после чего опять поворачивать налево на каждом встречающемся распутье. Путем методичного пере- бора всех возможных путей вы, в конце концов, найдете выход. Visual Prolog при поиске решения задачи использует именно такой метод проб и возвращений назад; этот метод называется поиск с возвратом. Если, начиная поиск решения задачи (или целевого утверждения), Visual Prolog должен выбрать между альтернативными путями, то он ставит маркер у места ветвления (называемого точ- кой отката) и выбирает первую подцель, которую и станет проверять. Если данная подцель не выполнится (что эквивалентно достижению тупика в лабиринте), Visual Prolog вернется к точке отката и попробует проверить другую подцель. В листинге 11.2 рассмотрен простой пример, для выполнения которого использован Test Goal (см. гл. 8). .............. • ' • ...................................................: Листинг 11.2. Программа ch04e02:pro predicates likes(symbol,symbol) tastes(symbol,symbol) food(symbol)
268 Часть IV. Программирование на Visual Prolog clauses likes(bill,X) food(X), tastes(X,good). tastes(pizza,good). tastes(brussels_sprouts,bad). food(brussels_sprouts). food(pizza). Эта маленькая программа составлена из двух множеств фактов и одного правила. Правило, представленное отношением likes, утверждает, что Билл любит вкусную пищу. Чтобы увидеть, как работает поиск с возвратом, дадим программе для решения сле- дующее целевое утверждение: likestbill, What). Внимание! Когда Пролог пытается произвести согласование целевого утверждения, он начина- ет поиск с вершины программы. В данном случае Пролог будет искать решение, производя с вершины программы поиск соответствия с подцелью likes (bill, what). Он обнаруживает соответствие с первым предложением в программе и переменная What унифицируется с переменной х. Сопоставление с заголовком правила заставля- ет Visual Prolog попытаться удовлетворить это правило. Производя это, он двигается по телу правила и обращается к первой находящейся здесь подцели: food(X). Внимание! Если выполняется новое обращение, поиск соответствия для этого обращения вновь начинается с вершины программы. Пытаясь согласовать первую подцель, Visual Prolog (начиная с вершины) производит сопоставление с каждым фактом или заголовком правила, встреченным в про- грамме. Он обнаруживает соответствие с запросом у первого же факта, представляющего отношение food. Таким образом, переменная X связывается со значением brussels sprouts. Поскольку существует более чем один возможный ответ на обра- щение food(X), Visual Prolog ставит точку возврата (маркер) возле факта food (brussels_sprouts). Эта точка поиска с возвратом указывает на то место, откуда Пролог начнет поиск следующего возможного соответствия для food(X) . Внимание! Когда установление соответствия обращения завершается успешно, говорят, что обращение возвращается, и может быть испытана очередная подцель.
Гпава 11. Унификация и поиск с возвратом 269 Поскольку переменная х связана с brussels_sprouts, следующее обращение будет выполняться так: tastes(brussels_sprouts, good) и Visual Prolog вновь начнет поиск с вершины программы, пытаясь согласовать это обращение. Поскольку соответствующих предложений не обнаруживается, обра- щение завершается неудачно, и теперь Visual Prolog запускает механизм возврата. Начиная поиск с возвратом, Пролог отступает к последней позиции, где была поставлена точка отката. В данном случае Пролог возвращается к факту food(brussels_sprouts). Внимание! Единственным способом освободить переменную, однажды связанную в предложе- нии, является откат при поиске с возвратом. Когда Пролог отступает к точке поиска с возвратом, он освобождает все перемен- ные, связанные после этой точки, и будет искать другое решение для исходного об- ращения. Обращение было food(X), так что связанность brussels_sprouts с х отменена. Те- перь Пролог пытается заново произвести решение для этого обращения. Он обнару- живает соответствие с фактом food (pizza); на этот раз переменная х связывается со значением pizza. Пролог переходит к следующей подцели в правиле, имея при этом новую связанную переменную. Производится новое обращение, tastes (pizza, good), и начинается поиск (опять от вершины программы). На этот раз соответствие найдено, и целевое утверждение успешно выполняется. Поскольку переменная what в целевом утверждении унифицирована с переменной х в правиле likes, а переменная х связана со значением pizza, переменная what от- ныне связана со значением pizza и Visual Prolog сообщает решение: What=pizza 1 Solution Поиск всех решений в Test Goal Как было описано выше, при помощи поиска с возвратом Visual Prolog не только найдет первое решение задачи, но при использовании режима Test Goal будет также способен найти все возможные решения. Рассмотрим программу ch04e03.pro (листинг 11.3), которая содержит сведения об именах и возрастах нескольких игроков в теннисном клубе. Листинг 11.3. Программа ch04e03.pro domains child = symbol age = integer
270 Часть IV. Программирование на Visual Prolog predicates player(chiId, age) clauses player(peter,9). player(paul,10). player (chris, 9) . player(susan,9). Спланируем турнир по пинг-понгу между девятилетними членами теннисного клуба. Каждая пара игроков должна провести между собой две игры. Задача — найти все возможные пары из тех игроков клуба, кому по девять лет. Она может быть решена заданием режиму Test Goal составной цели: goal player(Personl, 9), player(Person!, 9), Personl <> Person2. На естественном языке это прозвучало бы так: найти Personl в возрасте 9 лет и Person2 в возрасте 9 лет, отличное от Personl. Что будет делать Visual Prolog? 1. Попытается найти решение для первой подцели player (Personl, 9) и перейдет к следующей подцели только после того, как первая будет достигнута. Первая подцель согласуется сопоставлением Personl с peter. Затем Visual Prolog попытается согласовать следующую подцель: player(Person2, 9) Аналогично, Person2 сопоставляется с peter. Теперь Visual Prolog переходит к третьей и последней подцели: Personl о Person2 2. Так как и Personl, и Person'! связаны с peter, эта подцель не выполняется. Вследствие этого Visual Prolog выполняет поиск с возвратом к предыдущей под- цели и ищет другое решение для второй подцели: player(Person!, 9) Эта подцель выполняется при сопоставлении Person! с chris. 3. Теперь третья подцель: Personl О Person! Она может быть выполнена, т. к. peter и chris отличны. Таким образом, целевое утверждение полностью согласовано путем образования турнирной пары из Кри- са и Питера. 4. Поскольку Test Goal должен найти все возможные решения целевого утверж- дения, он находит точку поиска с возвратом предыдущей цели в надежде вновь добиться успеха.
Гпава 11. Унификация и поиск с возвратом 271 Так как player(Person?, 9) также может быть согласовано, если принять Person? за susan, то Visual Prolog еще раз проверит третью подцель. Достигается успех (т. к. peter отличен от susan), и другое решение для всего целевого утверждения найдено. 5. В дальнейшем поиске решений Test Goal вновь возвращается к точке поиска с возвратом второй подцели, но все возможности для этой подцели уже исчерпа- ны, и поиск с возвратом теперь выполняется от первой подцели. Она вновь мо- жет быть согласована сопоставлением Personl с chris. Вторая подцель теперь имеет успех в результате сопоставления Person2 с peter, так что третья подцель согласована, и все целевое утверждение опять выполнено. Итак, запланирована еще одна встреча, на этот раз между Крисом и Питером. 6. В поисках еще одного решения целевого утверждения Test Goal возвращается к точке поиска с возвратом второй подцели в правиле. Здесь Person2 ставится в соответствие chris, и при этом условии проверяется третья подцель. Она не выполняется, т. к. Personl и Person2 эквивалентны. Тогда Test Goal выполняет поиск с возвратом от второй подцели в поисках другого решения: Person2 сопос- тавляется с susan, третья подцель выполнена. Test Goal назначает очередную встречу в теннисном клубе (Крис против Сюзан). 7. И вновь, памятуя о необходимости найти все решения, Test Goal возвращается ко второй подцели, но на этот раз безуспешно. Когда вторая подцель не выпол- няется, процесс возвращается к первой подцели, на этот раз, находя соответствие Personl с susan. Пытаясь выполнить вторую подцель, Test Goal сопоставляет Person? с peter, и впоследствии третья подцель выполнится при этих условиях. Итак, назначена пятая встреча. 8. Снова возврат ко второй подцели, где Person? сопоставляется с chris. Найдено шестое решение задачи о теннисном клубе и, наконец, получено полное множе- ство турнирных пар. 9. Последнее исследуемое решение связывает с susan как Personl, так и Person? и приводит к невыполнению последней подцели. Visual Prolog должен вернуться ко второй подцели, но там не осталось никаких новых вариантов. Тогда он возвра- щается для поиска к первой подцели, но и здесь все возможности для Personl уже исчерпаны. Для данного целевого утверждения не может быть найдено дру- гих решений, и работа программы завершается. Убедитесь, что Test Goal ответит следующим: Personl=peter, Personl=peter, Personl=chris, Personl=chris, Personl=susan, Personl=susan, 6 Solutions Person2=chris Person2=susan Person2=peter Person2=susan Person2=peter Person2=chris Обратите внимание, как поиск с возвратом может вызывать вывод Test Goal избы- точных решений. В нашем примере Test Goal не отметил, что Personl = peter то же
272 Часть IV. Программирование на Visual Prolog самое, что и Person2 = peter. Далее в этой главе мы покажем, как управлять поис- ком в Visual Prolog. Упражнение на поиск с возвратом Используя программу ch04e04.pro (листинг 11.4), решите, что ответит Visual Prolog на следующее целевое утверждение: player(Personl, 9), player(Person2, 10). Проверьте ваш ответ, выполнив программу с данным целевым утверждением с по- мощью Test Goal. Детальный обзор поиска с возвратом После разбора предыдущего простого примера рассмотрим теперь более подробно, как в Visual Prolog работает механизм поиска с возвратом. Начнем с того, что иссле- дуем программу ch04e04.pro В свете целевого утверждения, состоящего из двух под- целей: likes(X, wine), likes(X, books) При исследовании целевого утверждения Visual Prolog, в первую очередь, отмечает, какие подцели согласовались, а какие нет. Процесс поиска может быть представлен целевым деревом (рис. 11.1). Рис. 11.1. Целевое дерево процесса поиска Перед началом исследования целевого утверждения целевое дерево состоит из двух несогласованных подцелей. На последующих изображениях целевого дерева согласо- ванная подцель в целевом дереве будет отмечаться подчеркиванием, а соответст- вующее предложение — записываться под этой подцелью (листинг 11.4). Листинг 11.4. Программа ch04e04.pro domains name,thing = symbol predicates likes(name, thing) reads(name) is_inquisitive(name) clauses likes(john,wine).
Глава 11. Унификация и поиск с возвратом 273 likes(lance,skiing). likes(lance,books). likes(lance,films). likes(Z,books) reads(Z), is_inquisitive(Z). reads(john). is_inquisitive(john). goal likes(X,wine),likes(X,books). Четыре основных правила поиска с возвратом Правило 1 Подцели должны быть согласованы по порядку, сверху вниз. В изучаемом примере целевое дерево демонстрирует, что должны быть согласованы две подцели. Visual Prolog определяет, какую подцель ему использовать при попытке сопоставления предложения, исходя из второго основного правила поиска с воз- вратом: Правило 2 Предикатные предложения проверяются в том порядке, в каком они появляются в программе, сверху вниз. Выполняя программу ch04e04.pro, Visual Prolog находит предложение, соответствую- щее первому факту, определяющему предикат likes. Посмотрите на рис. 11.2, как теперь выглядит целевое дерево. likes (john, wine) Рис. 11.2. Целевое дерево 2 Подцель likes (X, wine) соответствует факту likes (john, wine), и х связывается со значением john. Visual Prolog пытается согласовать следующую справа подцель. Обращение ко второй подцели начинает совершенно новый поиск с условием Х=j ohn.
274 Часть IV. Программирование на Visual Prolog Первое предложение likes(john, wine) не соответствует подцели likes(X, books) т. к. wine (вино) — это вовсе не то же самое, что books (книги). Поэтому Visual Prolog должен проверить следующее предложение, но lance не соответствует значе- нию х (потому что в данном случае х связан с john), так что поиск продолжается с третьим предложением, определяющим предикат likes: likes(Z, books):- reads(Z), is_inquisitive(Z). Аргумент z — переменная, поэтому она может соответствовать х. Вторые аргументы находятся в согласии, так что вызов соответствует заголовку правила. Когда х согла- суется с z, аргументы унифицируются. В результате унификации аргументов Visual Prolog приравняет значение, которое имеет х (т. е. john), и переменную z. В резуль- тате переменная z теперь также имеет значение john. Теперь подцель соответствует левой части (заголовку) правила. Продолжение поиска определяется третьим фундаментальным правилом поиска с возвратом: Правило 3 Когда подцель соответствует заголовку правила, далее должно быть согласовано тело этого правила: тело правила теперь образует новое множество подцелей для согласования. Целевое дерево станет таким, как на рис. 11.3. likes (john, wine) likes (z, books) Рис. 11.3. Целевое дерево 3 Теперь целевое дерево включает в себя подцели reads(Z) and is_inquisitive(Z),
Гпава 11. Унификация и поиск с возвратом 275 где z связана со значением john. Далее Visual Prolog будет искать факты, соответст- вующие обеим подцелям. Последнее результирующее целевое дерево изображено на рис. 11.4. likes (john, wine) likes (z, books) Рис. 11.4. Результирующее целевое дерево Правило 4 Целевое утверждение считается согласованным, когда соответствующий факт най- ден для каждой оконечности (листа) целевого дерева. Таким образом, теперь начальное целевое утверждение согласовано. Visual Prolog использует результат процедуры поиска по-разному, в зависимости от того, как был начат поиск. Если целевое утверждение является обращением из под- цели в теле правила, то Visual Prolog пытается согласовать следующую подцель в правиле, после того как обращение возвращено. Если целевое утверждение является вопросом пользователя, то Visual Prolog непосредственно отвечает: X=john 1 Solution. Как вы видели в программе ch04e04.pro (см. листинг 11.4), однажды согласовав це- левое утверждение, Test Goal возвращается (откатывается) назад для поиска всех альтернативных решений. Test Goal возвращается назад и в том случае, если подцель не выполняется, надеясь пересогласовать предыдущую подцель с другими предложе- ниями. Выполняя подцель, Visual Prolog начинает поиск с первого предложения, опреде- ляющего предикат. Затем может произойти одно из двух: □ Visual Prolog находит соответствующее предложение, тогда: • если имеется другое предложение, которое, возможно, может вновь согласо- вать подцель, Visual Prolog выставляет указатель (с тем, чтобы отметить точку
276 Часть IV. Программирование на Visual Prolog возврата) и связывает все свободные переменные в подцели (которые соответ- ствуют значениям в предложении) с соответствующими значениями; • если данное предложение является заголовком правила, то затем оценивается тело этого правила. Подцели в теле правила должны быть удовлетворены для успешного завершения обращения. □ Visual Prolog не может найти соответствующее предложение. Целевое утвержде- ние не согласуется и Visual Prolog выполняет поиск с возвратом в попытке вновь согласовать предыдущую подцель. Когда процесс достигает последней точки воз- врата, Visual Prolog освобождает все переменные, которым были присвоены но- вые значения (после того, как была поставлена точка возврата), и вновь пытается согласовать исходное обращение. Visual Prolog начинает поиск с вершины программы. Когда он выполняет возврат к обращению, новый процесс поиска начинается с точки отката, выставленной по- следней. Если поиск безуспешен, то вновь выполняется поиск с возвратом. Если процесс поиска с возвратом исчерпал все предложения для всех подцелей, то это означает, что целевое утверждение не согласуется. Поиск с возвратом для внутреннего целевого утверждения Приведем еще один, усложненный пример (листинг 11.5), иллюстрирующий, как в Visual Prolog происходит поиск с возвратом, когда программа скомпилирована и вы- полняется как автономная исполняемая программа (см. разд. "Тестирование примеров как автономных исполняемых программ" гл. 8). Листинг 11.5. Программа ch04e05.pro predicates type(symbol, symbol) is_a(symbol, symbol) lives(symbol, symbol) can_swim(symbol) clauses type(ungulate,animal). type(fish,animal). is_a (zebra,ungulate). is_a(herring,fish). is_a(shark, fish). lives(zebra,on_land). lives(frog,on_land). lives(frog,in_water). lives(shark,in_water). can_swim(Y) type(X,animal), is_a(Y,X), )ives(Y,in_water).
~ава 11. Унификация и поиск с возвратом 277 can_swim(What) , write("A ",What," can swim\n"), readchar(_). После того как программа скомпилирована и запущена (например, с использовани- ем команды меню Project | Run), Visual Prolog автоматически начнет выполнение целевого утверждения, пытаясь согласовать все подцели в разделе программы goal. I. Visual Prolog обращается к предикату can swim со свободной переменной what. Пытаясь выполнить это обращение, Пролог просматривает программу в поисках соответствия. Он обнаруживает соответствие с предложением, определяющим can swim, и переменная what унифицируется с переменной Y. 2. Затем Visual Prolog пытается согласовать тело правила. При этом происходит об- ращение к первой подцели в теле правила, type(X, animal), и поиск соответст- вия для этого обращения. Он обнаруживает соответствие с первым фактом, опре- деляющим отношение type. 3. В этот момент X связывается с ungulate. Поскольку здесь налицо более чем одно возможное решение, Пролог проставляет точку возврата возле факта type(ungulate, animal). 4. Имея X, связанным с ungulate, Visual Prolog производит обращение ко второй подцели в правиле (is_a(Y, ungulate)) и снова ищет соответствие. Он находит его с первым фактом, is_a(zebra, ungulate) . Y связывается c zebra, и Пролог выставляет точку возврата возле факта is_a (zebra, ungulate). 5. Теперь, имея х, связанным с ungulate, и Y — с zebra, Visual Prolog пытается со- гласовать последнюю подцель, lives (zebra, in water). Пролог проверяет каж- дое предложение lives, но в программе нет предложения lives (zebra, in water), поэтому обращение завершается неудачно, и далее начинается поиск с возвратом другого решения. 6. Когда Visual Prolog совершает обратный ход, процесс возвращается к последней позиции, где была помещена точка возврата. В данном случае последняя точка возврата была поставлена у второй подцели в правиле, на факте is_a(zebra, ungulate). 7. При достижении точки возврата Visual Prolog освобождает переменные, которым были присвоены новые значения после последней точки возврата, и пытается найти другое решение для обрабатываемого обращения. В данном случае обра- щение было is_a(Y, ungulate). 8. Visual Prolog продолжает спуск по предложениям в поиске другого соответствую- щего предложения, начиная с того места, где поиск был прекращен. Так как в программе больше нет соответствующих предложений, обращение завершается неудачно, и Пролог вновь ведет поиск с возвратом в попытке решить исходное целевое утверждение. 9. Теперь в качестве последней точки возврата рассматривается type (ungulate, animal).
278 Часть IV. Программирование на Visual Prolog What is unified with/ X is bound to ungulate У is bound to zebra No match No more facts that match this call X is now bound to fish У is now bound to herring No match У is now bound to shark What is bound to shark Рис. 11.5. Алгоритм работы программы can_swim
Гпава 11. Унификация и поиск с возвратом 279 10. Visual Prolog освобождает переменные, использованные в исходном обращении, и пытается найти другое решение для обращения type(X, animal). Поиск начи- нается после точки возврата. Пролог находит соответствие со следующим фак- том type в программе (type (fish, animal)); X связывается с fish, и новая точка возврата ставится возле этого факта. 11. Далее Visual Prolog продвигается вниз, к следующей подцели в правиле; по- скольку это уже новое обращение, поиск начинается с вершины программы для is_a (Y, fish). 12. Visual Prolog находит соответствие для этого обращения, и Y становится связан- ным с herring. 13. Так как y теперь связан с herring, следующая подцель, к которой происходит обращение, суть lives (herring, in_water) . Это новое обращение — поиск на- чинается с вершины программы. 14. Пролог исследует каждый факт lives, но не находит соответствия, и подцель не выполняется. 15. Visual Prolog возвращается к последней точке возврата is_a (herring, fish). 16. Переменные, которые были связаны этим сопоставлением, теперь освобождены. Начиная с того места, где процесс был прекращен, Пролог теперь ищет новое решение для обращения is_a(Y, fish). 17. Visual Prolog находит соответствие со следующим предложением is_a, Y стано- вится связанным с идентификатором shark. 18. Пролог опять исследует последнюю подцель, имея переменную Y, связанную с shark. Он выполняет обращение lives (shark, inwater); поиск начинается с вершины программы (новое обращение) и обнаруживает соответствие. По- следняя для правила подцель выполняется. 19. К этому моменту тело правила can_swim(Y) согласовано. Visual Prolog возвраща- ет Y вызову can_swim(What). Так как What связана с Y, a Y — с shark, то в целе- вом утверждении what связывается с shark. 20. Пролог продолжает процесс с того места в разделе goal, где он был остановлен, и обращается ко второй подцели в целевом утверждении. 21. Visual Prolog завершает программу выводом: A shark can swim. и программа завершается успешно (рис. 11.5). Попытайтесь проследить за этими шагами, используя отладчик )Debugger). Запусти- те отладчик из VDE с помощью команды Project | Debug. Когда появится' окно отладчика, выберите команду меню отладчика View | Local Variables, используйте команду Run | Trace Into или "горячую" клавишу <F7> для пошагового выполнения программы и наблюдения за изменениями значений переменных (подробнее см. разд. "Отладчик Visual Prolog" гл. 27).
280 Часть IV Программирование на Visual Prolog Управление поиском решений Встроенный механизм поиска с возвратом в Прологе может привести к поиску не- нужных решений, в результате чего теряется эффективность, например, когда жела- тельно найти только одно решение. В других случаях может оказаться необходимым продолжать поиск дополнительных решений, даже если целевое утверждение уже согласовано. В этом разделе показаны некоторые методы, которые можно использо- вать для управления поиском решений ваших целевых утверждений. Visual Prolog обеспечивает два инструментальных средства, которые дают возмож- ность управлять механизмом поиска с возвратом: предикат fail, который использу- ется для инициализации поиска с возвратом, и cut или отсечение (обозначает- ся !) — для запрета возможности возврата. Использование предиката fail Visual Prolog начинает поиск с возвратом, когда вызов завершается неудачно. В оп- ределенных ситуациях бывает необходимо инициализировать выполнение поиска с возвратом, чтобы найти другие решения. Visual Prolog поддерживает специальный предикат fail, вызывающий неуспешное завершение, и, следовательно, инициали- зирует возврат. Действие предиката fail равносильно эффекту от сравнения 2 = 3 или другой невозможной подцели. Программа ch04e06.pro (листинг 11.6) иллюстри- рует использование этого специального предиката. |ГЛистинг 116. Программа ch04e06.pro domains name = symbol predicates father(name, name) everybody clauses father(leonard,katherine). father(carl,jason). father(carl,marilyn). everybody:- father(X, Y), write(X," is ",Y,"'s father\n"), fail. Пусть необходимо найти все решения цели father (X,Y). Используя утилиту Test Goal, можно записать цель как goal father (X, Y) .
Глава 11. Унификация и поиск с возвратом 281 Test Goal найдет все решения цели father (X,Y) и отобразит значения всех перемен- ных следующим образом: X=leonard, Y=katherine X=carl, Y=jason X=carl, Y=marilyn 3 Solutions Но если вы скомпилируете эту программу и запустите ее (клавишей <F9> или командой меню Project | Run), то Visual Prolog найдет только первое подходящее решение для father (X, Y). После того как целевое утверждение, определенное в раз- деле goal, выполнено впервые, ничто не говорит Прологу о необходимости продол- жения поиска с возвратом. Поэтому обращение к father приведет только к одному решению. Как же найти все возможные решения? Предикат everybody в программе ch04e06.pro использует fail для поддержки поиска с возвратом. Задача предиката everybody — найти все решения для father и выдать полный ответ. Сравните предыдущие ответы утилиты Test Goal с целью father (X,Y) и ответы на выполнение следующей цели: goal everybody. отображенные сгенерированной программой: leonard is katherine's father carl is jason's father carl is marilyn's father Предикат everybody использует поиск с возвратом с тем, чтобы получить все реше- ния для father (X, Y), заставляя Пролог выполнять поиск с возвратом сквозь тело правила everybody: father(X, Y), write(X," is ",Y,"'s father\n"), fail. fail не может быть согласован (он всегда неуспешен), поэтому Visual Prolog вынуж- ден повторять поиск с возвратом. При поиске с возвратом он возвращается к по- следнему обращению, которое может произвести множественные решения. Такое обращение называют недетерминированным. Недетерминированное обращение явля- ется противоположностью детерминированному обращению, которое может произве- сти только одно решение. Предикат write не может быть вновь согласован (он не может предложить новых решений), поэтому Visual Prolog должен выполнить откат дальше, на этот раз к пер- вой подцели в правиле. Обратите внимание, что помещать подцель после fail в теле правила бесполезно. Предикат fail все время завершается неудачно, нет возможности для достижения подцели, расположенной после fail.
282 Часть IV. Программирование на Visual Prolog Упражнения 1. Загрузите и запустите программу ch04e06.pro (см. листинг 11.6) и исследуйте сле- дующие целевые утверждения: • father(X, Y). • everybody. 2. Измените тело правила, определяющего everybody, таким образом, чтобы прави- ло заканчивалось предикатом write (удалите обращение к fail). Теперь скомпи- лируйте и запустите программу, задавая everybody в качестве цели. Почему Test Goal не находит всех решений, как в случае вопроса father(X, у)? 3. Восстановите обращение к fail в конце правила everybody. Опять задайте во- прос everybody в качестве цели и запустите Test Goal. Почему решения для everybody прерваны по? Прерывание поиска с возвратом: отсечение Visual Prolog предусматривает возможность отсечения, которая используется для прерывания поиска с возвратом; отсечение обозначается восклицательным зна- ком (!). Действует отсечение просто: через него невозможно совершить откат (поиск с возвратом). Отсечение помещается в программу таким же образом, как и подцель в теле прави- ла. Когда процесс проходит через отсечение, немедленно удовлетворяется обраще- ние к cut и выполняется обращение к очередной подцели (если таковая имеется). Однажды пройдя через отсечение, уже невозможно произвести откат к подцелям, расположенным в обрабатываемом предложении перед отсечением, и также невоз- можно возвратиться к другим предложениям, определяющим обрабатывающий пре- дикат (предикат, содержащий отсечение). Существуют два основных случая применения отсечения. □ Если вы заранее знаете, что определенные посылки никогда не приведут к ос- мысленным решениям (поиск решений в этом случае будет лишней тратой вре- мени), — примените отсечение, — программа станет быстрее и экономичнее. Такой прием называют зеленым отсечением. □ Если отсечения требует сама логика программы для исключения из рассмотрения альтернативных подцелей. Это — красное отсечение. Использование отсечений В этом разделе даются примеры, показывающие, как следует использовать отсече- ние, рассматриваются несколько условных правил (rl, г2 и гЗ|, которые опреде- ляют условный предикат г, а также несколько подцелей — а, Ь, с и т. д. Предотвращение поиска с возвратом к предыдущей подцели в правиле rl а, Ь, I • t с.
Гпава 11. Унификация и поиск с возвратом 283 Такая запись является способом сообщить Visual Prolog о том, что вас удовлетворит первое решение, найденное им для подцелей а и ь. Имея возможность найти мно- жественные решения при обращении к с путем поиска с возвратом, Пролог при этом не может произвести откат (поиск с возвратом) через отсечение и найти аль- тернативное решение для обращений а и ь. Он также не может возвратиться к дру- гому предложению, определяющему предикат rl. В качестве конкретного примера рассмотрим программу ch04e07.pro (листинг 11.7). Листинг 11.7. Программа ch04e07.pro predicates buy_car(symbol,symbol) car(symbol,symbol,integer) colors(symbol,symbol) clauses buy_car(Model, Color) car(Model,Color,Price), colors(Color,sexy), i Price < 25000, car(maserati,green, 25000). car(corvette,black,24000). car(corvette,red,26000). car(porsche,red,24000). colors(red,sexy). colors(black,mean). colors(green,preppy). goal buy_car(corvette, Y). В данном примере поставлена цель: найти Corvette (Корвет) приятного цвета, под- ходящий по стоимости. Отсечение в правиле buy car означает, что поскольку в базе данных содержится только один "Корвет" приятного цвета, хоть и со слишком высо- кой ценой, то нет нужды искать другую машину. Получив целевое утверждение buy_car(corvette, Y) программа отработает следующие шаги: 1. Visual Prolog обращается к саг, первой подцели для предиката buy car. 2. Выполняет проверку для первой машины, Maserati, которая завершается не- удачно. 3. Затем проверяет следующее предложение саг и находит соответствие, связывая переменную color со значением black.
284 Часть IV. Программирование иа Visual Prolog 4. Переходит к следующему обращению и проверяет, имеет ли выбранная машина приятный цвет. Черный цвет не является приятным в данной программе, таким образом проверка завершается неудачно. 5. Выполняет поиск с возвратом к обращению саг и снова ищет Corvette, удовле- творяющий этому критерию. 6. Находит соответствие и снова проверяет цвет. На этот раз цвет оказывается при- ятным, и Visual Prolog переходит к следующей подцели в правиле: к отсечению. Отсечение немедленно выполняется, "замораживая" все переменные, ранее свя- занные в этом предложении. 7. Переходит к следующей (и последней) подцели в правиле, к сравнению Price < 25000. 8. Проверка завершается неудачно, и Visual Prolog пытается совершить поиск с воз- вратом с целью найти другую машину для проверки. Отсечение предотвращает попытку решить последнюю подцель, и наше целевое утверждение завершается неудачно. Предотвращение поиска с возвратом к следующему предложению Отсечение может быть использовано, как способ сообщить Visual Prolog, что он вы- брал верное предложение для определенного предиката. Например, рассмотрим сле- дующий фрагмент: г (1) • / а, Ь, с. г(2) । d. г(3) • г с. Г (J writef'This is a catchall clause."). Использование отсечения делает предикат г детерминированным. В данном случае Visual Prolog выполняет обращение к г с единственным целым аргументом. Предпо- ложим, что произведено обращение r(l). Visual Prolog просматривает программу в поисках соответствия для обращения; он находит его с первым предложением, определяющим г. Поскольку имеется более чем одно возможное решение для дан- ного обращения, Visual Prolog проставляет точку возврата около этого предложения. Теперь Visual Prolog начинает обработку тела правила, проходит через отсечение и исключает возможность возвращения к другому предложенйю г. Это отменяет точки поиска с возвратом, повышая эффективность выполнения программы, а также га- рантирует, что отлавливающее ошибки предложение будет выполнено лишь в том случае, если ни одно из условий не будет соответствовать обращению к г.
Гпава 11. Унификация и поиск с возвратом 285 Обратите внимание, что конструкция такого типа весьма похожа на конструкцию сазе в других языках программирования; условие проверки записывается в заголовке правил. Вы могли бы написать такие предложения: г(X) X = 1, I • t а, Ь, с. г(X) X = 2, । d. Г(Х) X = 3, • f с. г(_) write("This is a catchall clause."). Однако следует, по возможности, помещать проверочное условие именно в заголо- вок правила, — это повышает эффективность программы и упрощает ее чтение. В качестве другого примера рассмотрим программу ch04e08.pro (листинг 11.8). За- пустите ее с помощью Test Goal. ) Листинг 11.8. Программа ch04e08.pro predicates friend(symbol,symbol) girl(symbol) likes(symbol, symbol) clauses friend(bill,jane):- girl (jane), likes(bill,jane), i _ friend(bill,jim) likes(jim,baseball), i ф friend(bill,sue) girl(sue). girl(mary). girl (jane) . girl(sue). likes(jim,baseball). likes(bill,sue).
286 Часть IV. Программирование на Visual Prolog goal friend(bill, Who). Если бы в программе не было отсечения, то Visual Prolog предложил бы два реше- ния: Билл является другом как Джейн, так и Сью. Однако отсечение в первом пред- ложении, определяющем friend, говорит о том, что если это предложение согласо- вано, то друг Билла уже найден, и нет нужды продолжать поиск других кандидатур. Поиск с возвратом может иметь место внутри предложений в попытке согласовать обращение, но, однажды обнаружив решение, Visual Prolog проходит через отсече- ние. Предложения friend, записанные так, как показано выше, возвратят одного и только одного друга Билла (если друг вообще может быть найден). Детерминизм и отсечение Если бы предикат friend, определенный в предыдущей программе, не содержал от- сечений, то это был бы недетерминированный предикат (способный производить множественные решения при помощи поиска с возвратом). В предыдущих реализа- циях Пролога программисты должны были обращать особое внимание на недетер- минированные предложения из-за сопутствующих им дополнительных требований к ресурсам памяти. Теперь Visual Prolog сам выполняет проверку на недетерминиро- ванные предложения, облегчая вашу работу. В Прологе существует директива компилятора check_determ. Если вставить эту ди- рективу в самое начало программы, то Visual Prolog будет выдавать предупреждение в случае обнаружения недетерминированных предложений в процессе компиляции. Вы можете превратить недетерминированные предложения в детерминированные, вставляя отсечения в тело правил, определяющих данный предикат. Например, по- мещение отсечений в предложения, определяющие предикат friend, делает этот предикат детерминированным, поскольку в данном случае обращение к friend мо- жет возвратить одно и только одно решение. Предикат not Следующая программа ch04e!0.pro (листинг 11.9) демонстрирует, как вы можете ис- пользовать предикат not для того, чтобы выявить успевающего студента: студента, у которого средний балл (GPA) не менее 3.5 и у которого в настоящее время не про- должается испытательный срок. Листинг 11.9. Программа ch04e10.pro domains name = symbol gpa - real predicates honor_student(name) student(name, gpa) probation(name)
Гпава 11. Унификация и поиск с возвратом 287 clauses honor_student(Name) student(Name, GPA), GPA>=3.5, not(probation(Name)). student ("Betty Blue", 3.5). student("David Smith", 2.0). student("John Johnson", 3.7). probation("Betty Blue"). probation("David Smith"). goal honor_student(X). При использовании предиката not необходимо иметь в виду следующее: Внимание! Предикат not будет успешным, если не может быть доказана истинность данной подцели. Это приводит к предотвращению связывания внутри not несвязанных переменных. При вызове изнутри not подцели со свободными переменными, Visual Prolog воз- вратит сообщение об ошибке: "Free variables not allowed in not or retractall" (Сво- бодные переменные не разрешены в not или retract). Это происходит вследствие того, что для связывания свободных переменных в подцели, подцель должна унифи- цироваться с каким-либо другим предложением и выполняться. Правильным спосо- бом управления несвязанными переменными подцели внутри not является исполь- зование анонимных переменных. Вот несколько примеров корректных и некорректных предложений: likes(bill, Anyone)% Anyone — выходной аргумент likes(sue, Anyone), not(hates(bill, Anyone). В этом примере Anyone связывается посредством likes (sue, Anyone) до того, как Visual Prolog делает вывод, что hates (bill, Anyone) не является истиной. Данное предложение работает корректно. Если вы измените его таким образом, что обращение к not будет выполняться пер- вым, то получите сообщение об ошибке: "Free variable are not allowed in not" (Сво- бодные переменные в not не разрешены). likes(bill, Anyone)% Это не будет работать правильно not(hates(bill, Anyone)), likes(sue, Anyone). Даже если вы замените в not (hates (bill, Anyone)) Anyone на анонимную перемен- ную, и предложение, таким образом, не будет возвращать ошибку, все равно полу- чите неправильный результат.
288 Часть IV. Программирование на Visual Prolog likes(bill, Anyone):- % Это не будет работать правильно not(hates(bill, _)), likestsue, Anyone). Это предложение утверждает, что Биллу нравится кто угодно, если неизвестно ниче- го о том, кого Билл ненавидит, и если этот "кто-то" нравится Сью. Подлинное пред- ложение утверждало, что Биллу нравится тот, кто нравится Сью, и при этом Билл не испытывает к этому человеку ненависти. Неверное использование предиката not приведет к сообщению об ошибке или к ошибкам в логике вашей программы. Следующая программа ch04ell.pro является примером правильного использования предиката not (листинг 11.10). Листинг 11.10. Программа ch04e11.pro predicates likes_shopping(symbol) has_credit_card(symbol,symbol) bottomed_out(symbol,symbol) clauses likes_shopping(Who) has_credit_card(Who,Card), not(bottomed_out(Who, Card)) , write(Who," can shop with the ",Card, " credit card.\n"). has_credit_card(chris,visa). has_credit_card(chris,diners). has_credit_card(joe, shell). has_credit_card(sam, mastercard). has_credit_card(sam,citibank). bottomed_out(chris,diners). bottomed_out(sam,mastercard). bottomed_out(chris,visa). goal likes_shopping(Who). Упражнения 1. Предположим, что средний налогоплательщик в США — это женатый человек, имеющий двоих детей, который зарабатывает не менее 500 и не более 2000 дол- ларов в месяц. Определите предикат special_taxpayer, который, при целевом ут- верждении special-taxpayer(fred), ВЫПОЛНИТСЯ ЛИШЬ В том случае, если fred нарушит одно из условий для среднего налогоплательщика. Используйте отсече- ние для гарантии того, что не выполняется ненужный поиск с возвратом. 2. Игроки в некотором теннисном клубе разбиты на три лиги, и могут вызывать на состязание только членов своей лиги или находящихся лигой ниже (если таковая имеется).
Гпава 11. Унификация и поиск с возвратом 289 Напишите на Visual Prolog программу, которая будет определять все возможные пары игроков клуба в следующей форме: tom versus bill marjory versus annette Используйте отсечение, чтобы, например tom versus bill И bill versus tom не выводились одновременно. 3. Следующая программа ch04el2.pro (листинг 11.11) — это упражнение на поиск с возвратом, а не проверка вашей способности раскрывать таинственные убийст- ва. Загрузите и запустите эту программу с помощью Test Goal. { Замечание ) Виновен Берт, т. к. он имеет мотив и испачкан тем же веществом, что и жертва. Листинг 11.11. Программа ch04e12.pro domains name,sex,occupation,object,vice,substance = symbol age=integer predicates person(name, age, sex, occupation) had_affair(name, name) killed_with(name, object) killed(name) killer(name) motive (vice)- smeared_in(name, substance) owns(name, obj e ct) operates__identically(object, object) owns_probably(name, object) suspect(name) /* * * Факты об убийстве * * */ clauses person(bert,55,m,carpenter). person(allan,25,m,football_player). person(allan,25,m,butcher). person (john, 25,m,pickpocket) . had_affair(barbara,john). had_affair(barbara,bert). had_affair(susan, john). 10 Зак. 963
290 Часть IV. Программирование на Visual Prolog killed_with(susan,club). killed(susan). motive(money). motive(jealousy) . motive(righteousness). smeared_in(bert, blood). smeared_in(susan, blood). smeared_in(allan, mud). smeared_in(john, chocolate). smeared_in(barbara,chocolate). owns(bert,wooden_leg). owns (john, pistol) . /* * * Подоплека * * */ operates_identically(wooden_leg, club). operateS-identically(bar, club). operates_identically(pair_of_scissors, knife). operates_identically(football_boot, club). owns_probably(X,football_boot) person (X,_,_,football_player). owns_probably(X,pair_of_scissors):- person(X,_,_,hairdresser). owns_probably(X, Object) owns(X,Object). Л ********************* * * Подозреваем всех тех, кто имеет оружие, * * которым могла быть убита Сюзан. * ***********************у suspect(X):- killed_with(susan,Weapon), operates_identically(Object,Weapon), owns_probably(X,Object). '*•*•*'*'*'*'*'*•*'***•*•*'*•*••*•*•*•***•*** * Подозреваем мужчин, которые имели связи с Сюзан.* ************•*★•***•****★★***/ suspect(X):- motive(jealousy), person (X, _, m, _) , had_affair(susan,X). /********************* * Подозреваем женщин, которые имели связи* * с кем-либо из тех, кого знала Сюзан. *
Гпава 11. Унификация и поиск с возвратом 291 suspect(X):- motive (jealousy) , person(X,_, f,_) , had_affair(X, Man) , had_affair(susan,Man). * Подозрительные преступники, мотив которых — деньги.* suspect(X):- motive(money), person(Х,_,_,pickpocket). killer(Killer) person(Killer,_,_,_), killed(Killed), Killed <> Killer, % Это не самоубийство suspect(Killer) , smeared_in(Killer,Goo), smeared_in(Killed,Goo). goal killer(X). О Прологе с процедурной точки зрения Пролог — это декларативный язык. Описывая задачу в терминах фактов и правил, вы предоставляете Visual Prolog самому искать способ решения. Другие языки програм- мирования, такие как Pascal, Basic и С — процедурные. Это означает, что вы долж- ны писать подпрограммы и функции, которые подробно "объяснят" компьютеру, какие шаги должны быть сделаны для решения задачи. Сейчас давайте оглянемся назад и рассмотрим некоторые моменты с точки зрения процедурного программирования. Факты и правила в качестве процедур Можно рассматривать правила Пролога как определения процедур. Например, пра- вило: likes(bill, Something) likes(cindy,Something) означает: "Для того чтобы доказать, что Билл любит что-то, необходимо доказать, что Синди любит это".
292 Часть IV. Программирование на Visual Prolog Таким образом, видим, что предикаты типа: say_hello:~ write("Hello"), nl. и greet:- write("Hello, Earthlings!"), nl. соответствуют подпрограммам и функциям в других языках программирования. Вы можете рассматривать даже факты Пролога, как процедуры; например, факт likes(bill, pasta) означает: "Для того чтобы доказать, что Билл любит pasta, не нужно ничего делать, и если аргументы who и what в вашем запросе likes (who, what) — свободные перемен- ные, то вы можете присвоить им значения bill и pasta, соответственно". Далее мы покажем, как известные процедуры программирования (условное ветвле- ние, булевы выражения, безусловные переходы и возвращение результата вычисле- ния) могут быть реализованы в Прологе. Использование правил для условного ветвления Одно из основных различий между правилами в Прологе и процедурами в других языках программирования заключается в том, что Пролог позволяет задавать множе- ство альтернативных определений одной и той же процедуры. Это видно по "семей- ной" программе в гл. 10. Человек может быть предком, будучи отцом или матерью, поэтому определение предка состоит из двух правил. Вы можете использовать множество определений так же, как вы применяете пред- ложение case в Pascal, задавая множество альтернативных определений для каждого значения аргумента (или множества значений аргумента). Пролог же будет переби- рать одно правило за другим, пока не найдет то, которое подходит, и затем выпол- нит действие, заданное правилом (как в программе ch04e 13.pro листинга 11.12). Листинг 11.12. Программа ch04e13.pro predicates action(integer) clauses action(1):- nl, write("You typed l."),nl. action(2) nl, write("You typed two."),nl.
Глава 11, Унификация и поиск с возвратом 293 action(3):- nl, write("Three was what you typed."),nl. action(N) nl, NO1, N<>2, N<>3, write("I don't know that number!"),nl. goal write("Type a number from 1 to 3: "), readint(Choice), action(Choice). Если пользователь нажмет клавиши <1>, <2> или <3>, action будет вызвана с соот- ветствующим значением аргумента и будет вызвано одно из первых трех правил это- го примера. Выполнение проверки в правиле Посмотрите более внимательно на четвертое правило для action. Оно будет сопос- тавлено для любого аргумента, переданного правилу. Если вы хотите быть уверен- ными, что оно не напечатает I don't know that number (Я не знаю такого числа), когда число попадает в правильный диапазон, — это задача для подцелей Х<>1, Х<>2, Х<>3 где о обозначает "не равно". Теперь, для того чтобы напечатать "Я не знаю такого числа", Пролог должен сначала доказать, что х не равен 1, 2 или 3. Если какая-либо из этих подцелей неуспешна, то Пролог попытается сделать откат и найти новые альтернативы. Но так как таких альтернатив нет, то остаток предложения никогда не будет выполнен. (Замечание j Предикат action подразумевает, что Choice уже связана. Если вы вызываете action со свободной переменной в качестве аргумента, то компилятор сгенерирует ошибку. Отсечение как GoTo Программа ch04el3.pro (см. листинг 11.12) не совсем корректна из-за того, что после выбора и выполнения нужного правила Пролог продолжает поиск альтернатив. Вы могли бы сэкономить ресурсы и время, если бы указали, где нужно прекратить поиск альтернатив, используя отсечение. Это означает: "Если вы дошли до этого места, то не нужно производить откаты внутри этого правила и не нужно проверять остальные альтернативы этого правила". Возвращение все еще возможно, но только на более высоком уровне. Если текущее правило вызывается другими правилами, и высшие правила имеют альтернативы, то они могут быть испробованы. Но отсечение отбрасывает альтернативы внутри пра- вила и альтернативы данного правила (предиката).
294 Часть IV. Программирование на Visual Prolog Используя отсечение (cut), эта программа может быть переписана следующим обра- зом (листинг 11.13). Листинг 11.13. Программа ch04e14.pro . - predicates action(integer) clauses action(1): -!, nl, write("You typed 1.”). action(2):-!, nl, write("You typed two."). action(3):-!, nl, write("Three was what you typed."). action(_):- writeCI don't know that number!"). goal write("Type a number from 1 to 3: "), readint(Num), action(Num),nl. Отсечение не имеет никакого эффекта, пока оно не будет выполнено реально. В приведенном выше примере, для того чтобы выполнить отсечение, Пролог должен войти в правило, содержащее отсечение и достичь точки, где расположено отсечение. Отсечение может быть представлено другими примерами: action(X) Х>3, • г write("Too high."). В этом правиле отсечение не произведет никакого действия, пока не будет достигну- та первая подцель х>з. Заметьте, что порядок правил здесь имеет значение. В программе ch04e!3.pro вы могли написать правила в любом порядке; только одно из них сопоставлялось с конкретным числом. Но в примере ch04e!4.pro вы должны быть уверены, что ком- пьютер не сделает попытки выполнить правило, печатающее "Я не знаю такого чис- ла", раньше, чем будут испробованы (и не выполнят своих отсечений) все предыду- щие правила. Отсечения в программе ch04el4.pro иногда называют красными отсечениями, т. к. они меняют логику программы. Если вы сохраните проверки х<>1, х<>2 и хоз, из- менив программу только вставкой отсечений в каждом предложении, то вы сделаете зеленые отсечения. Они экономят время и, тем не менее, оставляют программу такой
Гпава 11. Унификация и поиск с возвратом 295 же правильной, как и без отсечений. Выигрыш при этом не так велик, но риск вне- сти ошибку в программу уменьшается. Отсечение — это мощный, но и опасный оператор Пролога. В этом отношении он соответствует предложению GoTo, в остальных языках программирования он многое позволяет, но делает вашу программу более трудной для понимания. Возврат вычисленного значения Как мы уже видели, правила и факты Пролога могут возвращать информацию в цель, которая их вызывает. Это делается путем связывания переменных, которые были ранее не связанными. Факт likes(bill, cindy). возвращает информацию в цель likes (bill, Who) . путем присваивания переменной who значения cindy. Правило может возвращать тем же способом и результат вычислений. В листин- ге 11.14 приведен пример (программа cli04e 15.pro). ; Листинг 11.14. Программа ch04e15.pro predicates classify(integer,symbol) clauses classify(0,zero). classify(X,negative):- X < 0. classify(X,positive) X > 0. Первый аргумент classify должен всегда получать константу или связанную пере- менную. Второй аргумент может быть связанной или свободной переменной, он сопоставляется с символами zero, negative, pozitive в зависимости от значения первого аргумента. Здесь приведены несколько примеров правил, которые могут возвращать значения. 1. Вы можете узнать (используя Test Goal), положительно ли число: Goal classify(45, positive), yes Так как 45 больше 0, только третье предложение classify может быть успешным. Оно сопоставляет второй аргумент с positive. Но второй аргумент уже равен positive, поэтому сопоставление успешно, и вы получаете ответ yes (да).
296 Часть IV. Программирование на Visual Prolog 2. Если сопоставление неуспешно, вы получаете ответ по (нет): Goal classify(45, negative). no Что происходит при этом: • Пролог проверяет первое предложение, но первый аргумент не равен О (а также второй не равен zero); • затем он проверяет второе предложение, связав х с 45, но проверка х<0 неус- пешна; • после этого он проверяет третье предложение, но на этот раз второй аргумент не совпадает. 3. Для получения правильного ответа, а не yes или по, вы должны вызвать classify со свободным вторым аргументом. Goal classify(45, What). What=positive 1 Solution Что происходит в этом случае: • цель classify(45, what) не сопоставляется с заголовком первого предложе- ния, т. к. 45 не сопоставляется с 0. Первый класс использовать нельзя; • цель classify(45, what) снова сопоставляется с заголовком следующего предложения, classify(X, negative), связывая X с 45 и negative с What. Но подцель х<о неуспешна, т. к. х равен 45 и неверно, что 45<0, поэтому Пролог возвращается из этого предложения, освобождая созданные связи; • наконец, classify(45, what) сопоставляется с classify(X, positive), связы- вая х и 45, а также what и positive, подцель х>0 правильна. Так как это ус- пешное решение, Пролог не выполняет поиск с возвратом; он возвращается в вызывающую процедуру (которая в данном случае цель, которую вы задали). И поскольку переменная X принадлежит к вызывающей процедуре, эта про- цедура может использовать ее значение — в данном случае автоматически на- печатать его. Резюме В этой главе мы обсудили унификацию, поиск с возвратом, детерминизм, предикаты not, fail и cut, и рассмотрели предикаты Visual Prolog с процедурной точки зрения. □ Факты и правила Пролога получают информацию при вызове с аргументами, которые могут быть константами или связанными переменными; они возвраща- ют информацию в вызывающую процедуру путем, связывания аргументов, кото- рые являются несвязанными переменными. □ Унификация — это процесс сопоставления двух предикатов и присваивания сво- бодным переменным значений для того, чтобы сделать предикаты идентичными.
Гпава 11. Унификация и поиск с возвратом 297 Этот механизм необходим, чтобы Visual Prolog мог определить, какое предложе- ние вызвать и каким переменным присвоить значения. Представлены важные моменты, связанные с сопоставлением (унификацией): • когда Пролог начинает попытки достичь цели, он начинает поиск с начала программы; • когда вызов завершается успехом, говорят, что вызов возвратился, и делается попытка доказать следующую подцель; • если переменная была связана в предложении, единственный способ сделать ее снова свободной — это откат (поиск с возвратом). □ Поиск с возвратом — это механизм, который указывает Visual Prolog, как искать решения для программы. Этот процесс дает Прологу возможность перебрать все известные факты и правила для решения. Рассмотрены четыре основных прин- ципа поиска с возвратом: • подцели должны проверяться по порядку, сверху вниз; • предикатные предложения проверяются в том порядке, в котором они появ- ляются в программе, сверху вниз; • когда подцель сопоставляется с заголовком правила, тело правила должно по- сле этого быть доказано (тело правила состоит из новых подцелей, которые должны быть доказаны); • цель доказана, когда соответствующие факты найдены для каждой листьевой вершины дерева целей. □ Вызов, который может дать множество решений — недетерминированный, тогда как вызов, дающий одно и только одно решение — детерминированный. □ Visual Prolog дает три средства для управления направлением логического поиска в программе: • предикат fail всегда дает неуспех, он вызывает поиск с возвратом для того, чтобы искать другое решение; • предикат not дает успех, когда связанная с ним подцель не может быть дока- зана; • предикат cut отменяет поиск с возвратом. □ Правила Пролога с процедурной точки зрения могут действовать как предложе- ния case, представляя собой булевы функции, а также как оператор GoTo (при использовании cut), и возвращать вычисленные значения.
ГЛАВА 1 2 Простые и составные объекты До сих пор мы рассмотрели только несколько видов объектов данных Visual Prolog, таких как числа, идентификаторы и строки, В этой главе мы обсудим все типы объ- ектов данных, которые Visual Prolog может создавать, от простых до составных. Мы также покажем типы структур данных и объектов данных, которые могут содер- жаться в программах на Visual Prolog. Так как стандартные домены не покрывают составные структуры данных, мы объясним, как объявлять и использовать составные структуры данных в разделах domains и predicates. Простые объекты данных Простой объект данных — это переменная или константа. Не путайте это значение слова "константа'1 с символьными константами, которые вы определяете в разделе constants программы. То, что мы здесь называем константой, это нечто, идентифи- цирующее объект, который нельзя изменять: символ (char), число (integer или real) или атом (symbol или string). Переменные как объекты данных Названия переменных должны начинаться с заглавной буквы (A—Z) или с символа подчеркивания (_). Символ подчеркивания представляет анонимную переменную, которая используется в ситуации "неважно что". В Прологе переменная может свя- зываться с любым допустимым аргументом Пролога или объектом данных. Переменные Пролога локальны, а не глобальны. Так, если два предложения содер- жат переменную, названную X, то это две различные переменные. Они могут быть связаны друг с другом, если совпадут во время унификации, но обычно они не ока- зывают влияния друг на друга. Константы как объекты данных Константы включают символы, числа и атомы. Опять же, не путайте константы в данном контексте с символьными константами, определенными в разделе constants
Гпава 12. Простые и составные объекты 299 программы. Значение константы — это ее имя. Так константа 2 может соответство- вать только числу 2, а константа abracadabra — только идентификатору abracadabra. Символы Символы имеют тип char. Печатные символы (ASCII 32—127) — это цифры (0—9), прописные буквы A—Z, строчные буквы a—z, символы пунктуации и специальные символы. Литеры вне этого диапазона могут быть непереносимыми с одной плат- формы на другую; в особенности, литеры, меньшие ASCII 32 (пробел) и управ- ляющие символы, традиционно используемые терминалами и связующим оборудо- ванием. Символ-константа записывается в простых кавычках: 'а' '3' » * » ' { ’ 'W 'А' Если же вы хотите записать обратную косую черту или простую кавычку, как литер- ную константу, вы должны поставить перед ней символ обратный слэш \ (управ- ляющий escape-символ): ’\\’ backslash '\If single quote. Существует набор символьных констант, которые представляют специальные функ- ции в том случае, если им предшествует управляющий символ (табл. 12.1). Таблица 12.1. Символьные константы, представляющие специальные функции Константа Описание ' \п' Новая строка (перевод строки) ’ \ г ’ Возврат каретки ' \t' Табуляция (горизонтальная) Символьные константы могут также быть записаны своим десятичным ASCII-кодом после управляющего символа, например: ’ \2251 В ’\1341 Ж Но точная литера, отображенная для более экзотических значений ASCII, будет раз- личной в зависимости от вашей видеокарты/терминала. Числа Числа могут быть целыми (integer) или вещественными (real). Вещественные хра- нятся в стандартном формате IEEE и имеют значения от 1е-308 до 1е308 (от 1О“308 до 1О+308). Примеры целых и вещественных чисел приведены в табл. 12.2.
300 Часть IV. Программирование на Visual Prolog Таблица 12.2. Целые и вещественные числа Целые Вещественные 3 3. -77 34.96 32034 -32769 -10 4е27 0 -7.4е-296 Атомы Атомы имеют тип идентификатор (symbol) или строка (string). Отличие между ни- ми — главным образом вопрос машинного представления и реализации, и, в основ- ном, оно синтаксически не заметно. Когда атом передается в качестве аргумента при вызове предиката, то к какому домену принадлежит атом — symbol или string — определяется по тому, как описан этот аргумент в декларации предиката. Visual Prolog автоматически преобразует типы между доменами string и symbol, по- этому вы можете использовать атомы symbol в доменах string и наоборот. Однако принято считать, что объект в двойных кавычках принадлежит домену string, а объ- ект, не нуждающийся в кавычках, домену symbol. Атомы типа symbol — это имена, начинающиеся со строчной буквы и содержащие только буквы, цифры и знак под- черкивания. Атомы типа string выделяются двойными кавычками и могут содержать любую комбинацию литер, кроме ASCII-нуля (0, бинарный нуль), который обозначает ко- нец строки атома. Примеры строк и идентификаторов приведены в табл. 12.3. Таблица 12.3. Строки и идентификаторы Атомы-идентификаторы Атомы-строки food "Jesse James" rick__Jones_2nd "123 Pike street" f red__Flintstone_1000_Bc_Bedrock " jon" a "a" new_york "New York" pdcProlog "Visual Prolog, by Prolog Development Center" Так как string/symbol взаимозаменяемы, их отличие не существенно. Однако имена предикатов и функторы для составных объектов должны соответствовать синтакси- ческим соглашениям домена symbol.
Гпава 12. Простые и составные объекты 301 Составные объекты данных и функторы Составные объекты данных позволяют интерпретировать некоторые части информа- ции как единое целое таким образом, чтобы затем можно было легко разделить их вновь. Возьмем, например, дату "октябрь 15, 1991". Она состоит из трех частей ин- формации — месяц, день и год. Представим ее на рис. 12.1, как древовидную струк- туру. DATE October 15 1991 Рис. 12.1. Древовидная структура даты Вы можете сделать это, объявив домен, содержащий составной объект date: domains date_cmp = date(string,unsigned,unsigned) а затем просто записать: D = date("October",15,1991). Такая запись выглядит как факт Пролога, но это не так — это объект данных, кото- рый вы можете обрабатывать наряду с символами и числами. Он начинается с име- ни, называемого функтором (в данном случае date), за которым следуют три аргу- мента. Обратите внимание, что функтор в Visual Prolog — не то же самое, что функция в других языках программирования; это просто имя, которое определяет вид состав- ного объекта данных и объединяет вместе его аргументы. Внимание! Функтор не обозначает, что будут выполнены какие-либо вычисления. Аргументы составного объекта данных могут сами быть составными объектами. На- пример, вы можете рассматривать чей-нибудь день рождения (рис. 12.2), как ин- формацию со следующей структурой: birthday person date / \ / I \ "Leo" "Jensen" "Apr" 14 1960 Рис. 12.2. Древовидная структура даты рождения
302 Часть IV. Программирование на Visual Prolog На языке Пролог это выглядит следующим образом: birthday(person("Leo","Jensen”),date("Apr”r14,1960)) У составного объекта birthday в этом примере есть две части: объект person("Leo”, "Jensen") и объект date ("Apr", 14, I960), Функторами для этих объ- ектов будут person И date. Унификация составных объектов Составной объект может быть унифицирован с простой переменной или с состав- ным объектом (возможно, содержащим переменные в качестве частей во внутренней структуре), который ему соответствует. Это означает, что составной объект можно использовать для того, чтобы передавать целый набор значений как единый объект, и затем применять унификацию для их разделения. Например: date("April",14,1960) сопоставляется с х и присваивает х значение date ("April", 14,1960). Также date("April”,14,1960) сопоставляется с date (Mo, Da, Yr) и присваивает переменным Mo = "April", Da = 14 И Yr - 1960. Некоторые примеры программ с составными объектами приведены в следующем разделе. Использование знака равенства для унификации составных объектов Visual Prolog осуществляет унификацию в двух случаях. Во-первых, когда цель со- поставляется с заголовком предложений. Во-вторых, через знак равенства (=), кото- рый является инфиксным предикатом (предикатом, который расположен между своими аргументами, а не перед ними). Фактически, Visual Prolog выполняет операцию присваивания для унификации объ- ектов по разные стороны знака равенства. Это свойство полезно для нахождения значений аргументов составного объекта. Например, программа ch05e01.pro (лис- тинг 12.1) проверяет, совпадают ли фамилии у двух людей, и затем дает второму человеку тот же адрес, что и у первого. . . > > >• . .. »•> ......... . . . >>> но .<><><. ... « . . *^*а *> > i Листинг 12.1. Программа ch05e01.pro ii'f 'ii'o -* * if'.t liH . t ir >«>>>>>, I r>i . Н^,1И>||П * Ilion 111,111'»b «hvl >at.»,' *>> >>* >* ***>>> (4 ‘ fe 1i»**m*‘»ii1 •'* * '* >*' * ** ** domains person ~ person(name,address) name ~ name(first,last) address = addr(street, city, state) street = street(number,street name) city,state,street_name = string
Гпава 12. Простые и составные объекты 303 first,last number = string = integer goal Pl = person(name(jim,mos),addr(street(5,"1st st"),igo,"CA")), Pl = person(name(_,mos),Address), P2 ~ person(name(jane,mos),Address), write("Pl=",Pl),nl, write("P2=",P2),nl. Использование нескольких значений как единого целого Составные объекты могут рассматриваться в предложениях Пролога как единые объ- екты, что сильно упрощает написание программ. Рассмотрим, например, факт: owns(john, bookfFrom Here to Eternity", "James Jones")). в котором утверждается, что у Джона есть книга "From Here to Eternity" (Отсю- да в вечность), написанная James Jones (Джеймсом Джонсом). Аналогично можно записать: owns(john, horse(blacky)). что означает: John owns a horse named blacky.(У Джона есть лошадь Влеки.) Составными объектами в этих двух примерах являются: book("From Here to Eternity", "James Jones") horse(blacky) Если вместо этого описать только два факта: owns(john, "From Here to Eternity"). owns(john, blacky). то нельзя было бы определить, является ли blacky названием книги или именем лошади. С другой стороны, можно использовать первый компонент составного объ- екта — функтор для распознавания различных объектов. Этот пример использует функторы book и horse для указания разницы между объектами. Внимание! Составные объекты состоят из функтора и объектов, принадлежащих этому функто- ру, например: functor(object1, object2, ..., objectN). Пример использования составных объектов Важная особенность составных объектов состоит в том, что они позволяют легко передавать группы величин, как один аргумент. Рассмотрим в качестве примера ве-
304 Часть IV. Программирование на Visual Prolog дение телефонной базы данных. Допустим, вы хотите включить в базу данных дни рождения ваших друзей и родственников. Для этого пришлось бы написать про- грамму, часть которой приведена ниже: predicates phone_list(symbol First, symbol Last, symbol Phone, symbol Month, integer Day, integer Year) clauses phone_l.ist (ed, will is, 422_02_08, aug, 3, 1955) . phone_list(chris, grahm, 433_99_06, may, 12, 1962). Обратите внимание, что в факте phone list шесть аргументов, пять из них могут быть разбиты (рис. 12.3) на два составных объекта. person birthday / \ / I \ First Name Last Name Month Day Year Рис. 12.3. Разбивка составных объектов Может оказаться более полезным представлять факты так, чтобы они отражали эти составные объекты данных. Вернувшись на шаг назад, видим, что person (лицо) — это отношение, а имя и фамилия — объекты. Также, birthday (день рождения) — это отношение между тремя аргументами: месяцем, днем и годом. В представлении Пролога они могут быть записаны следующим образом: person(First_name, Last_name) birthday(Month, Day, Year) Вы можете теперь переписать свою маленькую базу данных с включением этих со- ставных объектов как части вашей базы данных. domains name = person(symbol First, symbol Last) birthday - b_date(symbol Month, integer Day, integer Year) ph_num = symbol % Phone_number predicates phone_list(name, ph_num, birthday) clauses phone_list(person(ed,willis), "422-02-08", b_date(aug,3,1955)). phone_list (person (chris, grahm) , "433-99-06" , b__date (may, 12,1962) ) . В эту программу введены два определения составных доменов. Мы рассмотрим не- которые подробности этих составных структур данных далее, а сейчас остановимся на применении таких составных объектов.
Глава 12. Простые и составные объекты 305 Предикат phone list теперь содержит три аргумента, что отличается от шести — в предыдущем примере. Иногда разбиение данных в составном объекте делает более ясной логику программы и может помочь в обработке данных. А теперь добавим несколько правил в нашу маленькую программу. Допустим, вы хотите создать список людей, у которых день рождения в этом месяце. Ниже приве- дена программа ch05e03.pro (листинг 12.2), которая решает эту задачу, используя встроенный предикат date для получения даты из внутреннего календаря компьюте- ра. Предикат date будет описан позднее, в гл. 22, сейчас же достаточно знать, что он возвращает текущие год, месяц и день из календаря компьютера. domains name = person(symbol,symbol) % (первый,последний) birthday = b_date(symbol,integer,integer) % (месяц,день,год) ph_num = symbol % телефонный номер predicates phone_list(name,ph_num,birthday) get_months_birthdays() convert_month(symbol, integer) check_birthday_month(integer,birthday) write_person(name) clauses get_months_birthdays:- write("************ This Month’s Birthday List *************")fnl, write(" First name\t\t Last Name\n"), date(_, This_month, _), % получить месяц из системных часов phone_list(Person, _, Date), check_birthday_month(This_month, Date), write_person(Person) , f ail. get_months^birthdays:- write("\n\n Press any key to continue: "),nl, readchar( ). write_person (person (First_name, Last__name) ) write (" ”, First_name, "\t\t ",LasVname) ,nl. check_birthday_month(Mon,b_date(Month,_,_)) convert_month(Month,Monthl)f Mon = Monthl. phone_list (person (ed, willis) , ”7 67-84 63'', b_date (jan, 3,1955)). phone_list(person(benjamin,thomas), "438-8400", b_date(feb,5,1985)). phone_list(person(ray,william)f "555-5653", b_date(mar,3,1935)). phone_list(person(thomas,alfred), ”767-2223", b_date(apr,29,1951)).
306 Часть IV. Программирование на Visual Prolog phone_list (person(chris, grahm) , "555-1212", b__date (may, 12,1962)) . phone_list(person(dustin,robert), "4 38-8400", b_date(jun, 17, 1980)). phone_list(person(anna,friend), ”767-8463", b_date(jun,20,1986)). phone_list(person(brandy,rae), "555-5653", b_date(jul,16,1981)). phone_list (person(naomi, friend) , "767-2223", b__date (aug, 10,1981) ) . phone_list(person(christina,lynn), "438-8400", b_date(sep,25,1981)). phone_list (person(kathy, ann), ”438-8400", b__date (oct, 20,1952) ) . phone__list (person (elizabeth, ann), "555-1212”, b_date (nov, 9, 1984 )) . phone_list(person(aaron,friend), "767-2223", b_date(nov,15,1987)). phone__list (person (jennifer, caitlin) , "438-8400", b__date (dec, 31,1981)) . convert_month(jan,1). convert__month (feb, 2) . convert_month(mar,3). convertjnonth (apr, 4) . convert_month(may, 5). convert_month(jun,6). convert^month(jul, 7). convert_month(aug,8). convertjnonth (sep,9). convertjnonth(oct,10). convert_month(nov,11). convertjnonth (dec, 12) . goal get jnonths_birthdays(). Загрузите и запустите эту программу в Test Goal. 1. Программа использует окно для вывода результата. 2. Помещает в окне заголовок, помогающий понять результат. 3. Использует в предикате getjnonth_birthdays встроенный предикат date. 4. Кроме того, программа производит поиск в базе данных и печатает список лю- дей, родившихся в текущем месяце. Сначала ищется первый человек в базе дан- ных. Вызов phone_list (Person, Date) помещает имя и фамилию человека ъ переменную person, помещая функтор person целиком в Person, а день рожде- ния — в переменную Date. Заметим, что необходимы две переменные: одна для хранения полного имени че- ловека, а другая — для хранения дня рождения. Это достигается за счет исполь- зования составных объектов. 5. Ваша программа может теперь передавать день рождения человека просто путем передачи переменной Date. Это происходит в следующей подцели, где программа передает текущий месяц (представленный целым числом) и день рождения в предикат check_birthdayjnonth. 6. Visual Prolog вызывает предикат check birthday month с двумя переменными первая переменная связана с целым, а вторая — с термом birthday. В заголовке
Гпава 12. Простые и составные объекты 307 правила, которое определяет check_birthday_month, первый аргумент, This_month, сравнивается с переменной моп. Второй аргумент, Date, сопоставля- ется с b_date (Month, _,_) . Так как нас интересует месяц рождения человека, используются анонимные пе- ременные для даты и года рождения. 7. Предикат check_birtday__month сначала превращает символ месяца в целое число. После того, как это сделано, Visual Prolog может сравнить значение текущего ме- сяца с месяцем рождения человека. Если это сравнение удачно, подцель check birthday month завершается успешно и обработка продолжается. Если сравнение неуспешно (человек родился в другом месяце), Visual Prolog откатыва- ется для поиска другого решения задачи. 8. Следующая подцель, которую нужно обработать, — write__person. Лицо, данные которого нужно обработать, имеет день рождения в этом месяце, и поэтому в от- чет попадают только правильные данные. После распечатки информации пред- ложение терпит неуспех, что вызывает поиск с возвратом. 9. Поиск с возвратом всегда возвращается к последнему неудовлетворенному вызо- ву и пытается его удовлетворить. В данной программе последний неудовлетво- ренный вызов — phone_list. Поэтому программа будет искать другое лицо, ко- торое может быть обработано. Если в базе данных нет больше людей, текущее предложение терпит неуспех, Visual Prolog пытается доказать этот вызов, про- сматривая базу данных дальше, но т. к. есть еще предложение getjnonth_birthdays, Visual Prolog пытается доказать его, доказав подцели этого предложения. Большая часть обработки заключена в предикате get_month_birthdays. Чем помога- ют составные объекты в этой программе? Упражнение Улучшите приведенную программу (листинг 12.2) так, чтобы она печатала также даты рождения перечисленных людей. Затем добавьте в отчет телефонные номера. Объявление составных доменов Рассмотрим, как определяются составные домены. После компиляции программы, которая содержит следующие отношения: owns(john, book("From Here, to Eternity", "James Jones”)). и owns(john, horse(blacky)). вы должны послать системе запрос в следующем виде: owns(john, X) Переменная х может быть связана с различными типами объектов: книга, лошадь и, возможно, другими объектами, которые вы определите. Отметим, что теперь вы не можете более использовать старое определение предиката owns: owns(symbol, symbol)
308 Часть IV, Программирование на Visual Prolog Второй элемент более не является объектом типа symbol. Вместо этого вы можете дать новое определение этого предиката: owns(name, articles) Домен articles в разделе domains можно описать так: domains articles = book(title,author); horse(name) % Articles — это books или horses title, author, name = symbol Точка с запятой читается как "или”. В этом случае возможны два варианта: книга будет определяться своим заглавием и автором, а лошадь будет распознаваться своим именем. Домены title, author и name имеют стандартный тип symbol. К определению домена легко могут быть добавлены другие варианты. Например, articles может также включать лодку, дом, чековую книжку. Лодку можно опреде- лить функтором без присоединенных к нему аргументов. С другой стороны, вы мо- жете включить платежный баланс, как часть чековой книжки. Определение домена articles расширится до: Articles = book(title, author); horse(name); boat; bankbook(balance) title, author, name = symbol balance = real Ниже приведена полная программа ch05e04.pro (листинг 12.3), которая показывает, как составные объекты из домена articles могут использоваться в фактах, которые определяют предикат owns. ГТ1истинг12.3.ПрЬграммасЬ05е04.рго 1 J41оt*4%«*!* V*4*4• i44'4*44(й«**'<«• «Ь«V*4*4'44*4*^4<Г&«4«'44 k i«44«f4‘iw«>'«?<Г«w'w444,L«|fk«444^444 V«4"«v«444«*4i4k44*4«44444V**4»••••4»♦’»• •*4• •• •• • *it Л *4it4444(F4tf f Й***4***4*4*4i*4***444* 4*4*4*4Л4'4«4«4**#*ГТ*i 4Г4,444,<4tr««44« ^**4*4*444«»-*4 domains articles = book(title,author) ; horse(name); boat; bankbook(balance) title, author, name = symbol balance = real predicates owns(name,articles) clauses owns(john, bookfA friend of the family", "Irwin Shaw”)), owns(john, horse(blacky)). owns(john, boat). ownsfjohn, bankbook(1000)). goal owns (john, Thing). А теперь загрузите программу в VDE и запустите Test Goal.
глава 12. Простые и составные объекты 309 Visual Prolog (Test Goal) ответит: Thing-book("A friend of the family”,"Irwin Shaw”) Thing=horse("blacky") Thing=boat Ihing=bankbook(1000) 4 Solutions Ниже дано общее представление о том, как декларировать домены для составных объектов: romain = alternative!(D Name, D Name, ...); alternative2(D Name, D Name, ...); Здесь alternative! и alternatives — допустимые (но различные) функторы. Запись d, D, ...) представляет список имен доменов, которые объявлены где-то в про- грамме или являются стандартными типами доменов (такими как symbol, integer, real и др.). Необязательные параметры Name могут использоваться для комментария имен аргументов; они будут игнорироваться компилятором. Замечания J • Альтернативы разделяются точкой с запятой. • Каждая альтернатива состоит из функтора и, возможно, списка доменов соот- ветствующих аргументов. • Если функтор не имеет аргументов, вы можете записать в вашей программе al- ternativeN или alternativeN (). В данной книге используется первый вариант синтаксиса. Многоуровневые составные объекты Visual Prolog позволяет конструировать составные объекты на нескольких уровнях. Например, в: book("The Ugly Duckling", "Andersen") вместо фамилии автора вы можете использовать новую структуру, которая описыва- ет автора более детально, включая имя и фамилию. Вызывая функтор для нового объекта author (автор), вы можете изменить описание книги на: book("The Ugly Duckling", author("Hans Christian", "Andersen")) В старом определении домена book(title, author) вторым аргументом функтора book был author. Но старое определение author = symbol может включать только одно имя, а этого уже недостаточно. Определим теперь author, как составной объект, состоящий из имени и фамилии автора.
310 Часть IV. Программирование на Visual Prolog Это достигается с помощью декларации следующего домена: author = author(first_name, last_name) что приводит к следующим определениям: domains articles - book(title, author); . . author = author(first_name, last_name) title, first_name, last__name = symbol % Первый уровень % Второй уровень % Третий уровень При использовании составных объектов со многими уровнями часто помогает такое ’'дерево” (рис. 12.4): book title author f irst_name last__name Рис. 12.4. Дерево многоуровневого составного объекта Декларация домена объявляет только один уровень дерева, а не целое дерево. На- пример, book не может быть описана таким предложением: domains book = book(title,author(first_name,last_name)) % Неправильно Рассмотрим в качестве еще одного примера составных объектов грамматический разбор структуры предложения: У Элен есть книга. Наиболее простая структура английского предложения состоит из существительного и группы сказуемого: предложение = предложение(существительное, группа_сказуемого) Существительное — это просто слово: существительное = существительное(слово) А группа сказуемого состоит из глагола и группы существительного или из одного глагола. группа__сказуемого = группа__сказуемого (глагол, существительное); глагол - глагол(слово) Используя эти определения доменов (предложение, существительное, сказуемое и глагол), предложение У Элен есть книга будет записано: предложение(существительное(Элен), группа__сказуемого (глагол (имеет) , существительное (книга) ) )
лава 12. Простые и составные объекты 311 предложение существительное Элен группа_сказуемого глагол существительное I I имеет книга Рис. 12.5. Дерево предложения Соответствующее дерево представлено на рис. 12.5. Структура данных наподобие этой может получаться на выходе из программы син- таксического разбора, который определяет грамматическую структуру предложения. Разбор не встроен в Visual Prolog, но мы включили в ваш пакет программ на прила- гаемом CD простой анализатор предложений. Попытайтесь запустить проект <CDROM>:\RUN\VP1\PROGRAMS\SEN AN, когда будете готовы заняться им. Упражнения 1. Напишите подходящие определения доменов с помощью составных объектов Visual Prolog, которые могут быть использованы в каталоге музыкальных пьес. Типичное содержимое каталога: Show: West Side Story Lyrics: Stephen Sondheim Music: Leonard Bernstein (Пьеса : Вестсайдская история) (Текст : Стефан Сондхейм) (Музыка: Леонард Бернстайн) 2. Используя составные объекты везде, где возможно, напишите программу на Visual Prolog, поддерживающую, например, базу данных о сенаторах США. Дан- ные должны включать имя и фамилию сенатора, штат и партию, размер предста- вительства, дату выборов и запись голосований по десяти законопроектам. Определение составных смешанных доменов В этом разделе мы обсудим различные типы определений доменов, которые можно добавлять к программам. Эти объявления позволят использовать предикаты, кото- рые имеют возможность: □ получать аргумент более чем одного типа; □ получать различное количество аргументов, все разных указанных типов; □ получать различное количество аргументов, некоторые из которых могут быть более чем одного из возможных типов. Аргументы множественных типов Для того чтобы позволить предикатам Visual Prolog получать аргументы, которые содержат информацию различных типов, вам нужно добавить описание функтора.
312 Часть IV. Программирование на Visual Prolog В следующем примере предложение your_age (ваш возраст) получает аргумент типа аде (возраст), который может иметь тип string, real или integer. domains age = i(integer); r(real); s(string) predicates your_age(age) clauses your_age(i(Age)) write(Age). your__age (r (Age) ) write (Age). your_age(s(Age)) write(Age). Visual Prolog не допустит следующего описания домена: domains age = integer; real; string% He разрешено Списки Предположим, вы хотите заполнить расписание занятий по различным предметам, которые могут проводить разные преподаватели. Вы можете написать следующую программу: predicates teacher(symbol First_name, symbol Last_name, symbol Class) % учитель(имя, фамилия, класс) clauses teacher(ed, willis, englishl) . teacher(ed, willis, mathl). teacher(ed, willis, historyl). teacher(mary, maker, history2). teacher(mary, maker, math2). teacher(chris, grahm, geometry). Здесь вы должны повторять имя учителя для каждого предмета, который он или она ведет. Для каждого предмета вам приходится добавлять факт к базе данных. Хотя это и совершенно правильно в такой ситуации, но можно найти школу, где преподают сотни предметов; такой тип данных становится слишком сложным. Здесь было бы удобно создать аргумент для предиката, который содержит одно или несколько зна- чений. Список в Прологе ~ это как раз то, что нужно. В следующей программе аргумент class (класс) имеет тип "список*’. Мы покажем здесь, как список представляется в Прологе, а предикаты, работающие со списками, опишем в гл. 14. domains classes ~ symbol* % объявляем домен-список
Гпава 12. Простые и составные объекты 313 predicates teacher(symbol First, symbol Last, classes Classes) % ( имя фамилия предметы) clauses teacher(ed, willis, [englishl, mathl, historyl]). teacher(mary, maker, [history2, math2]). teacher(chris, grahm, [geometry]). В этом примере текст программы более краток и понятен, чем в предыдущем. Обра- тите внимание на определение домена: domains * classes = symbol* Звездочка (*) обозначает, что classes — это список идентификаторов. Так же про- сто можно объявить список целых: domains integer_list = integer* После того, как домен определен, его очень просто использовать; поместите его в качестве аргумента в предикат в разделе predicates. Ниже приведен пример ис- пользования списка целых: domains integer_list - integer* predicates test_scores (symbol First, symbol Last, integer__list Test_Scores) /* список_дэезультатов (имя фамилия список_результатов */ clauses test_scores(lisa, lavender, [86, 91, 75]). test__s cores (libby, dazzner, [79, 75]). test_scores(jeff, zheutlin, []). В случае Jeff zheutlin обратите внимание, что список может вообще не иметь эле- ментов. Списки описаны более детально в гл. 14. Резюме В этой главе были рассмотрены следующие важные моменты: □ Программа на Visual Prolog может содержать множество типов объектов данных: простые и составные, стандартные и определяемые пользователем. Простой объ- ект данных — это один из следующих: • переменная, например, х, MyVariable, _another_variable, либо подчеркива- ние (_) для анонимной переменной; • константа: символ char, число real или integer, атом symbol или string.
314 Часть IV. Программирование на VisualProlog □ Составные объекты данных позволяют вам рассматривать несколько частей ин- формации как единое целое. Составной объект данных состоит из имени (функ- тора) и из одного или более аргументов. Домен можно определить как несколько альтернативных функторов. □ Функтор Visual Prolog — это не то же самое, что функция в других языках про- граммирования. Функтор не обозначает выполнения каких-либо вычислений. Это просто имя, которое идентифицирует вид составных объектов данных и объ- единяет вместе свои аргументы. □ Составной объект может рассматриваться как единый объект; для различения объектов можно использовать функторы. Visual Prolog позволяет конструировать составные объекты с несколькими уровнями; аргументы составного объекта также могут быть составными объектами. Со смешанными составными доменами мож- но использовать предикаты, которые получают: • аргумент более чем одного из возможных типов (объявление функтора); • различное число аргументов, каждый определенного типа (объявление спи- сков); • разное количество аргументов, некоторые из них могут быть более чем одного из возможных типов.
ГЛАВА 1 3 Повтор и рекурсия Компьютеры способны повторять одно и то же действие снова и снова, Visual Prolog может выражать повторение как в процедурах, так и в структурах данных. Идея по- вторяющихся структур данных может показаться странной, но Пролог позволяет создавать структуры данных, размер которых не известен во время создания. В этой главе мы обсудим процесс повторения (циклы и рекурсивные процедуры), а затем рассмотрим рекурсивные структуры данных. Процесс повторения Программисты на языках Pascal, Basic или С, которые начинают использовать Visual Prolog, часто испытывают разочарование, обнаружив, что язык не имеет конструк- ций for, while или repeat. Не существует прямого способа выражения повтора. Пролог обеспечивает только два вида повторения — откат., с помощью которого осуществляется поиск многих решений в одном запросе, и рекурсию, в которой про- цедура вызывает сама себя. Как будет показано, этот недостаток не снижает мощи Пролога. Фактически, Visual Prolog распознает специальный случай рекурсии — хвостовую рекурсию — и компи- лирует ее в оптимизированную итерационную петлю. Это означает, что хотя про- граммная логика и выражается рекурсивно, скомпилированный код так же эффек- тивен, как если бы программа была написана на Pascal или Basic. В этом разделе мы расширим ваше искусство писать повторяющиеся процессы на Прологе. Вы увидите, что рекурсия во многих случаях яснее, более логична и менее чревата ошибками, чем циклы, используемые обычными языками программиро- вания. Перед погружением в рекурсию, однако, рассмотрим еще раз поиск с воз- вратом. Снова поиск с возвратом Когда выполняется процедура поиска с возвратом (откат), происходит поиск другого решения целевого утверждения. Это осуществляется путем возврата к последней из проверенных подцелей, имеющей альтернативное решение, использования следую-
316 Часть IV. Программирование на Visual Prolog щей альтернативы этой подцели и новой попытки движения вперед. Программа ch06e01.pro (листинг 13.1) показывает, как используется поиск с возвратом для вы- полнения повторяющихся операций. Эта программа выводит на печатающее устрой- ство все возможные решения запроса. ; Листинг 13.1. Программа ch06e01 .pro predicates country(symbol) print_countries clauses country("England"). country("France"). country("Germany") . country("Denmark" ) . print__countries: - country(X) , write(X)t nl, fail. print_countries. goal print__countries. % записать значение X % начать новую строку Предикат country составляет список названий различных стран, которые являются решениями для поставленного целевого утверждения: country(X) Затем предикат print countries отпечатывает все эти решения. Он определен сле- дующим образом: print_countries country(X), write(X), nl, fail. print countries. Первое предложение гласит: "Чтобы отпечатать страны, надо найти решения country (X), написать значение х, начать новую строку и инициировать неудачное завершение". В этом случае "неудачное завершение" (fail) означает: "Принять, что решение поставленного целевого утверждения не было достигнуто, поэтому вернуться назад и искать альтернативное решение". Встроенный предикат fail всегда вызывает "неудачное завершение", но можно было бы вызвать поиск с возвратом и обратившись к таким целевым утверждениям, как, например: 5 = 2 + 2 или country (shangri_la).
Гпава 13. Повтор и рекурсия 317 Первый проход выполняется до конца, х присваивается значение england, которое выводится на печать. Затем, наткнувшись на fail, компьютер возвращается в нача- ло. Так как nl или write(X) не имеют альтернативных решений, то компьютер пе- реходит к поиску следующего решения предиката country (X). При последнем выполнении country (X), ранее свободная переменная х стала свя- занной. Поэтому, снова выполняя country (X), компьютер освобождает переменную х, находит альтернативное решение предиката country (X) и присваивает х новое значение. После этого обработка продолжается, и название другой страны выводит- ся на печать. В конце концов, первое предложение будет проверено для всех альтернатив. Оста- нется выполнить только второе предложение того же предиката, что происходит без каких-либо дополнительных сложностей. И наконец, целевое утверждение print_countries успешно завершится следующим результатом: england france germany denmark yes Если бы второго предложения не было, то целевое утверждение print countries не выполнилось бы, и последнее сообщение было бы по, при этом результат был бы таким же. Упражнение Измените программу ch06e01.pro так, чтобы предикат country имел 2 характеристи- ки — name (название) и population (население) — и на печать выводились только названия стран, имеющих население более 10 миллионов (1е+7). Предварительные и последующие операции Отметим, что программа, которая находит решения для целевого утверждения, мо- жет выполнять какие-либо предварительные или завершающие операции. Например, в нашем примере программа могла бы: 1. Напечатать Some delightful places to live are... (Некоторые восхитительные места для проживания...). 2. Напечатать все решения для country (X). 3. Завершить печать фразой And maybe others (могут быть и другие). Заметьте, что print_countries, определенное в предыдущем примере, уже содержит предложение вывести на печать все решения country (X) и отпечатать завершающее сообщение. Первое предложение для print_countries соответствует шагу 2 и выводит на печать все решения. Его второе предложение соответствует шагу 3 и просто успешно за- вершает целевое утверждение (потому что первое предложение всегда в режиме fail — ’’неудачное завершение").
318 Часть IV. Программирование на Visual Prolog Можно было бы изменить второе предложение в программе ch06e01.pro. print_countries :- write ("And maybe others.’’) л nl. которое выполнило бы шаг 3, как указано. А что можно сказать о шаге 1? В нем нет смысла, когда print_countries содержит только 2 предложения. Но в предикате может быть и три предложения: print_countries :- write("Some delightful places to live are’’),nl, fail. print__countries : - country(X), write(X), nl, print__countries : - write("And maybe others."), nl. Наличие fail в первом предложении важно, поскольку он обеспечивает после вы- полнения первого предложения возврат и переход ко второму предложению. Кроме того, это важно, потому что предикаты write и nl не образуют альтернатив. Строго говоря, первое предложение проверяет все возможные решения перед тем, как завершиться неуспехом. Такая структура из трех предложений более удобна по сравнению с общепринятым подходом. Однако вы можете захотеть еще упростить код и применить такой метод для составления программы: print_countries_with__captions : - write("Some delightful places to live are"),nl, print_countries, write("And maybe others."),nl. print_countries :- country(X), write(X),nl, fail. Но такая программа работать не будет. Проблема в том, что, как написано в послед- нем примере, print countries всегда будет "неудачно завершаться", и print_ countries_with_captions никогда не вызовет выполнения подцелей, следующих за print_countries. В результате фраза And maybe others никогда не напечатается. Все, что необходимо сделать для исправления ошибки, — это восстановить второе предложение print_countries для предиката print_countries в его первоначальное состояние. Если вы хотите, чтобы целевое утверждение print countries_with_ captions было выполнимым, то print countries должно иметь, по крайней мере, одно предложение, не содержащее fail.
Гпава 13. Повтор и рекурсия 319 Использование отката с петлями Поиск с возвратом является хорошим способом определить все возможные решения целевого утверждения. Но, даже если ваша задача не имеет множества решений, можно использовать поиск с возвратом для выполнения итераций. Просто опреде- лите предикат с двумя предложениями: repeat. repeat :- repeat. Этот прием демонстрирует создание структуры управления Пролога (листинг 13.2), которая порождает бесконечное множество решений, а каким образом, — поймете позже, прочитав раздел о хвостовой рекурсии. Цель предиката repeat — допустить бесконечность поиска с возвратом (бесконечное количество откатов). ,* М/Л ’?-** 1 ,*<’ “''л» л ... IJJ *|<Л л « л л л 11,1 , л л I fi<iiUflfllHillHHi,tflllllufltl|l|llMi|((lll|liil|fl'ltl^l < * 1лл« лл iMIjmji а*«,л»4 |аы1,м*»' «, л« л«* ^ Листинг 13,2. Программа ch06e02.pro '-. :.7> . >,,!>>> «|>Мн11|*а11‘/111|11И«>>|1|1|«1>>11|111Н>411>1 .ЛЛ.1Л.Л1 » л«.аа>ааа«**«а*а'ааааа*<«а«ЛГ1аа»аа».»ааЛааа<.»^»а /* Использование repeat для сохранения введенных символов и * * печатать их до тех пор, пока пользователь не нажмет Enter (Ввод).*/ predicates repeat typewriter clauses repeat. repeat:-repeat. typewriter:- repeat, readchar(C), % Читать символ, его значение присвоить С write(С), С ~ '\г',% Символ возврат каретки (Enter)? или неуспех । goal typewriter() , nl. За Программа ch06e02.pro показывает, как работает repeat. Правило typewriter: - опи- сывает процесс приема символов с клавиатуры и отображения их на экране, пока пользователь не нажмет клавишу <Enter> (<Return>). Правило typewriter работает следующим образом: 1. Выполняет repeat (который ничего не делает, но ставит точку отката). 2. Присваивает переменной с значение символа. 3. Отображает с. 4. Проверяет, соответствует ли с коду возврата каретки. 5. Если соответствует, то — завершение. Если нет — возвращается к точке отката и ищет альтернативы. Так как ни write, ни readchar не являются альтернативами,
320 Часть IV. Программирование на Visual Prolog постоянно происходит возврат к repeat, который всегда имеет альтернативные решения. 6. Теперь обработка снова продвигается вперед: считывает следующий символ, ото- бражает его и проверяет на соответствие коду возврата каретки. Заметьте, кстати, что с теряет свое значение после отката в позицию перед вызовом предиката readchar (С), который связывает переменную с. Такой тип освобождения переменной существенен, когда поиск с возвратом применяется для определения альтернативных решений целевого утверждения, но не эффективен при использова- нии поиска с возвратом в других целях. Смысл в том, что хотя поиск с возвратом и может повторять операции сколько угодно раз, он не способен '‘запомнить" что-либо из одного повторения для другого. Внимание! < ---- ' _ _ Все переменные теряют свои значения, когда обработка откатывается в позицию, предшествующую тем вызовам предикатов, которые эти значения устанавливали. Не существует простого способа сохранения значений счетчика числа повторений петли или любых других значений, вычисляемых при выполнении циклов повто- рения. Упражнение Измените программу ch06e02.pro так, чтобы, если набирать клавиши на нижнем регистре, то на дисплее отображались бы значения верхнего регистра. Рекурсивные процедуры Другой способ организации повторений — рекурсия. Рекурсивная процедура — это процедура, которая вызывает сама себя. В рекурсивной процедуре нет проблемы запоминания результатов ее выполнения, потому что любые вычисленные значения можно передавать из одного вызова в другой как аргументы рекурсивно вызываемо- го предиката. Логика рекурсии проста для осуществления. Забудьте на мгновение, что компьютер последовательно проходит ячейки памяти одну за другой, и представьте себе ЭВМ, способную "понять": Найти факториал числа N: Если N равно 1, то факториал равен 1 Иначе найти факториал N-1 и умножить его на N. Этот подход означает следующее: чтобы найти факториал 3, вы должны найти фак- ториал 2, а чтобы найти факториал 2, вы должны вычислить факториал 1. Вы може- те найти факториал 1 без обращения к другим факториалам, поэтому повторения не начнутся. Если у вас есть факториал 1, то умножаете его на 2, чтобы получить фак- ториал 2, а затем умножаете полученное на 3, чтобы получить факториал 3.
Глава 13. Повтор и рекурсия 321 В Visual Prolog это выглядит так: factorial(1, 1) :- ! . factorial(X, FactX) :- Y - X-l, factorial(Y, FactY), FactX = X*FactY. Полностью осуществление такого подхода к поставленной задаче представлено в программе сЬОбеОЗ.рго (листинг 13.3). Листинг 13.3. Программа ch06e03.pro Ml|lllvlbta||||frtata|||bbta*vi||fr.|tatavivib ««Гй**«««««й«к««««#ЬЬ l««|»|«|||«|»ta||l|b «ta4|ll«l«ta*«|lllll|«l«lta lb ta«ll*tata«««««lta tata««l*lta ««Ata taq.l«l«lta |T> •* « »ta ta«« Ita ««ta | vl Ita ta||frl<f<tata«ll»lta fr| ta b «« *«««««Ь ИГ* /* Рекурсивная программа вычисления факториалов. Рекурсия обычная, не хвостовая.*/ predicates factorial(unsigned,real) clauses factorial(1,1);-!. factorial(X,FactX) Y=X-1, factorial(Y,FactY) , FactX = X*FactY. goal X=3, factorial(X,FactX). Как компьютер выполняет предикат factorial в середине обработки предиката factorial? Если вы вызываете factorial с х=3, то factorial обратится к себе с х=2. Будет ли тогда х иметь два значения, или второе значение затирает первое, или...? Дело в том, что компьютер создает новую копию предиката factorial таким обра- зом, что factorial становится способным вызывать сам себя как полностью само- стоятельную процедуру. При этом, конечно, код выполнения не будет копироваться, но все аргументы и промежуточные переменные копируются. Информация хранится в области памяти, называемой стековым фреймом (stack frame) или просто стеком (stack), который создается каждый раз при вызове правила. Когда выполнение правила завершается, занятая его стековым фреймом память освобождается (если это не недетерминированный откат), и выполнение продолжа- ется в стековом фрейме правила-родителя. Преимущества рекурсии Рекурсия имеет три основных преимущества: О она может выражать алгоритмы, которые нельзя удобно выразить никаким дру- гим образом; О она логически проще метода итерации; □ она широко используется в обработке списков. 11 Зак. 963
322 Часть IV. Программирование на Visual Prolog Рекурсия — хороший способ для описания задач, содержащих в себе подзадачу тако- го же типа. Например, поиск в дереве (дерево состоит из более мелких деревьев) и рекурсивная сортировка (для сортировки списка, он разделяется на части, части сортируются и затем объединяются вместе). Логически рекурсивным алгоритмам присуща структура индуктивного математиче- ского доказательства. Приведенная выше рекурсивная программа вычисления фак- ториала сЬ06е02.рго описывает бесконечное множество различных вычислений с помощью всего лишь двух предложений. Это позволяет легко увидеть правиль- ность этих предложений. Кроме того, правильность каждого предложения может быть изучена независимо от другого. Оптимизация хвостовой рекурсии У рекурсии есть один большой недостаток — она съедает память. Всякий раз, когда одна процедура вызывает другую, информация о выполнении вызывающей процеду- ры должна быть сохранена для того, чтобы она (вызывающая процедура) могла, по- сле выполнения вызванной процедуры, возобновить выполнение на том же месте, где остановилась. Это означает, что если процедура вызывает себя 100 раз, то 100 различных состояний должно быть записано одновременно (состояния выполнения решения сохраняются в стековом фрейме). Максимальный размер стека у 16-битных платформ, таких как IBM PC, работающая под DOS, составляет 64 Кбайт, что по- зволяет разместить максимум 3000 или 4000 стековых фреймов. На 32-битных плат- формах стек теоретически может возрасти до нескольких гигабайт; но здесь проявят- ся другие системные ограничения, прежде чем стек переполнится. Что же можно сделать, чтобы избежать использования столь большого стекового пространства? Рассмотрим специальный случай, когда процедура может вызвать себя без сохране- ния информации о своем состоянии. Что, если вызывающая процедура не собирает- ся возобновлять свое выполнение после завершения вызванной процедуры? Предположим, что процедура вызывается последний раз, т. е. когда вызванная про- цедура завершит работу, вызывающая процедура не возобновит свое выполнение. Это значит, что вызывающей процедуре не нужно сохранять свое состояние, потому что эта информация уже не понадобится. Как только вызванная процедура завер- шится, работа процессора должна идти в направлении, указанном для вызывающей процедуры после ее выполнения. Например, допустим, что процедура А вызывает процедуру в, а в — с в качестве сво- его последнего шага. Когда в вызывает с, в не должна больше ничего делать. Поэто- му, вместо того чтобы сохранить в стеке процедуры в информацию о текущем со- стоянии С, вы можете переписать старую сохраненную информацию о состоянии в (которая больше не нужна) на текущую информацию о с, сделав соответствующие изменения в хранимой информации. Когда с закончит выполнение, она будет счи- тать, что она вызвана непосредственно процедурой А. Предположим, что на последнем шаге выполнения процедура в вместо процедуры с вызывает себя. Получается, что когда в вызывает в, стек (состояние) для вызываю- щей в должен быть заменен стеком для вызванной в. Это очень простая операция, просто аргументам присваиваются новые значения и затем выполнение процесса
Гпава 13. Повтор и рекурсия 323 возвращается на начало процедуры в. Поэтому, с процедурной точки зрения, проис- ходящее очень похоже на всего лишь обновление управляющих переменных в цикле. Эта операция называется оптимизацией хвостовой рекурсии (tail recursion optimiza- tion) или оптимизацией последнего вызова (last-call optimization). Обратите внимание, что по техническим причинам оптимизация последнего вызова неприменима к ре- курсивным функциям (предикаты, возвращающие значение, описаны в гл. 17). Как задать хвостовую рекурсию Что означает фраза "одна процедура вызывает другую, выполняя свой самый по- следний шаг"? На языке Пролог это значит: □ вызов является самой последней подцелью предложения; □ ранее в предложении не было точек возврата. Ниже приводится удовлетворяющий обоим условиям пример: count(N) write(N)f nl, NewN = N+l, count(NewN). Эта процедура является хвостовой рекурсией, которая вызывает себя без резервиро- вания нового стекового фрейма, и поэтому не истощает запас памяти. Как показы- вает программа ch06e04.pro (листинг 13.4), если вы дадите ей целевое утверждение count(0) . то предикат count будет печатать целые числа, начиная с 0, и никогда не остановит- ся. В конечном счете произойдет целочисленное переполнение, но остановки из-за истощения памяти не произойдет. |Листанг 13.4. Програм ма сh06е04.рго |Т1 * М b ь»1 •••••• tf * ьЧ » l»»»»»»»b1»1Hb,ibtbbbtb,t. |,|||,||111т1|Ь|.тьЬЬТЬЬЬТЬЬЬЬ>ЬЬЬЬ»>>ЬЬ,>1>>>«ЬЬ‘>»>»>||'>.>>>>||>|>|>|>|.>>>>>ьЬ'ь*>Ь>ЬЧ»*>ЬИь'.ЧЬ>ЬП>>>‘««>«‘«ь‘ь«"‘<‘‘"""М"Ь‘Ь»""""‘",‘",‘‘‘‘ь,’‘ *b* /* Программа с хвостовой рекурсией, которая не истощает память */ predicates count(ulong) clauses count(N):- write(’\r',N), NewN = N+l, count(NewN). GOAL nl, count(0). Упражнение Преобразуйте программу ch06e04.pro так, чтобы не было больше хвостовой рекур- сии. Сколько итераций может она выполнить до истощения своей памяти? Попро-
324 Часть IV. Программирование на Visual Prolog буйте и посмотрите. (На 32-битных платформах это займет заметное время, и про- грамма, вероятно, не превысит виртуального стекового адресного пространства. Бо- лее вероятно, что операционная система израсходует доступную физическую память. На 16-битных платформах число возможных итераций напрямую зависит от размера стека задачи.) Из-за чего возникает не оптимизированная хвостовая рекурсия Выше был продемонстрирован правильный пример использования хвостовой рекур- сии, программа ch06e05.pro показывает три ошибочных способа организации хво- стовой рекурсии. □ Если рекурсивный вызов — не самый последний шаг, процедура не является хво- стовой рекурсией. Например: badcountl(X) write('\r1,X), NewX = X+l, badcountl(NewX), nl. Каждый раз, когда badcountl вызывает себя, стек должен быть сохранен для того, чтобы обработку можно было вернуть к вызывающей процедуре, которая должна выполняться до nl. Поэтому она сделает всего несколько тысяч рекурсивных вы- зовов до исчерпания памяти. □ Другой способ сделать хвостовую рекурсию не оптимизированной — оставить некоторую возможную альтернативу непроверенной к моменту выполнения ре- курсивного вызова. Тогда стек должен быть сохранен, т. к. в случае неудачного завершения рекурсивного вызова вызывающая процедура может откатиться и на- чать проверять эту альтернативу. Например: badcount2(X):- write('\r',X), NewX = X+l, badcount2(NewX). badcount2(X) : - X < 0, write("X отрицательно."). Здесь первое предложение badcount2 вызывает себя, когда второе предложение еще не выполнено. Снова программа истощает память после определенного ко- личества вызовов. □ Для потери оптимизации хвостовой рекурсии не обязательно иметь непроверен- ную альтернативу как отдельное предложение рекурсивной процедуры. Непрове- ренная альтернатива может быть и в любом вызываемом предикате. Например: badcount3(X) write(1\r',Х),
Гпава 13. Повтор и рекурсия 325 NewX = Х+1, check(NewX), badcount3(NewX). Предположим, что х — положительная величина, как это обычно бывает. Когда badcount3 вызывает себя, первое предложение check достигает цели, а второе пред- ложение check еще не проверено. Поэтому badcount3 должен сохранить копию своего стекового фрейма, чтобы иметь возможность вернуться и начать проверять второе предложение check в случае, если рекурсивный вызов завершится неудачно (листинг 13.5). % В 32-битной архитектуре эти примеры будут выполняться достаточно долго, % занимая много памяти и значительно уменьшая общую производительность % системы. predicates badcountl(long) badcount2(long) badcount 3(long) check(long) clauses % badcountl: Рекурсивный вызов — не последний шаг. badcountl(X):- write('\r*,X), NewX = X+l, badcountl(NewX)f nl. % badcount2: Это предложение, которое не выполняется во время % осуществления рекурсивного вызова badcount2(X):- write('\r',Х), NewX - Х+1, badcount2(NewX). badcount2(X):- X < 0, write("X отрицательно."). % badcount3: Непроверенная альтернатива в процедуре, % вызванной перед рекурсивным вызовом. badcount3(X):- write('\r', X), NewX = Х+1, check(NewX),
326 Часть /И Программирование на Visual Prolog badcount3(NewX). check(Z) Z >= 0. check(Z):- Z < 0. 4 Заметьте, что badcount2 и badcount3 хуже, чем badcountl, потому что они генериру- ют точки возврата. Вероятно, вы сейчас думаете, что невозможно гарантировать, что процедура являет- ся оптимизированной хвостовой рекурсией. Хотя довольно просто сделать рекурсив- ный вызов в последней подцели заключительного предложения, но как гарантиро- вать, что в любых других вызываемых предикатах нет альтернатив? Отсечение позволяет отвергать все возможные излишние альтернативы. Чтобы уста- новить cut, необходимо использовать директиву компилятора check_determ (дирек- тивы компилятора описаны в гл. 23). Вы можете исправить badcount3 следующим образом (модифицируя его имя): cutcount3(X) write('\r'fX)t NewX = X+l, check(NewX) , i - / cutcount3(NewX). Команда ‘'отсечение" означает "сжечь мосты за собой", или, точнее, "однажды дос- тигнув этой точки, не обращать внимания на альтернативные предложения этого предиката и альтернативные решения предыдущих подцелей в данном предложе- нии". Что точнее — решайте сами. Поскольку альтернативы исключаются, фрейм стека не нужен и рекурсивный вызов может свободно идти дальше. "Отсечение" также эффективно и в badcount2, если переместить проверку из второго предложения в первое: cutcount2(X) X >= 0, !, write(f\rf,X), NewX = X+l, cutcount2(NewX). cutcount2(X) write("X is negative."). Отсечение — это действительно решение. Оно используется всякий раз, когда аль- тернативы нам не интересны. В исходной версии предыдущего примера второе предложение должно было оставлять выбор, т. к. первое предложение не содержало проверки х. Переместив проверку в первое предложение и отрицая ее, решение можно принять уже там, а отсечение установить в соответствии с утверждением: "Теперь я знаю, что я не должен писать, что х отрицателен". То же касается cutcount3. Предикат check показывает ситуацию, когда вы хотите совершить некую дополнительную операцию над х, основанную на знаке. Однако
Гпава 13. Повтор и рекурсия 327 код для check недетерминирован, и отсечение после его вызова — это все, на что вам надо решиться. Однако вышесказанное немного искусственно — возможно, бы- ло бы правильнее, чтобы check был детерминирован: check (Z) Z >- О, i • г ... % использование Z check(Z) Z < О, ... % использование Z Проверка во втором предложении check — полное отрицание проверки в первом, поэтому check можно переписать как: check(Z) Z >= О, t - 9 О, О • • • Если отсечение выполняется, компьютер предполагает, что непроверенных альтер- натив нет, и не создает стековый фрейм. Программа сИОбеОб.рго (листинг 13.6) содержит измененные версии badcount2 и badcount3. [ Листинг 13.6. Программа ch06e06.pro . iifii'ivtvi .г.ннн.мм. 1 а... а. а аа а а*а*а**v**.***a Д .* а а а а . **. а а «.а *а а а »«»,'» й. а» а а а а г а а а. а а. .*»а. а а а *а < а а. аа а.* а а о* а * * а* а /* Показывает, как bedcount2 и bedcount3 могут быть улучшены объявлением отсечения ("cut") для исключения непроверенных предложений. Эти версии используют оптимизированную хвостовую рекурсию. */ predicates cutcount2(long) cutcount3(long) check(long) clauses cutcount2(X):- X>=0, i - f write(’\r',X)f NewX — X+l, cutcount2(NewX). cutcount2(_):- write("X отрицательно."). cutcount3(X):- write('\r',X), NewX = X+l, check(NewX), i • f cutcount3(NewX).
328 Часть IV, Программирование на Visual Prolog check(Z):-Z >= 0. check(Z): -Z < 0. К сожалению, отсечение не сможет помочь с badcount 1, в котором необходимость создания копий стековых фреймов не связана с непроверенными альтернативами. Единственный способ усовершенствовать badcountl — произвести вычисления та- ким образом, чтобы рекурсивный вызов происходил в конце предложения. Использование аргументов в качестве переменных цикла Сейчас, после освоения хвостовой рекурсии, как бы вы поступили с циклическими переменными и счетчиками? Чтобы ответить на этот вопрос, мы совершим неболь- шое преобразование с Pascal на Пролог, предполагая, что вы знакомы с языком Pas- cal. Обычно результаты прямых переводов между двумя языками, как естественны- ми, так и языками программирования, достаточно убоги. И хотя приведенный ниже пример неплох и является разумной иллюстрацией чисто процедурного программи- рования на Прологе, вам никогда не следует писать программы на Visual Prolog ме- тодом слепого перевода их с другого языка. Пролог — очень мощный и выразитель- ный язык, и правильно написанные Пролог-программы показывают иной стиль программирования и имеют совсем иные проблемы, нежели программы на других языках. В разд. "Рекурсивные процедуры" данной главы мы показали вычисление факториала с помощью рекурсивной процедуры. Здесь мы используем для этого итерацию. В Pascal это выглядело бы так: Р : = 1; for I := 1 to N do P :== P*I; FactN := P; Если вы знакомы c Pascal, то знаете, что := является оператором присваивания и произносится как "присвоить”. Здесь 4 переменных. N — число, факториал которого будет вычисляться; FactN — результат вычисления; I — циклическая переменная, изменяемая от 1 до n; р — суммирующая переменная. Конечно, опытный програм- мист на Pascal объединил бы FactN и р, но для перевода на Пролог так будет удоб- нее. Первый шаг в переводе на Пролог — замена for более простой формулировкой для цикла, точнее определяющей, что происходит с I на каждом шаге. Используем для этого определение while: Р := 1; /* Инициализация Р и I */ I := 1; while I <= N do /* Задание цикла */ begin Р := Р*1; /* Обновление Р и I */ I := 1+1 end; FactN := Р; /* Показать результат */
Гпава 13. Повтор и рекурсия 329 Программа ch06e07.pro (листинг 13.7) показывает переведенный на Пролог цикл while языка Pascal. ;****.»^*" ...................... ******»,>'<***.*'*.******<* ; Листинг 137. Программа ch06e07.pro кЛЛ*......-.......-.--.*»....-,.......... ......I»'...... .....г....-.г.т.Л.......... ..г predicates factorial(unsigned,long) factorial_aux(unsigned,lo;^,unsigned,long) % Числа, которые вероятг: . анут большими, объявляются long. clauses factorial(N, FactN):- factorial_aux(N,FactN,1,1). factorial_aux(N,FactN,I,P) I <= N,!, NewP = P * I, Newl =1+1, factorial__aux (N, FactN, Newl, NewP) . factorial_aux(N, FactN, I, P) :- I > N, FactN = P. Рассмотрим программу более детально. У предложения для предиката factorial есть только два аргумента — N и FactN. Они являются как бы входом и выходом, если смотреть с точки зрения того, кто вычис- ляет факториал. Предложения для factor i a laux (N, FactN, I, p) фактически обес- печивают рекурсию. Их аргументами являются четыре переменные, которые должны передаваться из одного шага в другой. Поэтому factorial просто вызывает factorial aux, передавая ему N и FactN с начальными значениями для I и Р: factorial(N, FactN) factorial_aux(N, FactN, 1, 1). Так i и p инициализируются. Но как factorial передает FactN, ведь у нее пока нет еще значения? Ответ заключа- ется в том, что концептуально Visual Prolog здесь унифицирует переменную, назван- ную FactN в одном предложении, с переменной, названной FactN в другом предло- жении. Таким же образом factorial_aux передает себе FactN в качестве аргумента в рекурсивном вызове. В конечном счете последняя FactN получит значение, и после этого все другие FactN, которые унифицировались с ней, получат такое же значение. Мы сказали "концептуально", т. к. реально есть лишь одна FactN, Visual Prolog мо- жет определить из исходного кода, что FactN в действительности не используется перед вторым предложением factorial aux, а все время передается одна и та же FactN. Теперь о работе factorial aux. Обычно этот предикат проверяет предложение "/меньше либо равно У для циклического вычисления, а затем рекурсивно вызыва-
330 Часть IV. Программирование на Visual Prolog ет себя с новыми значениями для тир. Здесь проявляется еще одна особенность Visual Prolog. В Прологе верное для арифметики выражение р = р + 1 совсем не является определением присвоения (как это должно быть на большинстве других языков программирования). Внимание! Вы не можете изменить значение переменной в Visual Prolog. В Прологе это так же абсурдно, как и в алгебре. Вместо этого вы должны создать новую переменную и придать ей нужное значение. Например: NewP = Р + 1 Поэтому первое предложение выглядит следующим образом: factorial_aux(N, FactN, I, Р) :- I <= N, !, NewP = P*I, New! = 1+1, factorial_aux(N, FactN, Newl, NewP). Как и в случае cutcount2, в этом предложении отсечение будет обеспечивать опти- мизацию хвостовой рекурсии, хотя оно и не является последним предложением в предикате. В конечном счете I будет превышать N; текущие значения р и FactN унифицируются и рекурсия прекратится. Это реализуется во втором предложении, которое выпол- нится, когда проверка i <= N в первом предложении будет неуспешна. factorial_aux(N, FactN, I, P) I > N, FactN = P. Здесь нет необходимости делать FactN = р отдельным шагом; унификация может происходить в списке аргументов. Подстановка одинакового названия переменных требует, чтобы аргументы в этих позициях были равны. Более того, проверка i>n избыточна, т. к. обратное было проверено в первом предложении. Это дает завер- шающее предложение: f actorial__aux (_, FactN, _, FactN) . Упражнения 1. Приведенная в листинге 13.8 программа ch06e08.pro — улучшенная версия вы- числения факториала.
Гпава 13. Повтор и рекурсия 331 predicates factorial(unsigned,long) factorial(unsigned,long,unsigned,long) clauses factorial(N,FactN)i- factorial(N,FactN,1,1). factorial(N,FactN,N,FactN):- i * ♦ factorial(N,FactN,I,P):- Newl = 1+1, NewP = P*NewI, factorial(N, FactN, Newl, NewP). Загрузите и выполните эту программу. Внимательно посмотрите на код второго предложения factorial/4. Оно использует преимущество того факта, что во вре- мя первого его вызова переменная-счетчик I всегда равна 1. Это позволяет вы- полнять шаг умножения вместе с увеличенной переменной-счетчиком Newl, а не с I, экономя тем самым одну рекурсию/итерацию. Это отражено в первом пред- ложении. 2. Напишите программу с хвостовой рекурсией, которая будет работать как про- грамма сЬ06е08.рго, но без поиска с возвратом. 3. Напишите программу с хвостовой рекурсией, которая печатает таблицу степеней числа 2, как показано ниже: N 2ЛИ 2 2 3 4 4 8 16 10 1024 Остановите программу при N = 10. 4. Напишите программу с хвостовой рекурсией, которая допускает ввод числа и способна завершаться двумя способами. Она должна начинаться умножением числа на себя до тех пор, пока не достигнет числа 81 или числа, большего чем 100. Если достигнуто число 81, то печатается "yes", если же число больше 100 — печатается "по". Рекурсивные структуры данных Рекурсивными могут быть не только предложения, но и структуры данных. Пролог является единственным широко используемым языком программирования, который позволяет просто определить типы рекурсивных данных. Тип данных является ре-
332 Часть IV. Программирование на Visual Prolog курсивным, если он допускает структуры, содержащие такие же структуры, как и они сами. Наиболее важным рекурсивным типом данных является список, хотя он и не выгля- дит непосредственно рекурсивной конструкцией. Многочисленные встроенные в Пролог средства обработки списков не будут здесь обсуждаться: мы им посвятим гл. 14. В этой главе мы определим рекурсивный тип данных, опишем и используем его для написания удивительно быстрой сортирующей программы. Структурой вводимого нами типа данных является дерево (рис. 13.1). Важно, что каждая ветвь дерева сама является деревом, поэтому структура рекурсивна. Рис. 13.1, Часть фамильного дерева Деревья как типы данных Рекурсивные типы популяризировались Никлаусом Виртом, изобретателем языка программирования Pascal. Он не применял в Pascal рекурсивные типы, но опреде- лил, какими они должны быть. Но если бы в Pascal они все-таки были, то можно было бы определить дерево наподобие следующего: tree = record name: string[80]; left, right: tree end. * Некорректно в Pascal! */ На естественный язык этот фрагмент переводится так: "Дерево состоит из имени (Name), которое есть строка (string), а также левого и правого поддеревьев, которые тоже являются деревьями". Однако в Pascal можно написать только следующим образом: treeptг = Atree; tree = record name: string[80] ; left, right: treeptr end.
Гпава 13. Повтор и рекурсия 333 Заметьте существенное различие: этот фрагмент имеет дело с представлением дерева в памяти^ а не с собственно структурой дерева. Он обращается с деревом как с объ- ектом, состоящим из узлов, каждый из которых содержит некоторые данные и ука- затели на два других узла. Visual Prolog позволяет определить действительно рекурсивные типы, в которых ука- затели создаются и обрабатываются автоматически. Например, можно определить дерево следующим образом: domains treetype == tree (string, treetype, treetype) Эта декларация говорит о том, что дерево записывается как функтор tree, аргумен- тами которого являются строка и два других дерева. Но это не совсем удовлетворительная декларация, т. к. нет способа закончить рекур- сию. В действительности дерево не может распространяться до бесконечности. Не- которые узлы не имеют связей с последующими деревьями. В Pascal это можно вы- разить, присвоив некоторым указателям специальное нулевое значение, нов Проло- ге нет доступа к указателям. Решение состоит в том, чтобы определить два типа деревьев — обычное и пустое. Это достигается тем, что дерево может иметь один из двух функторов tree с тремя аргументами или empty без аргументов. domains treetype = tree(string, treetype, treetype) ; empty Заметьте, что названия tree (функтор, у которого три аргумента) и empty (функтор без аргументов) создаются программистом, и ни одному из них нет предопределен- ного в Прологе значения. С тем же успехом можно использовать ххх и ууу. Вот как дерево, представленное на рис. 13.1, будет описано в Пролог-программе: tree ("Cathy*', tree("Michael", tree("Charles", empty, empty), tree("Hazel", empty, empty)), tree("Melody", tree("Jim", empty, empty), tree("Eleanor", empty, empty))) Для удобства чтения программа имеет подразделы. Но в Прологе не требуется такого подразделения, и деревья, при нормальной записи, не требуется подразделять. Точно такая же структура будет составлена другим способом: tree("Cathy" tree("Michael" , tree("Charles",empty,empty),tree("Hazel",empty,empty)), tree("Melody”,tree("Jim",empty,empty),tree("Eleanor",empty,empty))) Заметьте, что это не предложение Пролога, а лишь сложная структура данных. Обход дерева Прежде чем продолжить рассмотрение того, как создать дерево, подумаем, что с ним делать, если оно уже есть. Одной из наиболее часто осуществляемых операций
334 Часть IV. Программирование на Visual Prolog с деревом является исследование всех узлов и обработка их некоторым образом, ли- бо поиск некоторого значения, либо сбор всех значений. Эти процедуры известны как обход дерева. Основной алгоритм для этого следующий: 1. Если дерево пусто, то ничего не делать. 2. Иначе, обработать текущее значение, затем перейти на левое поддерево, затем перейти на правое поддерево. Как и само дерево, алгоритм является рекурсивным: он обрабатывает левое и правое поддеревья так же, как и исходное дерево. В Прологе он выражается двумя предло- жениями: одно для пустого, а другое для непустого дерева. traverse(empty). traverse(tree(X, Y, Z) ) do_s ome t h i ng_w i1 h__X, traverse(Y), traverse(Z). % ничего не делать Рис. 13.2. Обход дерева "сначала — вглубь" Этот алгоритм известен как поиск '‘сначала — вглубь”, т. к. он спускается по каждой ветви вниз, насколько возможно, прежде чем вернуться вверх для обхода другой вет- ви (рис. 33.2). Чтобы посмотреть алгоритм в действии, изучите программу ch06e09.pro (листинг 33.9), которая обходит дерево и печатает все элементы, которые ей попадаются. Дерево, показанное на рис. 13.3 и 13.2, программа ch06e09.pro рас- печатает следующим образом: Cathy Michael Charles Hazel Melody Jim Eleanor Конечно, вы можете легко приспособить программу для выполнения каких-то дру- гих операций над элементами, а не для их печати.
Глава 13. Повтор и рекурсия 335 7' ' <-t' 'л. ''••.:/ 'f ' . :>'-1 >* v> VI >>>*>>> « «• <№»>»>»>>»•>»«»>»« »>*»»>>>>>>>»»>>>>> Ikllll.l iv4vkk4kk*ak ikkbkV 14 №** i ** 4 • » p* ** 4V-'«»' ***-****«»« * <!* к /* Обход дерева ’’сначала—вглубь” и печать каждого элемента, который попадается на пути ♦/ domains treetype ~ tree(string, treetype, treetype); empty() predicates traverse(treetype) clauses traverse(empty). traverse(tree(Name,Left,Right)):- write(Name,'\n*), traverse(Left), traverse(Right). goal traverse(tree("Cathy”, tree("Michael", tree("Charles", empty, empty), tree("Hazel”, empty, empty)), tree("Melody", tree("Jim", empty, empty), tree("Eleanor", empty, empty)))). Поиск ‘'сначала — вглубь" очень похож на способ, которым Пролог осуществляет поиск в базе знаний. Он организует предложения в дерево и проходит каждое под- дерево, пока предложения не закончатся. Вы могли бы описать дерево посредством набора правил Пролога, таких как: father_of("Cathy", "Michael"). mothereof("Cathy", "Melody"). father_of("Michael", "Charles"). mother_of("Michael", "Hazel"). Это было бы предпочтительнее, если бы дерево предназначалось только для выра- жения родственных связей между людьми. Но такой вид описания делает невозмож- ным обработку всего дерева как единой, сложной структуры данных. Как вы увиди- те, сложные структуры данных бывают весьма полезными, т. к. они упрощают слож- ные вычислительные задачи. Создание дерева Один из способов создания дерева — это вложенная структура из функторов и аргу- ментов, как в предыдущем примере ch06e09.pro. Однако в общем случае Пролог соз- дает дерево путем вычисления. На каждом шаге пустое поддерево заменяется непус- тым в процессе унификации (сопоставления по аргументам).
336 Часть IV. Программирование на Visual Prolog Создание дерева из одного узла путем присвоения обычных данных тривиально: create_tree(N, tree(N, empty, empty)). Здесь говорится: “Если n — данное, то tree(N, empty, empty) — это дерево из одно- го узла, содержащее его". Построение структуры дерева так же просто. Следующей процедуре нужны три дере- ва в качестве аргументов. Она вставляет первое дерево в качестве левого поддерева во второе дерево, и результат этого присваивает третьему дереву: insert_left(X, tree(А, _, В), tree(А, X, В)). Заметьте, что в этом предложении нет тела, т. е. нет четких шагов при его выполне- нии. Все, что должен сделать компьютер, — это соединить аргументы друг с другом в правильном порядке, — и работа закончена. Предположим, что вы хотите вставить tree (’'Michael", empty, empty) в качестве левого поддерева для tree ("Cathy", empty, empty). Чтобы это сделать, надо выпол- нить целевое утверждение: insert_left(tree("Michael", empty, empty), tree("Cathy", empty, empty), T) Тогда т примет значение: tree("Cathy", tree("Michael", empty, empty), empty). Это дает способ построения дерева шаг за шагом. Этот же способ показан в про- грамме ch06el0.pro (листинг 13.10). В действительности элементы, которые надо добавить к дереву, могут приходить с внешнего входа. ? Листинг 13.10. Программа ch06e10.pro А--*--*--*--*--*-*-*--*-*-*-**-*-*-*--*--*-*-*-*-*--*-*-*--*--*--*-*-*--*-**** ★ Простые процедуры построения дерева * * create_tree(А, В) помещает А в поле данных одноузлового дерева В, * * insert_left(А, В, С) вставляет А как левое поддерево В * ★ и присваивает результат С, * * insert—right(А, В, С) вставляет А как правое поддерево В ★ ★ и присваивает результат С * domains treetype = tree(string,treetype,treetype); empty() predicates create_tree(string,treetype) insert_left(treetype,treetype, treetype) insert—right(treetype, treetype, treetype) run clauses create tree(A,tree(A,empty, empty) ) .
Глава 13. Повтор и рекурсия 337 insert_left(X,tree(А,_,В),tree(A,X,В)). insert_right(X,tree(A,B,_),tree(А, В,X)). run: - % Сначала создадим несколько одноузловых деревьев create_tгее (’’Charles’’, Ch) t createntree("Hazel",H), Greatestree("Michael",Mi), create_tree("Jim",J), create_tree("Eleanor",E), create_tree("Melody",Me), create__tree ( "Cathy", Ca), % . затем соединим их insert_left(Ch, Mi, Mi2), insert_right(H, Mi2, Mi3), insert_left(J, Me, Me2), insert_right(E, Me2, Me3) , insert_left(Mi3, Ca, Ca2), insert_right(МеЗ, Ca2, Ca3), %.и печатаем результат write(СаЗ,'\n'). goal run. Заметьте, что в Прологе нет возможности изменить значение переменной после то- го, как присвоение произошло. Поэтому в программе ch06el0.pro используется так много имен переменных; каждый раз, когда нужно получить новое значение, требу- ется новая переменная. Но обычно большое число имен переменных не нужно, т. к. в общем случае повторяющиеся процедуры получают новые переменные, вызывая себя рекурсивно, и каждый вызов имеет определенный набор переменных. Обратите внимание, что утилита Test Goal имеет ограничение на количество переменных (<12), используемых в целевом утверждении (раздел goal), поэтому следует исполь- зовать предикат "оболочку" run. Бинарные поисковые деревья Итак, мы использовали дерево для представления отношений между элементами. Это, конечно, не самый лучший вид использования деревьев, т. к. предложения Пролога могут выполнить такую же работу. Но деревья можно использовать и иначе. В деревьях имеется возможность хранить данные так, что их можно быстро оты- скать. Дерево, построенное для такой цели, называется поисковым деревом. С точки зрения пользователя, сама структура дерева не несет информации, дерево — это бо- лее быстрая альтернатива списку или массиву. Вспомним, что при обходе обычного дерева вы рассматриваете текущий узел, а затем оба его поддерева. Чтобы найти определенное значение, вы должны рассмотреть каждый узел всего дерева.
338 Часть IV. Программирование на Visual Prolog {Замечание J Время, затрачиваемое на поиск в обычном дереве из N элементов, в среднем про- порционально Л/. Бинарное поисковое дерево строится таким образом, что, глядя на любой узел, можно предсказать, в каком из его узлов находится заданное значение. Это делается зада- нием отношения порядка между значениями, таким как алфавитный или пронуме- рованный порядок. Значения в левом поддереве предшествуют значению в текущем узле, а в правом — следуют после значения в текущем узле (рис. 13.3). Заметьте, что те же имена, установленные в ином порядке, дадут другое дерево, и хотя в дереве десять имен, любое из них можно найти максимум за пять шагов. Grasso Anthony Chrisholm Tubmann Lovelace Stanton Рис. 13.3. Бинарное поисковое дерево Как только вы посмотрите во время поиска на узел бинарного поискового дерева, вы можете исключить из рассмотрения половину оставшихся узлов и провести по- иск очень быстро. Если теперь размер дерева увеличить вдвое, то для поиска потре- буется только один дополнительный шаг. I Замечание J Время, требуемое для поиска значения на бинарном поисковом дереве, в среднем пропорционально Iog2 Л/ (а в действительности пропорционально log Л/ по любому основанию). Чтобы построить дерево, нужно начать с пустого дерева и добавлять к нему значе- ния одно за другим. Процедура добавления значения такая же, как при поиске: не- обходимо просто найти место, где это значение должно находиться, и вставить его туда. Этот алгоритм заключается в следующем: 1. Если текущий узел есть пустое дерево, то вставить в него значение. 2. Иначе, сравнить значение, которое необходимо вставить, со значением в теку- щем узле. Вставить значение в левое или правое поддерево, в зависимости от ре- зультата сравнения.
Гпава 13. Повтор и рекурсия 339 В Прологе этому алгоритму нужно три предложения — по одному на каждый слу- чай. Первое предложение таково: insert(Newltem, empty, tree(Newltem, empty, empty):- ’. На естественный язык эта запись переводится так: "Результатом вставки Newltem (нового значения) в empty (пустое дерево) будет дерево tree (Newltem, empty, empty)". Восклицательный знак (! — отсечение) означает, что если это предложение можно успешно применить, то другие предложения проверять не надо. Второе и третье предложения осуществляют вставку в непустые деревья: insert(Newltem, tree(Element, Left, Right), tree(Element, NewLeft, Right) Newltem < Element, i r insert(Newltem, Left, NewLeft). insert(Newltem, tree(Element, Left, Right), tree(Element, Left, NewRight) insert(Newltem, Right, NewRight). Если Newitem<Element, то вы вставляете его в левое поддерево, а если иначе, то вставляете его в правое поддерево. Заметьте, как много работы мы выполняем про- веркой соответствия аргументов в голове правила. Сортировка на основе дерева После того, как дерево построено, можно легко переставить все его элементы в ал- фавитном порядке. Алгоритм для этого — вновь вариант поиска "сначала — вглубь": 1. Если дерево пустое, то ничего не делать. 2. Иначе, переставить все элементы левого поддерева, потом текущий элемент, за- тем все элементы правого поддерева. Или на Прологе: retrieve_all(empty). % Ничего не делать retrieve_all(tree(Item, Left, Right)) retrieve_all(Left) , do_something_to(Item), retrieve_all(Right). Сортировку следующих друг за другом значений можно выполнить, вставляя их в дерево, и затем переставляя их по порядку. Для N значений это займет время, про- порциональное N log N, т. к. вставка и перестановка занимают время, пропорцио- нальное log V, причем то и другое должно быть выполнено N раз. Это самый быст- рый сортирующий алгоритм, известный на сегодня. Программа ch06ell.pro (листинг 13.11) применяет описанный способ для расстанов- ки по алфавиту вводимых символьных значений. В этом примере используются не- которые стандартные предикаты Visual Prolog, которые мы не встречали в предыду- щих главах. Эти предикаты будут подробно обсуждаться в последующих главах.
340 Часть IV. Программирование на Visual Prolog domains chartree = tree(char, chartree, chartree); end predicates nondeterm do(chartree) action(char, chartree, chartree) create—tree(chartree, chartree) insert(char, chartree, chartree) write_t ree(chart ree) nondeterm repeat clauses do(Tree):- repeat,nl, write(n*****************************************************w), nl, write("Enter 1 to update tree\n"), write("Enter 2 to show tree\n"), write("Enter 7 to exit\n"), write("*****************************************************"),nl write("Enter number ~ "), readchar(X),nl, action(X, Tree, NewTree), do (NewTree) . action(’1’,Tree,NewTree):- write("Enter characters or # to end: "), create_Tree(Tree, NewTree). action('2',Tree,Tree):- write_Tree(Tree) , write("\nPress a key to continue"), readchar(_),nl. action(’7’, _, end):- exit. create_Tree(Tree, NewTree) readchar(C), C<>’#’, i f write(C, " ") , insert(C, Tree, TempTree) , create_Tree(TempTree, NewTree). create_Tree(Tree, Tree). insert(New,end,tree(New,end,end)):- i • insert (New, tree (Element, Left, Right) , tree (Element, NewLeft, Right) ) New<Element, - f insert(New,Left,NewLeft).
гава 13. Повтор и рекурсия 341 insert(New, tree(Element,Left,Right),tree(Element,Left,NewRight) ) : - insert(New,Right,NewRight). write_Tree(end). <rite__Tree (tree (Item,Left,Right)) write_Tree(Left), write (Item, ’’ ") , write_JTree (Right) . repeat. repeat:-repeat. no(end). Загрузите и запустите программу ch06ell.pro и понаблюдайте, как Visual Prolog вы- полнит сортировку символьной последовательности на основе дерева. Упражнения w w * * V V * W W * * * * * * * * * * * 1. Программа ch06el2.pro подобна ch06ell.pro, но является более сложной. Она использует тот же алгоритм для расстановки по алфавиту любого стандартного текстового файла строчка за строчкой. Эта программа работала более чем в 5 раз быстрее, чем сортирующая DOS-программа SORT.EXE, но медленнее, чем силь- но оптимизированная сортирующая UNIX-программа. Очевидно, что сортировка на основе дерева более эффективна. В этом примере используются некоторые предикаты из файловой системы Visual Prolog, чтобы дать вам попробовать способ передачи файлов. При передаче файла со входа или на выход нужно системе дать информацию о нем, и затем исполь- зовать openread для чтения из файла или openwrite для записи в него. Если фай- лы открыты, то ввод/вывод между файлом и экраном можно начать, применив writedevice, а между открытым файлом и клавиатурой — readdevice предикаты. Детально эти предикаты будут рассмотрены ниже, в гл. 19. Загрузите и выполните программу ch06el2.pro (листинг 13.12). Когда она выведет "File to read" (файл для чтения), введите имя существующего текстового файла. Программа, строка за строкой, расставит его в алфавитном порядке. domains treetype = tree(string, treetype, treetype); empty file = infile; outfile predicates main read_input(treetype) read_input_aux(treetype, treetype) insert(string, treetype, treetype) write_output(treetype)
342 Часть /И Программирование на Visual Prolog clauses main :- write(”PDC Prolog Treesort"),nl, write("File to read: ”), readln(In)fnl, openread(infile, In)f% открыть заданный файл для чтения write("File to write: ")f readln(Out), nl, openwrite(outfile, Out), readdevice(infile), % передать все операции чтения в открытый файл read_input(Tree), writedevice(outfile), % передать все операции записи в открытый файл write—output(Tree), closefile(infile), % закрыть файл, открытый для чтения closefile(outfile). % закрыть файл, открытый для записи * read—input(Tree) ★ ★ Читает строки из текущего входного устройства до получения ★ ★ EOF, а затем организует из них бинарное поисковое дерево * ★ ★★★★★★★★★★★Д' -Д' read—input(Tree): - read—input—aux(empty,Tree). * read—input—aux(Tree, NewTree) * * Читает строки, вставляет их в NewTree и вызывает себя * * рекурсивно до получения EOF. * read—input—aux(Tree, NewTree) readln(S), t f insert(S, Tree, Treel), read—input—aux(Treel, NewTree). read—input—aux(Tree, Tree). % Если первое предложение % не удовлетворяется, то получен EOF * insert(Element, Tree, NewTree) * * вставляет элемент в дерево Tree и присваивает * * новому дереву NewTree * insert(Newltem, empty, tree(Newltem,empty,empty)):- 1. insert(Newltem,tree(Element,Left,Right), tree(Element,NewLeft, Right)):-
Глава 13. Повтор и рекурсия 343 Newltem < Element, i • t insert(Newltem, Left, NewLeft). insert(Newltem,tree(Element,Left,Right), tree(Element,Left,NewRight)) insert(Newltem, Right, NewRight). ******************************* * write_output(Tree) * * выводит элементы дерева в алфавитном порядке. * write_output(empty). % Ничего не делать write_output(tree(Item,Left,Right)):- write_output(Left), write(Item), nl, write_output(Right). goal main,nl. 2. Используйте рекурсивную структуру данных для гипертекста. Гипертекст — это структура, в которой каждая отдельная запись (статья) состоит из нескольких строк текста и указателей на несколько других записей. Каждая отдельная запись может соединяться с любой другой записью. Например, чтобы можно было полу- чить запись об Аврааме Линкольне как по указателю "Президент”, так и по ука- зателю "Гражданская война”. Для простоты, примените отдельные записи, состоящие из одной строки, и пусть они имеют указатели только на единственную другую запись. Подсказка: начните с записи: doma ins entrytype ~ empty(); entry(string, entry) Составьте объединенную структуру, в которой большинство записей имеют не- пустой второй аргумент. 3. А теперь примените описание вашего гипертекста для записи предложений Про- лога, т. е. примените предложения (а также рекурсивные структуры данных), ко- торым удовлетворяли бы записи. Резюме Повторим основные моменты, рассмотренные в этой главе. □ В Прологе есть два способа повторить выполнение одного и того же предложе- ния: через поиск с возвратом и через рекурсию. В случае неуспеха программа возвращается для поиска новой порции данных и повторяет предложение до тех пор, пока не закончатся альтернативы поиска. Рекурсия — это процесс, когда предложение вызывает само себя.
344 Часть IV. Программирование на Visual Prolog □ Поиск с возвратом — это мощное и эффективное средство с точки зрения памя- ти, но переменные обновляются после каждой итерации, поэтому их значения теряются. Рекурсия позволяет изменять значения переменных, но она неэффек- тивна с точки зрения требуемого объема памяти. □ Visual Prolog может оптимизировать хвостовые рекурсии, что снимает требования рекурсии к объему памяти. Чтобы достичь в Прологе оптимизации хвостовой ре- курсии, необходимо, чтобы рекурсивный вызов был последней подцелью в теле предложения, а также тело предложения и рекурсивный предикат были детерми- нированными.
ГЛАВА 14 Списки и рекурсия Обработка списков, т. е. объектов, которые содержат произвольное число элемен- тов — мощное средство Пролога. В этой главе объясняется, что такое списки и как их объявлять. Затем приводится несколько примеров, в которых показано, как можно использовать обработку списков в задачах. Далее определяются два извест- ных предиката Пролога — member (член) и append (объединение) при рассмотрении процедурных и рекурсивных аспектов обработки списков. После этого определяется стандартный предикат Visual Prolog — findall, который дает возможность находить и собирать все решения для одной цели. Завершается эта глава рассмотрением составных списков, т. е. комбинаций элементов различных типов, и примером грамматического разбора списков. Что такое список? В Прологе список — это объект, который содержит конечное число других объектов. Списки можно грубо сравнить с массивами в других языках, но, в отличие от масси- вов, для списков нет необходимости заранее объявлять их размер. Конечно, есть другие способы объединить несколько объектов в один. Если число объектов заранее известно, то вы можете сделать их аргументами одной составной структуры данных. Если число объектов не определено, то можно использовать ре- курсивную составную структуру данных, такую как дерево. Но работать со списками обычно легче, т. к. Visual Prolog обеспечивает для них более четкую запись. Список, содержащий числа 1, 2 и 3, записывается так: [1, 2, 3] Каждая составляющая списка называется элементом. Чтобы оформить списочную структуру данных, надо отделить элементы списка запятыми и заключить их в квад- ратные скобки. Вот несколько примеров: [dog, cat, canary] ["valerie ann", "jennifer caitlin", "benjamin thomas"]
346 Часть IV. Программирование на Visual Prolog Объявление списков Чтобы объявить домен для списка целых, надо использовать декларацию домена, такую как: domains integerlist: = integer* Символ (*) означает ’’список чего-либо”; таким образом, integer* означает "список целых”. Обратите внимание, что у слова "список” нет специального значения в Visual Prolog. С тем же успехом можно назвать список "Занзибаром". Именно обозначение * (а не название), говорит компилятору, что это список. Элементы списка могут быть любыми, включая другие списки. Однако все его эле- менты должны принадлежать одному домену. Декларация домена для элементов должна быть следующего вида: domains elementlist - elements* elements - .... Здесь elements имеют единый тип (например: integer, real или symbol) или явля- ются набором отличных друг от друга элементов, отмеченных разными функторами. В Visual Prolog нельзя смешивать стандартные типы в списке. Например, следующая декларация неправильно определяет список, составленный из элементов, являющих- ся целыми и действительными числами или идентификаторами: elementlist = elements* elements = integer; real; symbol /* Неверно */ Чтобы объявить список, составленный из целых, действительных и идентификато- ров, надо определить один тип, включающий все три типа с функторами, которые покажут, к какому типу относится тот или иной элемент. Например: elementlist = elements* elements = i(integer); r(real); s(symbol) % функторы здесь i/Гиз (см. разд. "Составные списки" данной главы). Головы и хвосты Список является рекурсивным составным объектом. Он состоит из двух частей — головы, которая является первым элементом, и хвоста, который является списком, включающим все последующие элементы. Хвост списка — всегда список, > голова списка — всегда элемент. Например: голова [а, Ь, с] есть а хвост [а, Ь, с] есть [Ь, с] Что происходит, когда вы доходите до одноэлементного списка? Ответ таков: голова[с] есть с хвост [с] есть []
Гпава 14. Списки и рекурсия 347 Если выбирать первый элемент списка достаточное количество раз, вы обязательно дойдете до пустого списка [ ]. Пустой список нельзя разделить на голову и хвост. В концептуальном плане это значит, что список имеет структуру дерева, как и дру- гие составные объекты. Структура дерева [a, b, с, d] представлена на рис. 14.1. список список список список Рис. 14.1. Структура дерева Одноэлементный список, как, например [а], не то же самое, что элемент, который в него входит, потому что [а] на самом деле — это составная структура данных, как показано на рис. 14.2. Рис. 14.2. Составная структура данных Работа со списками В Прологе есть способ явно отделить голову от хвоста. Вместо разделения элементов запятыми, это можно сделать вертикальной чертой " I ”. Например: [а, ь, с] эквивалентно [al [Ь, с]] и, продолжая процесс, [а | [Ь, с] ] эквивалентно [a I [b I [с] ] ], что эквивалентно [а | [Ь | [с | [ ] ] ] ] Можно использовать оба вида разделителей в одном и том же списке при условии, что вертикальная черта есть последний разделитель. При желании можно набрать [a, b, cf d] как [az ы [с, d]]. В табл. 14.1 вы найдете другие примеры. Таблица 14.1. Головы и хвосты списков Список Г олова Хвост [’а’, ’Ь', ’с'] ' а' % пустой список
348 Часть IV Программирование на Visual Prolog Таблица 14.1 (окончание) Список Голова Хвост [ ] Не определена Не определен [[1Л 2, 3], [2, 3, 4], []] [1, 2, 3] [[2, 3, 4], []] В табл. 14.2 приведены несколько примеров на присвоение всписках. Таблица 14.2. Присвоение в списках Список 1 Список 2 Присвоение переменным [X, Y, Z] [эгберт, ест, мороженое] Х=эгберг, У=ест, 2=мороженое [7] [X | Y] Х=7, Y=[] [1/ 2, 3, 4] [X, Y | Z] X=l, Y=2, Z=[3,4] [1, 2] [3 | X] fail % неудача Использование списков Список является рекурсивной составной структурой данных, поэтому нужны алго- ритмы для его обработки. Главный способ обработки списка — это просмотр и об- работка каждого его элемента, пока не будет достигнут конец. Алгоритму этого типа обычно нужны два предложения. Первое из них говорит, что делать с обычным списком (списком, который можно разделить на голову и хвост), второе — что делать с пустым списком. Печать списков Если нужно напечатать элементы списка, это делается так, как показано в листин- ге 14.1. чМИ*«М**м«*М** а«ж««а « I ••••••• I « 11 • • Ч**М****а *L» < *Яа RI| М а а а.» а^».* На** 1 * «.M.M.a •« **•••?* *а.» * •••*!»**• а.а * а I а а «, а аала а , • ............................................................................ laVaV«T-a а «аа га a a a a ar л* ai«444tt«ar«»aB«B a a a a a a t t^ita ,•«•« r>«a «a a аг»-в'««аа vdwfV^aaaakv n«i n av«*aar»*ftp«ar4r«44i4i»rid«dB а^аФй^О «« it»* a a a v-aiaiti « a ar a frtar«p»«a а а аГа««***аъ a a a a at ***>' »'a «а al domains list = integer* % Или любой тип, какой вы хотите predicates write_a_list(list) clauses write_a_list([]). % Если список пустой — ничего не делать write_a_list([Н|Т]):- % Присвоить Н-голова,Т-хвост, затем... write(Н),nl, write_a_list(Т).
Гпава 14. Списки и рекурсия 349 goal write_a_list ([1, 2f 3]}. Вот два целевых утверждения write_a_JList, описанные на обычном языке: Печатать пустой список — значит ничего не делать. Иначе, печатать список — означает печатать его голову (которая является одним элементом), затем печатать его хвост (список). При первом просмотре целевое утверждение таково: write__a_list ( [1, 2, 3]). Оно удовлетворяет второму предложению при и = 1 и т = [2, 3]. Компьютер напе- чатает 1 и вызовет рекурсивно write_a_list: write_a_list([2, 3]). % Это write_a_list(Т) Этот рекурсивный вызов удовлетворяет второму предложению. На этот раз н - 2 и т = [3], так что компьютер печатает 2 и снова рекурсивно вызывает write_a_list с целевым утверждением write_a__list ([3])'. Итак, какому предложению подходит это целевое утверждение? Вспомним, что, хотя список [3] имеет всего один элемент, у него есть голова 3 и хвост []. Следователь- но, целевое утверждение снова подходит под второе предложение с н=3 и т=[]. Про- грамма печатает 3 и делает рекурсивный вызов: write_a_list([]). Теперь видно, что этому целевому утверждению подходит первое предложение. Вто- рое предложение не подходит, т. к. [] нельзя разделить на голову и хвост. Так что, если бы не было первого предложения, целевое утверждение было бы невыполни- мым. Но первое предложение подходит, и целевое утверждение выполняется без дальнейших действий. Упражнение Является ли write_a_list хвостовой рекурсией? Будет ли оно таковой, если два предложения записать в обратном порядке? Подсчет элементов списка Рассмотрим, как можно определить число элементов в списке. Что такое длина спи- ска? Вот простое логическое определение: Длина [] — 0. Длина любого другого списка — I плюс длина его хвоста. Можно ли применить это? В Прологе — да. Для этого нужны два предложения (лис- тинг 14.2).
350 Часть IV. Программирование на VisualProlog J*1 71' •!S****‘ •**л***м*4*Ам* V * "м* ••чл?.^ I Листинг 14.2.Программа ch07e02.pro 1W• Irii 5rt «4,««««Vl«'f4lll V4 4 4 «»й 4 №«•'*•»«« I «««««*«*«,««««*« » « « 4 « « « « * I • «« ** « 4 4 * 4V« 4 »« Г domains list = integer* % или любой другой тип predicates length of(list,integer) clauses length—of ([], 0) . length—of([_|T],L):- length—of(T,TailLength), L = TailLength + 1. Посмотрим сначала на второе предложение. Действительно, [_|т] можно сопоста- вить любому непустому списку, с присвоением т хвоста списка. Значение головы не важно, главное, что оно есть, и компьютер может посчитать его за один элемент. Таким образом, целевое утверждение length_of([1, 2, 3], L). подходит второму предложению при т=[2, 3]. Следующим шагом будет подсчет длины т. Когда это будет сделано (не важно как), TailLength будет иметь значе- ние 2, и компьютер добавит к нему 1 и затем присвоит l значение 3. Итак, как компьютер выполнит промежуточный шаг? Это шаг, в котором определя- ется длина [2, 3] при выполнении целевого утверждения length—of([2, 3], TailLength). Другими словами, iength_of вызывает сама себя рекурсивно. Это целевое утвержде- ние подходит второму предложению с присвоением: [3] из целевого утверждения присваивается т в предложении; □ TailLength из целевого утверждения присваивается L в предложении. Напомним, что TailLength в целевом утверждении не совпадает с TailLength в предложении, потому что каждый рекурсивный вызов в правиле имеет свой собст- венный набор переменных (подробно см. гл. 13). Итак, целевое утверждение состоит в том, чтобы найти длину [3], т. е. 1, а затем добавить 1 к длине [2, 3], т. е. к 2, и т. д. Таким образом, length of вызывает сама себя рекурсивно, чтобы получить длину списка [3]. Хвост [3] - [], так что т будет присвоен [], а целевое утверждение будет состоять в том, чтобы найти длину [] и, добавив к ней 1, получить длину [3]. На сей раз все просто. Целевое утверждение length—of([], TailLength) удовлетворяет первому предложению, которое присвоит 0 переменной TailLength. Visual Prolog добавит к нему 1 и получит длину [3], затем вернется к вызывающему предложению. Оно в свою очередь снова добавит 1, получит длину [2, 3] и вернет- ся в вызывающее его предложение. Это начальное предложение снова добавит 1 и получит длину [1, 2, 3].
Глава 14. Списки и рекурсия 351 Не запутались? Надеемся, что нет. Посмотрите иллюстрацию всех вызовов. В ней мы пользовались названиями, чтобы показать, что переменные с одним идентифи- катором, но из разных предложений или из разных вызовов одного предложения отличаются друг от друга. length_of([1, 2, 3], L1). length_of([2, 3], L2). length_of([3], L3). length_of([], 0). L3 = 0+1=1. L2 = L3+1 = 2. Ll = L2+1 = 3. Упражнения i. Запишите предикат с названием sum of, который работает так же, как length_pf, за исключением того, что он работает со списком чисел и суммирует их. Напри- мер, целевое утверждение sum of([1, 2, 3, 4], S) . должно присваивать s значение 10. 2. Что будет, если вы попробуете выполнить целевое утверждение: sum_of(List, 10). Это целевое утверждение требует: ’’Создай мне список, к элементам которого на- до добавить 10”. Можно ли это сделать в Visual Prolog? Если нет, почему? (Под- сказка: в Visual Prolog нельзя выполнять арифметические операции с несвязан- ными переменными.) Хвостовая рекурсия Возможно, вы заметили, что length of не является и не может быть хвостовой ре- курсией потому, что рекурсивный вызов не является последним шагом в своем предложении. Можно ли написать предикат для определения длины списка, кото- рый будет хвостовой рекурсией? Да, но это потребует некоторых усилий. Проблема использования length_of заключается в том, что нельзя подсчитать длину списка, пока не подсчитана длина хвоста. Но есть обходной путь. Для определения длины списка вам потребуется предикат с тремя аргументами: □ первый — это сам список, который компьютер уменьшает при каждом вызове, пока список не опустеет так же, как и раньше; □ второй — свободный параметр, который будет хранить промежуточный результат (длину); □ третий — счетчик, который начинается с нуля и увеличивается на 1 при каждом вызове. Когда список станет пустым, унифицируем счетчик со свободным результатом.
352 Часть IV. Программирование на Visual Prolog Рассмотрим пример (листинг 14.3). .** ? 11 аа 111 1 '***•*»*. 1 * * *,ь » * * * «ь V.1.1 *’ iMHiiiM • 1;ii1i((,ibi(i(i О «bibiiiblbb>««a«ii«^iiiiibi ь. * »1 «.» «а^ j Листинг 14.3. Программа ch07e03.pro Ч ;***b'.b*. i «***** « « bb ь , ь, а, а а а'а a a b а ь« а > Ю1 *»<>>>>«>« ь«« «« 1>>>|Ф>Ф>ф1«1.1111.«иаа|а111>>11|Ьк111, ,hhaa*a а а а«« а« а« а а а'а а а а.к в, а а «а га «а« а« . а а а. а'а а а а а ак« а а а а а а а-а«*а . а а а а аь а «4а «а в,аа а. аа а а Ыа«авав а«аВ в а4«фв ««ф«фк1 domains list = integer* % или любой другой тип predicates length_of(list,integer, integer) clauses length_of([], Result, Result). length_of([_ IT ] ,Result,Counter):- NewCounter = Counter + 1, length_of(T, Result, NewCounter). goal length_of([1, 2, 3], L, 0) , % начать co счетчика = 0 write("L=",L), nl. Данная версия предиката length__of более сложная и менее логичная, чем предыду- щая. Она продемонстрирована лишь для доказательства того, что можно найти хво- стовые рекурсивные алгоритмы для целевых утверждений, которые, возможно, тре- буют другого типа рекурсии. Упражнения 1. Попробуйте обе версии length of для очень больших списков (например, 200 или 500 элементов). Что произойдет? Как соотносятся по скорости обе версии на длинных списках? 2. Перепишите sum__of по подобию новой версии length_of. * * * Иногда необходимо преобразовать один список в другой. Вы делаете это, работая со списком поэлементно, заменяя каждый элемент вычисленным значением. Вот при- мер: программа ch07e04.pro (листинг 14.4) добавит 1 к каждому элементу числового списка. Д’:'?;'BB*V”?V*’**r"*’B'*',.***B*,**r***^BB',.BVB-'-,-,*1*’-B*₽Br*’:V.B''t,'B'*b*BBB*******Bp**BB,***’B',*V’-,**'*BrTB**"B’*b"B*b'b*b ..... | ||||ЙНЙ1|ьОП pof ав4*«ФЬ«вФ«Ф«в«Ф«Фь«а.Ь4|a*k Ш А ь ь а, .4 .4 ва а ь ффЬ *.фаа,,,*а4 а*в4 Ни а%О ь4 a*b.Ti< W*i ‘ b«b ь it в b ы aa'i f all ФЬ *ЬФь'а iaC'4 b'a a ь a b a «в b « .* Ф.4.Ф» «ь «ь фв*«аа 4«at ФВФ bb k4 вф laVa a* a * ь ь ь b «ь >ь ьЪ b b n b «««.«ь «Ъ domains list = integer* predicates addl(list, list) clauses addl([],[]). % граничное условие addl([Head[Tail],[Headl[Taill]):- % отделить голову списка
Глава 14. Списки и рекурсия 353 Headl= Head+1, % добавить 1 к 1-му элементу addl(Tail, Taill). % вызвать элемент из остатка списка goal addl([1, 2,3,4], NewList). Переведя это на естественный язык, получим: Чтобы добавить 1 ко всем элементам пустого списка, надо создать другой пустой список. Чтобы добавить 1 ко всем элементам любого непустого списка, надо добавить 1 к голове и сделать полученный элемент головой результирующего списка, затем добавить 1 к каждому элементу хвоста списка и сделать это хвостом результата. Введите программу и запустите Test Goal с целевым утверждением addl([1,2,3,4], NewList). Test Goal выдаст результат: NewList=[2,3,4,5] 1 Solution Является ли addl хвостовой рекурсией? Если вы знакомы с языками Лисп или Пас- каль, то можете подумать, что нет, т. к. считаете, что предикат выполняет следую- щие операции: 1. Разделяет список на Head и Tail. 2. Добавляет 1 к Head и результат присваивает Headl. 3. Рекурсивно добавляет 1 ко всем элементам Tail, присваивает результат Taill. 4. Объединяет Headl и Taill и присваивает результат новому списку. Эта процедура не является хвостовой рекурсией, потому что рекурсивный вызов — это не последний шаг. Но, что важно —- Visual Prolog делает это не так; в нем addl является хвостовой рекурсией, потому что шаги на самом деле следующие: 1. Связать голову и хвост исходного списка с Head и Tail. 2. Связать голову и хвост результата с Headl и Taill (Headl и Taill пока не опреде- лены). 3. Добавить 1 к Head и присвоить результат Headl. 4. Рекурсивно добавить 1 ко всем элементам Tail, присваивая результат Taill. Когда все будет завершено, Headl и Taill уже являются головой и хвостом результа- та, и нет отдельной операции для их объединения. Таким образом, рекурсивный вызов является последним шагом. Конечно, не всегда нужно заменять каждый элемент. Далее следует пример про- граммы ch07e05.pro (листинг 14.5), которая просматривает список из чисел и делает из него копию, отбрасывая отрицательные числа.
354 Часть IV. Программирование на Visual Prolog Программа ch07605.pro Л ««I I*««•«»********"*I«««**«*«»»Л•**«•«««»«•«««№*••••%%**•••••«*««•««••••«< к 1 1 •-Мм л*> 1 Я * |||(йЙИн|Ж5 ••**!« rtf*«««««*ь О *« domains list = integer* predicates discarcL_negatives (list, list) clauses discard_negatives ([ ], [ ]) . discard_negatives([HIT],ProcessedTail):- H < 0, % если H отрицательно, то пропустить I/ discard_negatives(T, ProcessedTail). discard_negatives([H|T],[H |ProcessedTail]):- discard_negatives(T, ProcessedTail). К примеру, целевое утверждение discard_negatives([2, -45, 3, 468], X) получит X = [2, 3, 468]. Далее приведем предикат, который копирует элементы списка, заставляя каждый элемент появляться дважды: doubletalk([ ] , [ ] ) . doubletalk([Н|Т] , [Н, Н|DoubledTail]) doubletalk(Т, DoubledTail). Принадлежность к списку Предположим, что у вас есть список имен John, Leonard, Eric и Frank, и вы хотите, используя Visual Prolog, проверить, имеется ли заданное имя в этом списке. Другими словами, вам нужно выяснить отношение ’'принадлежность” между двумя аргумен- тами — именем и списком имен. Это выражается предикатом member(name, namelist). % "name" принадлежит "namelist" В программе ch07e06.pro (листинг 14.6) первое предложение проверяет голову спи- ска. Если голова списка совпадает с именем, которое вы ищете, то можно заклю- чить, что Name (имя) принадлежит списку. Так как хвост списка не представляет ин- тереса, он обозначается анонимной переменной. По первому предложению целевое утверждение member(john, [john, leonard, eric, frank]) будет выполнено. [Листинг 14.6. Программа ch07e06.pro ’У:1!;:':;. : !••• ’• ' ‘ ^,-x a.•, 4 ?«««« ««««<*« *» *«««'««»«»•••«• t'l*>*«** tt tt#t I ft ••?«»«« domains namelist = name* name = symbol
Гпава 14. Списки и рекурсия 355 predicates member(name, namelist) clauses member (Name, [Name|_]). member(Name, [_|Tail]):~ member(Name,Tail). Если голова списка не совпадает с Name, то нужно проверить, можно ли Name найти в хвосте списка. На обычном языке: Name принадлежит списку, если Name есть первый элемент списка,- или Name принадлежит списку, если Name принадлежит хвосту. Второе предложение member, выражающее отношение принадлежности, в Visual Prolog выглядит так: member(Name, [_(Tail]) member(Name, Tail). Упражнения 1. ‘Загрузите программу ch07e06.pro и попробуйте с помощью Test Goal выполнить следующее целевое утверждение: member(susan, [ian, susan, john]). 2. Добавьте утверждения в разделы domain и predicate таким образом, чтобы вы могли использовать member для установки принадлежности числа числовому спи- ску. Попробуйте несколько целевых утверждений, включая member(X, [1, 2, 3, 4]), для проверки вашей новой программы. 3. Имеет ли значение порядок написания двух предложений для предиката member? Посмотрите, как ведет себя программа, если поменять местами два предложения. Обнаружится ли различие при выполнении целевого утверждения member(X, [1, 2, 3, 4, 5]) для обоих случаев? Объединение списков В том виде, как он дан в программе ch07e06.pro, предикат member работает двумя способами. Рассмотрим его предложения еще раз: member (Name, [Name|_] ) . member(Name, [_|Tail]) member(Name, Tail). На эти предложения можно смотреть с двух различных точек зрения: декларативной и процедурной.
356 Часть IV. Программирование на Visual Prolog □ С декларативной точки зрения предпожения сообщают: Name принадлежит списку, если голова совпадает с Name; если нет, то Name принадлежит списку, если оно принадлежит его хвосту. □ С процедурной точки зрения эти два предложения можно трактовать так: Чтобы найти элемент списка, надо найти его голову; иначе надо найти элемент в хвосте. Эти две точки зрения соотносятся с целевым утверждением: member(2, [1, 2, 3, 4]). и member(X, [1, 2, 3, 4]). В результате, первое целевое утверждение "просит" Visual Prolog выяснить, верно ли утверждение, второе, — найти всех членов списка [1,2,3, 4]. Предикат member одина- ков в обоих случаях, но его поведение может быть рассмотрено с разных точек зрения. Рекурсия с процедурной точки зрения Особенность Пролога состоит в том, что часто, когда вы задаете предложения для предиката с одной точки зрения, они будут выполнены с другой. Чтобы увидеть эту двойственность, создадим в следующем примере предикат для присоединения одно- го списка к другому. Определим предикат append с тремя аргументами: append(Listl, List2, List3) Он объединяет Listl и List2 и создает List3. Еще раз воспользуемся рекурсией (на этот раз с процедурной точки зрения). Если Listl пустой, то результатом объединения Listl и List 2 останется Li st 2. На Прологе это будет выглядеть так: append([], List2, List2). Если Listl не пустой, то можно объединить Listl и List2 для формирования List3, сделав голову Listl головой List3. (В следующем утверждении переменная н ис- пользуется как голова для Listl и для List3.) Хвост List3 — это L3, он состоит из объединения остатка Listl (т. е. L1) и всего List2. То есть: append([H|L1], List2, [H|L3]) append(LI, List2, L3). Предикат append выполняется следующим образом: пока Listl не пустой, рекурсив- ное предложение передает по одному элементу в List3. Когда Listl станет Пустым, первое предложение унифицирует List2 с полученным List3. Упражнение М * М М * * ** W W а* * Предикат append определен в программе (листинг 14.7). Загрузите программу ch07e07.pro.
Гпава 14. Списки и рекурсия 357 5 Л истин г 14.7. П рогра м ма ch07e07.pro ' W ] Г»!'**'*»**» rJ <»»•». । .'кг. •» Г. •*»*<»»•» »•*»»*»»»»»»« »'»'»' I »Ч и »l| *1 Г»1 I »»| »»1 । i । »»|t J »l »l I I k»l I »l I I »l »l »f» »»l к| »|* »l I »l I I »l »l »Н I »*»l »»l »l I »l I I »l »l I »l > domains integerlist - integer* predicates append(integerlist,integerlist, integerlist) clauses append([], List, List). append ( [H| LIL List2, [H|L3]) append(LI,List2, L3). Запустите следующее целевое утверждение: append([1, 2, 3], [5, 6], L). А теперь попробуйте это: append([1, 2], [3], L), append(L, L, LL). Предикат может иметь несколько вариантов использования Рассматривая append с декларативной точки зрения, вы определили отношение меж- ду тремя списками. Однако это отношение сохранится, даже если Listl и List3 из- вестны, a List2 — нет. Оно также справедливо, если известен только Lists. Напри- мер, чтобы определить, какие из двух списков можно объединить для получения известного списка, надо составить целевое утверждение такого вида: append(LI, L2, [1, 2, 4] ). По этому целевому утверждению Visual Prolog найдет следующие решения: Ll=[], L2-[l,2,4] Ll=[l], L2=[2,4] Ll-[1,2], L2=[4] Ll=[l,2,4], L2=[] 4 Solutions Можно также применить append, чтобы определить, какой список можно подсоеди- нить к [3,4] для получения списка [1,2,3,4]. Запустите целевое утверждение appendfLl, [3,4], [1,2,3,4]). Visual Prolog найдет решение: Ll=[l, 2] . Предикат append определил отношение между входным набором и выходным набором таким образом, что отношение применимо в обоих направлениях. Задавая такое от- ношение, вы спрашиваете:
358 Часть IV, Программирование на Visual Prolog Который выход соответствует данному входу? ИЛИ Который вход соответствует данному выходу? Состояние аргументов при вызове предиката называется потоком параметров. Аргу- мент, который присваивается или назначается в момент вызова, называется входным аргументом и обозначается буквой 1; а свободный аргумент — это выходной аргу- мент, обозначается буквой о. У предиката append есть возможность работать с разными потоками параметров, в зависимости от того, какие исходные данные вы ему дадите. Однако не для всех предикатов имеется возможность быть вызванными с различными потоками пара- метров. Если предложение Пролога может быть использовано с различными пото- ками параметров, оно называется обратимым предложением. Когда вы записываете собственные предложения в Visual Prolog, помните, что обратимые предложения имеют дополнительные преимущества, и их создание добавляет ’’мощности*1 преди- катам . J Упражнение Измените предложения, определяющие member в программе ch07e06.pro, и напишите предложения для предиката even_member, который будет успешным, если вы дадите целевое утверждение even_member(2, [1, 2, 3, 4, 5, 6]). Предикат должен также вернуть следующий результат: Х-2 Х-4 Х=б 3 Solutions, если вы дадите ему: evenjnember (X, [1, 2, 3, 4, 5, 6] ) . Поиск всех решений для цели сразу В гл. 13 сравнивались поиск с возвратом и рекурсия, как способы выполнения по- вторяющихся процедур. Преимущество рекурсии состоит в том, что, в отличие от поиска с возвратом, она передает информацию (через параметры) от одного рекур- сивного вызова к следующему. Поэтому рекурсивная процедура может хранить память о промежуточных результатах или счетчиках по мере того, как она выпол- няется. Но есть одна вещь, которую поиск с возвратом может делать, а рекурсия — нет. Это поиск всех альтернативных решений в целевом утверждении. Может оказаться, что вам нужны все решения для целевого утверждения, и они необходимы все сразу, как часть единой сложной составной структуры данных. Встроенный предикат findal1 использует целевые утверждения в качестве одного из своих аргументов и
Глава 14. Списки и рекурсия 359 собирает все решения для этого целевого утверждения в единый список. У предика- та findall три аргумента: □ VarName (имя переменной) — определяет параметр, который необходимо собрать в список; □ myPredicate (мой предикат) — определяет предикат, из которого надо собрать значения; □ ListParam (список параметров) — содержит список значений, собранных мето- дом поиска с возвратом. Заметьте, что должен быть определенный пользователем тип, которому принадлежат значения ListParam. Программа ch07e08.pro (листинг 14.8) использует findall для печати среднего воз- раста группы людей. I Листинг 14.8. Программа ch07e08.pro л /5. у? , • • :•••••.: . • •• •••;••.: < Je. -У,- ГЬЯяЛ'+к, •***»*•»*• |>>||><>|(| I III f , Ч « « •, , « « •*« •, « г, »»*»••! , 1 »** » . >|ll»| |||1|||»|Г|Г»|||.||П ••»] *4 « Ч **, « » *»**'***» *♦•*»»*»*! | *»* « ••ii’i domains name,address = string age = integer list = age* predicates person(name, address, age) sumlist(list, age, integer) clauses sumlist([],0,0) . sumlist([H|T],Sum, N):- sumlist(T,SI,Nl), Sum=H+Sl, N=1+N1. person("Sherlock Holmes", "22B Baker Street", 42). person("Pete Spiers", "Apt. 22, 21st Street", 36). person("Mary Darrow", "Suite 2, Omega Home", 51). goal findall (Age,person(__, _, Age),L), sumlist (L, Sum, N) , Ave = Sum/N, write("Average=", Ave),nl. Предложение findall в этой программе создает список L, в котором собраны все возрасты, полученные из предиката person. Если бы вы захотели собрать список из всех людей, которым 42 года, то вам следовало бы выполнить следующее подцелевое утверждение: findall(Who, person(Who, _, 42), List) Но эта подцель требует от программы, чтобы та содержала объявление домена для результирующего списка: slist = string*
360 Часть IV. Программирование на Visual Prolog Составные списки Список целых может быть объявлен просто: integerlist = integer* Это же справедливо и для списка действительных чисел, списка идентификаторов или списка строк. Часто бывает важно иметь внутри одного списка комбинацию элементов, принадле- жащих разным типам: [2, 3, 5.12, ("food", "goo"], "new"] % Некорректно в Visual Prolog Составные списки — это списки, в которых используется более чем один тип эле- ментов. Для работы со списками из разнотипных элементов нужны специальные декларации, потому что Visual Prolog требует, чтобы все элементы списка принадле- жали одному типу. Для создания списка, который мог бы хранить различные типы элементов, в Прологе необходимо использовать функторы, потому что домен может содержать более одного типа данных в качестве аргументов для функторов. Пример объявления доменов для списка, который может содержать символы, целые, строки или списки: Domains % функторы 1, 1, сиз Hist = l(list); i (integer); c (char); s (string) list = Hist* Список [ 2, 9, ["food", "goo"], "new" ] % Некорректно в Visual Prolog должен быть представлен в Visual Prolog как: [i(2), i(9), 1([s("food"), s("goo")]), s("new")] % Корректно. В листинге 14.9 приведен пример (программа ch07e09.pro), показывающий объеди- нение списков и использование объявления доменов в типичном случае работы со списками. ****.***»><»*»•*< ».»»ш *.*.*>«' ИЛ».»»*»*».**."*"»*»'»*»»»»*;»»»*:», *.**•?, о М*Л<а*Л*»»..**?****»^АМ>аМ**£***Ч кЛистинг14.9. Программа ch07e09.pro -V domains Hist = l(list); i(integer); c(char); s(string) list - Hist* predicates append(list,list,list) clauses append ( [] ,L,L) . append([X|L1],L2,[X|L3]) append(LI, L2, L3).
Гпава 14. Списки и рекурсия 361 goal append([s(likes), 1 ([s(bill), s(mary)])],[s(bill), s(sue)],Ans), write("First list: ", Ans,"\n\n"), append([1([s("This"),s("is”),s("a"),s("list") ]), s(bee)], [c('c')],Ans2), write("Second list: ", Ans2, '\n', '\n'). Упражнения 1. Запишите предикат oddlist, который использует два аргумента. Первый аргу- мент — список целых, а второй — это список нечетных чисел, найденных в пер- вом списке. 2. Запишите предикат real average, который вычисляет среднее значение всех эле- ментов списка действительных чисел. 3. Запишите предикат flatten, в котором первый аргумент — это составной спи- сок, а второй аргумент — список, из которого удалены все подсписки. Он вырав- нивает список из нескольких списков в один. Например, вызов flatten([s(ed), i (3) , 1([г(3.9), 1([s(sally)])])], г(4.21), X) № даст результат X = [s(ed), i(3), r(3.9), s(sally), r(4.21)] 1 Solution который получается из первоначального списка после выравнивания. Грамматический разбор списков Программа ch07e!0.pro (листинг 14.10) иллюстрирует грамматический разбор спи- сков. Процесс грамматического разбора работает путем упрощения проблемы; в примере мы преобразуем каждую строку в структуру Пролога, которую можно ис: пользовать или вычислить позднее. Грамматический разбор в этом примере предназначен для примитивного компью- терного языка. Этот пример для данной стадии обучения может показаться слож- ным, но мы решили привести его именно здесь, т. к. грамматический разбор — одна из областей, где Visual Prolog особенно эффективен. Если вы пока не чувствуете себя готовыми, можно пропустить этот пример и продолжить изучение далее. Листинг 14.10. Программа ch07e10.рго domains toklist = string* predicates tokl(string,toklist) clauses tokl (Str, [H | T] ) : -
362 Часть IV. Программирование на Visual Prolog fronttoken(Str,H,Strl),!, tokl(Strl,T). tokl(_, []) . * Это вторая часть программы грамматического разбора ★ domains program - program(statementlist) statementlist = statement* % Определение, из чего состоит утверждение statement = if_Then_Else(exp,statement, statement); ifJThen(exp,statement); while(exp,statement); assign(id,exp) % Определение выражений exp = plus(exp,exp); minus(exp,exp); var(id); int(integer) id = string predicates s_jprogram(toklist, program) s_statement(toklist,toklist,statement) s_statementlist(toklist,toklist,statementlist} s_exp(toklist,toklist,exp) s_expl(toklist,toklist,exp,exp) s_exp2(toklist,toklist,exp) clauses s_j?rogram(Listl, program (Statement List)) s__statementlist (Listl, List2, Statement List), List2=[]. s_statementlist([],[]/[]):-!. s_statementlist(Listl,List4, [Statement I Program]} s_statement(Listl,List2,Statement}, List2=[”;”|List3], s_statementlist(List3,List4,Program). s_statement(["if"|Listl],List7, if_then_else(Exp,Statement1,Statement2) } s_exp(Listl,List2,Exp) , List2=4 "then"IList3], s_statement(List3,List4,Statementl), List4=["else"I List5], !, s_statement(List5,List6,Statement2), List6=["fi"|List7].
Глава 14. Списки и рекурсия 363 s_statement ([’’if” | Listl] r List 5, if__then (Exp, Statement)) : - !, s_exp(Listl,List2,Exp), List2=["then"|List3], s_statement(List3,List4,Statement), List4=["fi"|List5]. s_statement(["do"[Listl],List4,while(Exp,Statement)) :~!, s_statement(Listl,List2, Statement), List2= [ ’’while" I List3], s_exp(List3,List4,Exp). s_statement([IDJListl],List3,assign(Id,Exp)) isname(ID), Listl=["="|List2], s__exp(List2,List3,Exp) . s_exp(LIST1,List3,Exp) s_exp2(Listl,List2,Expl), s_expl(List2,List3,Expl,Exp). s_expl(["+"[Listl],List3,Expl,Exp), s_exp2(Listl,List2,Exp2), s_expl(List2,List3,plus(Expl,Exp2),Exp). s_expl ([”—'* [Listl], List3,Expl,Exp) : -!, s_exp2(Listl,List2,Exp2), s_expl(List2,List3,minus(Expl,Exp2),Exp), s_expl(List,List,Exp,Exp). s_exp2([Inti Rest],Rest,int(I)):- str_int(Int,I),!. s_exp2([Id I Rest],Rest,var(Id)) isname(Id). goal tokl(’’b=2; if b then a=l else a=2 fi; do a=a-l while a;’’,Ans), s^program (Ans, Res) . Загрузите и запустите эту программу. Visual Prolog вернет следующую структуру про- граммы: Ans=["b", "2", "if","b”,"then","а","1", "else", ”а", "2", "fi",”do", "а", "а", ’’-”,”1”, "while”, "а’’,";" h Res-program([assign("b”,int(2)), if_then_else(var("b”),assign("a",int(1)), assign("a",int(2) ) ) , while(var(”a"),assign("a",minus(var("a"),int(1)))) 1 Solution Преобразование в этом примере разделяется на два этапа: сканирование и грамма- тический разбор. Предикат tokl при сканировании принимает строку и преобразует
364 Часть IV. Программирование на Visual Prolog ее в список лексем. Все предикаты с именем на s__ — это предикаты грамматическо- го разбора. В этом примере входной текст — паскалеподобная программа, состав- ленная из паскалеподобных утверждений. Язык этой программы понимает только некоторые утверждения: if then else, if then, do while и assign. Утвержде- ния составляются из выражений и других утверждений. Выражение — это сложение, вычитание, переменные и целые. Вот как работает эта программа; 1. Первое предложение сканера s_program берет список лексем и проверяет, можно ли их преобразовать в список утверждений. 2. Предикат s_statementlist проверяет, могут ли названия в списке лексем быть разделены на отдельные утверждения, заканчивающиеся точкой с запятой. 3. Предикат s_statement проверяет, является ли первая лексема списка допустимым утверждением. Если да, то утверждение возвращается в структуру, и оставшиеся лексемы возвращаются предикату s_statementlist. • Четыре предложения предиката s_statement соответствуют четырем типам ут- верждений, которые знает грамматический разборщик. Если первое предло- жение s_statement не может преобразовать список лексем в IF THEN ELSE- утверждение, то это предложение не удовлетворено. Происходит откат к сле- дующему предложению s_statement, которое пытается преобразовать список лексем в утверждение if then. Если и это не удается, то происходит откат к следующему предложению, которое пытается преобразовать список лексем в утверждение DO WHILE. • Если первые три предложения s_statement оказались неудовлетворенными, то последнее предложение для этого предиката проверяет, не является ли утвер- ждение присваиванием, а именно; является ли первый терм идентификато- ром, является ли второй терм ==, и образуют ли следующие термы простое арифметическое выражение. 4. Предикаты s_exp, s_expl и s_exp2 работают так же, проверяя, являются ли пер- вые термы выражениями, и если да, то возвращают остаток термов и структуру выражения в s_statement. Для более детального рассмотрения примера грамматического разбора можно обра- титься к программе "Анализатор предложений" (Sentence Analyzer) <CDROM>: \RUN\VPI\PROGRAMS\SEN_AN на прилагаемом CD-диске. Резюме В этой главе были рассмотрены следующие важные моменты; □ Списки — это объекты, которые имеют произвольное число элементов. Вы объ- являете их, добавляя * (звездочку) после имени домена (стандартного или пред- варительно декларированного в программе). □ Список — это рекурсивный составной объект, состоящий из головы и хвоста. Го- лова — это первый элемент списка, а хвост — это все остальные элементы спи- ска (без первого элемента). Хвост списка — всегда список. Голова списка — все-
Гпава 14. Списки и рекурсия 365 гда элемент. Список может иметь ноль или больше элементов; пустой список записывается как []. □ Для разделения головы и хвоста в списке используются разделители (запятая, [, ] и |); например, список . [а, Ь, с, d] может быть записан как: [а I[Ь, с, d]] или [а, Ы[с, d]] или [а, Ь, с I[d]] или [а|[b|[с, d]]] или [а | [Ы [с I [d] ] ] ] или даже [aI[b|[сI [d| []]] ] ] □ Visual Prolog требует, чтобы все элементы в списке принадлежали одному и тому же домену, поэтому необходимо пользоваться функторами для создания списка, который хранит элементы разных доменов. □ Элементы списка могут быть любыми, включая и другие списки. Декларация до- мена для элементов списка должна быть такой формы: domains elementlist ~ elements* elements - .... где elements — это один из стандартных доменов (целый, вещественный и т. д.) или это набор альтернативных функторов с разными доменами (int (integer), rl(real), smb(symbol) ит. Д.). □ Работа со списком состоит из рекурсивного отделения его головы (и, обычно, производится некоторая ее обработка), до тех пор, пока список не станет пустым. □ Классические предикаты Пролога member и append дают возможность проверить, содержится ли элемент в списке и содержится ли один список в другом (или до- бавлен ли один список к другому) соответственно. □ Поток параметров — это состояние аргументов предиката при его вызове; они могут быть входными параметрами (i) — которым что-либо присвоено или они связаны, или выходными параметрами (о) — свободными. □ Предикат findal 1 — это встроенный в Visual Prolog предикат, который использу- ет целевое утверждение, как один из своих аргументов, и собирает все решения для этого целевого утверждения в один список.
ГЛАВА 1 5 Внутренняя база фактов Visual Prolog В этой главе мы опишем, как нужно объявлять разделы внутренних баз фактов Про- лога (internal fact databases) и как можно изменить содержание вашей внутренней базы фактов. Внутренняя база фактов состоит из фактов, которые вы можете непосредственно добавлять и удалять из вашей программы на Visual Prolog во время ее исполнения. Вы можете объявлять предикаты, описывающие внутреннюю базу данных в разделе facts программы и применять эти предикаты таким же образом, как используются предикаты, описанные в разделе predicates. Для добавления новых фактов в базу данных в Visual Prolog используются предикаты assert, asserta, assertz, а предикаты retract и retractall служат для удаления существующих фактов. Вы можете изменить содержание вашей базы фактов, снача- ла удалив факт, а потом вставив новую версию этого факта (или совершенно другой факт). Предикаты consult/1 и consult/2 считывают факты из файла и добавляют их к внутренней базе данных, a save/1 и save/2 сохраняют содержимое внутренней базы фактов в файле. Visual Prolog интерпретирует факты, принадлежащие к базе данных, таким же обра- зом, как обычные предикаты. Факты предикатов внутренней базы фактов хранятся в таблице, которую можно легко изменять, тогда как обычные предикаты для дос- тижения максимальной скорости компилируются в двоичный код. Объявление внутренней базы фактов Ключевое слово facts (это синоним устаревшего слова database) определяет начало объявления раздела facts. Раздел facts состоит из последовательности объявлений предикатов, описывающих соответствующую внутреннюю базу фактов. Во время выполнения можно с помощью предикатов asserta и assertz добавлять факты (но не правила) в базу фактов. Или, вызвав стандартный предикат consult, вы можете извлечь добавляемые факты из файла на диске. Раздел facts может выглядеть так, как в следующем примере:
Глава 15. Внутренняя база фактов Visual Prolog 367 domains name, address = string age == integer gender - male; female facts pe r son(name, address, age, gender) predicates male(name, address, age) female(name, address, age) child(name, age, gender) clauses male(Name, Address, Age) person(Name, Address, Age, male). В этом примере вы можете использовать предикат person таким же образом, как используются другие предикаты (male, female, child). Единственное отличие состо- ит в том, что вы можете добавлять и удалять факты для предиката person во время работы программы. Следует отметить следующие два ограничения на предикаты, объявленные в разделе фактов: □ разрешается добавлять в базу данных только факты, но не правила; □ факты базы не могут содержать свободные переменные. Допускается наличие нескольких разделов facts, но для этого нужно явно указать имя каждого раздела facts. facts — mydatabase myFirstRelation(integer) mySecondRelation(real, string) myThirdRelation(string) /* etc. */ Описание раздела facts с именем mydatabase создает базу данных фактов с именем mydatabase. Если вы не даете имени внутренней базе фактов, то по умолчанию ей присваивается стандартное имя dbasedom. Обратите внимание, что программа может содержать локальные безымянные разделы фактов, только если она состоит из единст- венного модуля, который не объявлен как часть проекта (см. разд. "Модульное про- граммирование" гл. 17). Визуальная среда разработки (VDE) компилирует программ- ный файл как единственный модуль только при использовании утилиты Test Goal. Иначе, безымянный раздел фактов должен быть объявлен глобальным. Для этого нужно перед ключевым словом facts поставить ключевое слово global. Имена предикатов базы фактов должны быть уникальными в модуле (исходном файле); в двух различных разделах facts нельзя применять одинаковые имена пре- дикатов. Аналогично, нельзя использовать одинаковые имена предикатов в разделах facts и predicates. Однако имена предикатов, определенных в локальных facts-
368 Часть IV. Программирование на Visual Prolog разделах, являются локальными для модуля, где они объявлены, и не конфликтуют с локальными именами предиката в/факгов, объявленных в других модулях. Использование внутренних баз фактов Поскольку Visual Prolog представляет реляционную базу данных как коллекцию фак- тов, вы можете использовать его в качестве мощного языка запросов к внутренним базам фактов. Алгоритм унификации Visual Prolog автоматически выбирает факты с правильными значениями для известных аргументов и присваивает значения неиз- вестным аргументам, пока его алгоритм поиска с возвратом выдает все решения для заданного запроса. Доступ к внутренней базе фактов Предикаты, принадлежащие внутренней базе фактов, доступны точно так же, как и другие предикаты. Единственное видимое различие состоит в том, что объявления таких предикатов расположены в разделе facts вместо раздела predicates. В сле- дующем примере: domains name ~ string sex = char facts person(name,sex) clauses person (’’Helen”, ’F’) . person("Maggie", ’ F') . person("Suzanne" , ’F' ) . person("Per" , 'Mr ) . вы можете вызвать person с целью person (Name, • F1) для нахождения всех женщин, или person ("Maggie”, ’ F’) для проверки того, что женщина по имени Maggie суще- ствует в вашей базе данных. По своей природе предикаты в разделе facts всегда недетерминированные. Так как факты могут быть добавлены в любой момент во время выполнения программы, компилятор всегда должен учитывать, что существует возможность найти альтерна- тивные решения в ходе поиска с возвратом. Если в разделе facts есть предикат, для которого никогда не будет более одного факта, то вы можете декларировать это, написав перед объявлением предиката факта ключевое слово determ (или ключевое слово single, если предикат всегда должен иметь один и только один факт): facts determ daylight__saving (integer) Заметим, что при попытке добавить новый факт для детерминированного предиката базы фактов, который уже имеет факт, вы получите ошибку.
Глава 15. Внутренняя база фактов Visual Prolog 369 Обновление внутренней базы фактов Факты для предикатов базы фактов могут быть определены во время компиляции в разделе clauses, как это показано в последнем примере. Во время выполнения фак- ты могут быть добавлены и удалены, используя описанные ниже предикаты. Обра- тите внимание, что факты, определенные во время компиляции в разделе clauses, также могут быть удалены, они ничем не отличаются от фактов, добавленных во время выполнения. Стандартные предикаты Visual Prolog для работы с фактами: assert, asserta, assertz, retract, retract all, consult и save — могут иметь один или два аргумента. Необязательный второй аргумент представляет собой имя внутренней базы фактов. Обозначение /1 и /2 после каждого имени предиката указывает необходимое число аргументов для данной версии предиката. Комментарии (такие как /* (i) */ и /* (о, i) */) показывают поток(и) параметров для этого предиката. Занесение фактов во время выполнения программы Во время выполнения факты могут быть добавлены во внутреннюю базу данных фактов посредством предикатов: assert, asserta и assertz, или путем загрузки фак- тов из файла с помощью consult. Существует три предиката для добавления одного факта во время выполнения: asserta(<the fact> ) asserta (<the fact>, facts__sectionName) assertz(<the fact>) assertz(<the fact>, facts_sectionName) assert(<the fact>) assert(<the fact>, facts_sectionName) Предикат asserta вставляет новый факт в базу данных фактов перед имеющимися фактами для данного предиката, a assertz вставляет факты после имеющихся фак- тов данного предиката. Использование предиката assert дает результат, аналогич- ный использованию assertz. Поскольку имена предикатов базы фактов уникальны внутри программы (для разде- лов глобальных фактов) или модуля (для разделов локальных фактов), для предика- тов asserta и assertz всегда известно, в какую базу данных фактов нужно добавлять факт. Однако для того, чтобы обеспечить работу с требуемой базой данных фактов, в целях проверки типа можно использовать необязательный второй аргумент. Первый предикат следующего примера вставит факт о Suzanne, описанный предика- том person, после всех фактов person, хранящихся на текущий момент в памяти. Второй — факт о Michael перед всеми имеющимися фактами предиката person. Третий — факт о John после всех других фактов likes в базе данных фактов likesDatabase, а четвертый вставит факт о shannon в той же базе данных фактов перед всеми другими фактами likes. assertz(person("Suzanne", "New Haven", 35)). asserta(person("Michael", "New York", 26)).
370 Часть IV. Программирование на Visual Prolog assertz (likes (’’John", ’’money"), likesDatabase). asserta(likes("Shannon", "hard work"), likesDatabase). После вызова этих предикатов база фактов будет выглядеть так, как будто вы начали работу со следующими фактами: % Внутренняя база фактов — dbasedom person("Michael", "New York", 2 6). % ... другие факты person ... person("Suzanne", "New Haven", 35). % Внутренняя база фактов — likesDatabase likes("Shannon", "hard work"). % ... другие факты likes... likes("John", "money"). Остерегайтесь случайно написать код, утверждающий один и тот же факт дважды. Внутренние базы фактов не предусматривают никакой уникальности, поэтому один и тот же факт может появляться во внутренней базе данных фактов много раз. Од- нако версию assert с проверкой на уникальность написать очень просто: facts — people person(string,string) predicates uassert(people) clauses uassert(person(Name,Address)): - person(Name,Address) , i в ; % OR- assert(person(Name,Address)). Считывание фактов из файла Предикат consult считывает из файла fileName факты, описанные в разделе facts, и вставляет их в вашу программу в конец соответствующей базы фактов. Предикат consult имеет один или два аргумента: consult(fileName) consult(fileName, databaseName) Однако в отличие от assertz, если вы вызовите consult только с одним аргументом (без имени базы фактов), то будут считаны лишь факты, которые были описаны в разделе без имени (по умолчанию dbasedom). Если вы вызовите consult с двумя аргументами (имя файла и имя базы фактов), то будут проверены только факты из указанной базы фактов. Если файл содержит еще что-нибудь, кроме фактов указанной базы, то предикат consult, когда он дойдет до этой строки, возвратит ошибку.
Гпава 15. Внутренняя база фактов Visual Prolog 371 Обратите внимание, что предикат consult считывает по одному факту. Если файл содержит десять фактов, а в седьмом факте имеется какая-нибудь синтаксическая ошибка, consult занесет шесть первых фактов в базу данных фактов, после чего вы- даст сообщение об ошибке. Отметим, что предикат consult может считывать файлы только в том формате, ко- торый создает save. Файлы не должны содержать: □ символов верхнего регистра, за исключением тех, которые содержатся внутри строк в двойных кавычках; □ пробелов, за исключением тех, которые содержатся внутри строк в двойных ка- вычках; □ комментариев; □ пустых строк; □ идентификаторов (symbol) без двойных кавычек. При создании или изменении файла с фактами в редакторе нужно соблюдать акку- ратность. Удаление фактов во время выполнения программы Предикат retract унифицирует факты и удаляет их из внутренней базы фактов. Он имеет следующий формат: retract(<the fact>) % (i) retract(<the fact>, databaseName) % (i, i) Предикат retract удаляет первый факт из вашей базы данных, который совпадает с фактом <the fact>, связывая свободные переменные <the fact> во время выпол- нения программы. Удаление фактов из внутренней базы фактов эквивалентно про- цессу доступа к ним с побочным эффектом удаления унифицировавшихся фактов, retract является недетерминированным, если предикат базы фактов, удаляемый retract, не был объявлен детерминированным. При поиске с возвратом предикат retract удаляет все унифицировавшиеся факты, пока они имеются, после чего он более не находит нужных фактов и завершается неуспешно. Предположим, в вашей программе имеются следующие разделы facts: facts person(string, string, integer) facts — likesDatabase likes(string, string) dislikes(string, string) clauses person ("Fred", "Capitola’*, 35). person("Fred", "Omaha", 37). person("Michael", "Brooklyn", 2 6) . likes("John", "money"), likes("Jane", "money").
372 Часть IV. Программирование на Visual Prolog likes("Chris", "chocolate”). likes("John”, "broccoli”). dislikes("Fred", "broccoli”). dislikes("Michael”, "beer"). Имея такие разделы facts, Visual Prolog можно задать следующие цели: retract(person("Fred", _)), % 1 retract(likes(_, "broccoli”)), % 2 retract(likes(_, "money"), likesDatabase), % 3 retract (person("Fred”, _), likesDatabase) . % 4 Первая цель удалит первый факт person о Fred из базы фактов dbasedom. С по- мощью второй цели из базы фактов likesDatabase будет удален первый факт, совпа- дающий с likes (X, "broccoli”). В случае обеих целей, Visual Prolog знает, из какой базы производить удаление, поскольку имена предикатов базы фактов уникальны: предикат person находится только в неименованной базе данных фактов, a likes — только в базе likesDatabase. Третья и четвертая цель показывают, как вы можете использовать для проверки типа второй аргумент. Третья цель успешно реализуется, удаляя первый факт, совпадаю- щий с likes (_, "money") из likesDatabase, а четвертая цель выдаст ошибку, потому что нет (и не может быть) факта person в базе данных фактов likesDatabase. Сооб- щение об ошибке выглядит следующим образом: 506 Type error: The functor does not belong to the domain. (Ошибка типа: Функтор не относится к данному домену) Следующая цель иллюстрирует, как вы можете получить значения из предиката retract: GOAL retract(person(Name, Age)), writefName, ”, ”, Age),nl, fail. Когда вы в качестве второго аргумента retract задаете имя базы фактов, вы можете не указывать имя предиката базы фактов, из которого вы удаляете факты. В этом случае retract будет искать и удалять все факты в указанной базе данных. На- пример: goal retract(X, mydatabase), write(X), fail. Удаление нескольких фактов сразу Предикат retractall удаляет из вашей базы фактов все факты, совпадающие с об- разцом <the factx Предикат retractall имеет следующий формат:
Глава 15. Внутренняя база фактов Visual Prolog 373 retractall(<the fact>) retractall(<the fact>, databaseName) действие retractall аналогично действию, заданному retractall(X):- retract(X), fail, retractall(_) . но значительно быстрее него. Очевидно, предикат retractall всегда завершается успешно. Из retractall выход- ные значения получить нельзя. Это означает, что, как и в случае not, нужно исполь- зовать символ подчеркивания для свободных переменных. Так же, как и в случае предикатов assert и retract, для проверки типа можно ис- пользовать второй аргумент. И, как в случае предиката retract, если при вызове retractall используется символ подчеркивания, то из указанного раздела facts можно удалить все факты. Следующая цель удаляет все факты о мужчинах из базы фактов с фактами person: retractall(person(_, male)). Следующая цель удаляет все факты из базы mydatabase. retractall(_, mydatabase). Сохранение базы фактов во время работы программы Предикат save сохраняет факты из указанной базы фактов (раздела facts) в файле. Этот предикат имеет один или два аргумента: save(fileName) % (i) save(fileName, databaseName) % (i, i) При вызове предиката save только с одним аргументом (без имени базы фактов), в файле fileName будут сохранены факты из базы фактов dbasedom, используемой по умолчанию. При вызове предиката save с двумя аргументами (имя файла и имя базы фактов), в указанном файле будут сохранены факты из раздела facts базы фактов с именем databaseName. Ключевые слова, определяющие свойства фактов В объявлении раздела facts можно использовать следующие необязательные ключе- вые слова: facts [ — <databasename>] [nocopy] [{ nondeterm | determ I single }] dbPredicate [' ( ' [Domain [ArgumentName]]* ')']
374 Часть IV. Программирование на Visual Prolog Необязательные ключевые слова nondeterm, determ и single объявляют режим де- терминизма объявленного предиката базы фактов dbPredicate. Только одно из них можно использовать. Если режим детерминизма предиката базы фактов не задан явно, тогда по умолчанию принимается значение nondeterm. Обратите внимание, что режим nondeterm для предикатов баз фактов всегда задается по умолчанию, не зави- симо от установки флажка Default Predicate Mode в диалоговом окне Compiler Options VDE. □ nondeterm — определяет, что база фактов может содержать любое число фактов для предиката базы фактов dbPredicate. Это режим по умолчанию. □ determ — определяет, что база фактов может содержать не более одного факта для предиката базы фактов dbPredicate. □ single — определяет, что база фактов всегда содержит один и только один факт для предиката базы фактов dbPredicate. □ nocopy — обычно, когда предикат базы фактов вызван для связывания переменной со строковым или составным объектом, вызванные данные копируются из кучи (heap) в глобальный стек Visual Prolog (G Stack). nocopy объявляет, что данные не будут скопированы, а переменные будут ссылаться непосредственно на данные факта, хранимые в куче. Это может значительно увеличить эффективность, но если копия не была сделана, после удаления факта переменная будет указывать на некий '’мусор”. Поэтому использовать такой подход следует осторожно. □ global — определяет, что база фактов — глобальная (см. разд. "Модульное програм- мирование'' гл. 17). Обратите внимание, что надежная техника программирования требует, чтобы вы не использовали глобальные факты. Вместо этого вы можете применять глобальные предикаты, работающие с локальными фактами. Факты, объявленные с ключевым словом nondeterm Ключевое слово nondeterm определяет режим по умолчанию для фактов (предикатов базы фактов), объявленных в разделе facts. Если ни одно из слов determ или single не использовано при объявлении фактов, компилятор применяет режим nondeterm. Обычно по своей природе предикаты базы фактов недетерминированные. Поскольку факты могут быть добавлены в любой момент выполнения программы, компилятор должен учитывать, что во время поиска с возвратом возможно нахождение альтерна- тивных решений. Факты, объявленные с ключевым словом determ Ключевое слово determ определяет, что база фактов может содержать не более одно- го факта для предиката базы фактов, объявленного с этим ключевым словом. По- этому, если программа пытается установить второй такой факт в базе данных фак- тов, Пролог сгенерирует ошибку. Следовательно, программисту следует с особой осторожностью использовать детерминированные факты. Объявление факта детерминированным позволяет компилятору генерировать более эффективный код, и при вызове таких предикатов вы не будете получать предупре- ждения о возможном недетерминированном вызове. Это полезно для флагов, счет- чиков и других подобных объектов.
Гпава 15. Внутренняя база фактов Visual Prolog 375 Особенно обратите внимание на то, что при удалении факта, который объявлен de- term, вызов недетерминированных предикатов retract/1 и retract/2 будет детерми- нированным. Поэтому, если вы знаете, что в любой момент времени база фактов содержит не более одного факта counter, вы можете написать: facts determ counter(integer CounterValue) goal • • retract(counter(Currentcount)), % Пролог не установит точку отката Count= Currentcount + 1, assert(counter(Count)), вместо facts counter(integer CounterValue) predicates detenu retract_d (dbasedom) clauses retracted(X): — retract(X),!. % детерминированный предикат goal * * retract__d(counter(Currentcount)) / % Пролог не установит точку отката Count- Currentcount +1, asserta(counter(Count)), Факты, объявленные с ключевым словом single Ключевое слово single определяет, что база фактов всегда содержит один и только один факт для предиката базы фактов, объявленного с ключевым словом single. Поэтому single (однократные) факты должны быть уже известны, когда программа вызывает цель; следовательно, они должны быть инициализированы в разделах clauses в исходном коде программы. Например: facts — properties single numberWindows_s(integer) clauses numberWindows___s (0) . Однократные факты не могут быть удалены. Если вы попробуете удалить однократ- ный факт, компилятор сгенерирует ошибку. В большинстве случаев компилятор мо- жет определить попытку удаления однократного факта на этапе компиляции. Так как один экземпляр однократного факта всегда существует, вызов однократного факта никогда не завершается неуспехом, если он вызван со свободными аргумен- тами.
376 Часть IV. Программирование на Visual Prolog Например, следующий вызов: numberWindows_s(Num), никогда не завершается неуспехом, если Num — свободная переменная. Следователь- но, удобно использовать однократные факты в предикатах, объявленных с типом детерминизма procedure. Предикаты assert, asserta, assertz и consult, примененные к факту single, дейст- вуют аналогично паре предикатов retract и assert. А именно предикаты assert (consult) изменяют существующий экземпляр факта на указанный новый. Использование ключевого слова single перед декларацией факта позволяет компи- лятору произвести оптимизированный код для доступа к однократному факту и его модификации. Например, для предикатов assert, примененных к однократному факту, компилятор генерирует код, который работает более эффективно, чем пара предикатов retract и assert, примененных к детерминированному факту (и тем более, чем пара предикатов retract и assert, при использовании с обычным (не- детерминированным) фактом). Инициализация однократных фактов для некоторых доменов (для которых не име- ется значений по умолчанию) нетривиальна. Может оказаться полезной следующая информация: □ бинарные (binary) домены данных могут быть инициализированы путем при- своения им конкретного бинарного значения. Например: global domains font = binary facts — properties single my_font(font) clauses my_font($[00]) □ другим важным особым случаем является инициализация однократных фактов, содержащих стандартный домен ref. Домен ref является доменом для ссылочных чисел во внешних базах данных Visual Prolog (см. гл. 21), но он также использует- ся во многих предопределенных доменах, объявленных в пакетах, предоставляе- мых Visual Prolog. Например, основной домен VPI window объявлен так: domains window = ref Обратите внимание, что для инициализации значений домена ref вы можете ис- пользовать беззнаковые числа с предшествующим символом тильда (-). Напри- мер, можно написать: facts single mywin (WINDOW) clauses mywin(~0).
Глава 15. Внутренняя база фактов Visual Prolog 377 Примеры использования внутренней базы фактов 1. Программа ch08e01.pro (листинг!5.1) — простой пример того, как с помощью внутренней базы фактов написать классификационную экспертную систему. Важным преимуществом использования базы фактов в этом примере является то, что вы можете добавлять знания (и удалять их) во время работы программы. к*'*********’************-*.****.*•.**<**'*>**•.* •;'*****!* '*•»•*%»*.*<^1»и|!»****М»* *!%•**»•'*'**»*****>****•»%» »^Л>»гЭ**^>.»»ЛЖ**<****-*».»**Л**Л*.Л.М.**••****•***!*•* i Листинг 15.1. Программа ch08ebl.pro:';; О?а<(а\вь.гВ(Вь,н »4а4*ф*4г Л цГквв» ька.,ka.a/L ЛВ«В<1ВВ »* »В^>«Ч*«В*ьф<Ьа'аа*а«^»аЬВа**Й»Л^Л^а'В **'«» »»|1»йВ4?кВВ*4В4аф^ »>вВв,Га»цаа)1»1фВа каааааь«афВа^Ва>Ь* domains thing = string conds = cond* cond = string 'facts — knowledgeBase is_a (thing, thing, conds) type_of(thing, thing, conds) false(cond) predicates run(thing) ask(conds) update clauses run(Item):- is_a(X, Item, List), ask(List), type_of(ANS, X, List2), ask(List2), write("The ", Item," you need is a/an ", Ans),nl. run(_):- write("This program does not have enough "), write("data to draw any conclusions."), nl. ask( []) . ask([HIT]) not(false(H)), write("Does this thing help you to "), write(H," (enter y/n)"), readchar(Ans), nl, Ans=’y’, ask(T). ask([H|_]) assertz(false(H)), fail. is_a(language,tool,["communicate"]). is_a(hammer,tool,["build a house","fix a fender","crack a nut"]). is_a(sewing-machine, tool, ["make clothing", "repair sails"]). is_a(plow, tool, ["prepare fields", "farm"]).
378 Часть IV Программирование на Visual Prolog type_of (english, language, [’'communicate with people"]). type_of(prolog, language, ["communicate with a computer"]). update:- retractall(type_of(prolog,language, ["communicate with a computer"])), asserta (type__of ("Visual Prolog", language, ["communicate with a personal computer"])), asserta(type_of(prolog, language, ["communicate with a mainframe computer"])). Следующие факты могли бы быть занесены с помощью предиката asserta или assertz, или считаны из файла с помощью предиката consult. Однако в этом примере они расположены в разделе clauses. is_a(language, tool, ["communicate"]). is_a(hammer, tool, ["build a house", "fix a fender", "crack a nut"]). is_a(sewing-machine, tool, ["make clothing", "repair sails"]). is_a(plow, tool, ["prepare fields", "farm"]}. type__of(english, language, ["communicate with people"]). type_of(prolog, language,'["communicate with a computer"]). В качестве цели введите: goal run(tool). Теперь введите следующую цель: update, run(tool). Предикат update включен в исходный код программы, удаляет факт type_of(prolog, language, ["communicate with a computer"]). из внутренней базы фактов knowledgeBase и добавляет два новых факта в про- грамму: type_of(prolog, language, ["communicate with a mainframe computer"]) type_of("Visual Prolog", language, ["communicate with a personal computer"]) С помощью вызова предиката save/2 с именами текстового файла и базы фак- тов в качестве его аргументов можно сохранить всю базу данных фактов knowledgeBase в текстовом файле. Например, после вызова save ("mydata.dba", knowledgeBase) файл mydata.dba будет аналогичен разделу clauses обычной программы Visual Prolog, и каждый факт будет записан в отдельной строке. С помощью предиката consult можно считать факты из этого файла в память: consult ("mydata.dba", knowledgeBase) .
Гпава 15. Внутренняя база фактов Visual Prolog 379 2. Вы можете манипулировать фактами, описывающими предикаты баз фактов (фактами, описанными в разделах facts) так, как будто они являются термами. При объявлении базы фактов Visual Prolog генерирует внутренний домен, соот- ветствующий фактам из раздела facts. Например: facts — dbal % dbal домен для этих предикатов person(name, telno) city(eno, cname) Получив такие объявления, компилятор Visual Prolog сгенерирует соответствую- щий домен dbal: domains dbal = person(name, telno); city(cno, cname) Этот домен dbal может быть использован так же, как любой другой домен. На- пример, для создания предиката my_consult, аналогичного стандартному преди- кату consult, вы можете использовать стандартный предикат readterm (см. гл. 19). Резюме В этой главе были рассмотрены следующие важные моменты. □ Внутренняя база фактов Visual Prolog состоит из фактов вашей программы, сгруппированных в разделе facts. Предикаты, определяемые пользователем и используемые в этих группах фактов, можно объявлять с помощью ключевого слова facts. □ Разделам facts можно давать имена (которые создают соответствующий внут- ренний домен). По умолчанию доменом для неименованного раздела facts будет домен bdasedom. В программе может присутствовать несколько разделов facts, но каждый из них при этом должен иметь уникальное имя. Предикат базы фак- тов можно описать только в одном разделе facts. □ С помощью стандартных предикатов asserta, assertz и consult можно добав- лять факты к внутренней базе данных фактов во время работы программы. С по- мощью предикатов retract и retractall можно удалять эти факты во время ра- боты программы. □ Предикат save сохраняет факты из внутренней базы фактов в файле (в опреде- ленном формате). С помощью редактора можно создавать или редактировать такой файл фактов, а затем можно вносить факты из файла в программу с по- мощью предиката consult. □ Ваша программа может обращаться к предикатам базы фактов в программе таким же образом, как и ко всем другим предикатам. □ При использовании внутренних доменов, сгенерированных для имен разделов facts, можно работать с фактами как с термами.
ГЛАВА 1 6 Арифметические вычисления и сравнения Возможности вычислений и сравнений в Visual Prolog аналогичны соответствующим возможностям таких языков программирования, как Basic, С, Pascal. Visual Prolog включает полный набор арифметических функций. Вычислительные возможности Visual Prolog уже были показаны на нескольких простых примерах, а В этой главе приведен обзор встроенных в Visual Prolog предикатов и функций для выполнения вычислений и сравнений. Мы также обсудим сравнение строк и сим- волов. Арифметические выражения Арифметические выражения состоят из операндов (чисел и переменных), операторов (+, *, /, div и mod) и скобок. Символы в правой части от знака равенства (который является предикатом =) составляют арифметическое выражение. Например: А = 1 + 6 / (11+3) * Z Шестнадцатеричные и восьмеричные числа начинаются с "Ох” или "Оо" соответст- венно. Например: OxFFF = 4095 86 = 0о112 + 12 Значение выражения может быть вычислено, только если все переменные в момент вычисления определены. При этом вычисления производятся в порядке, определяе- мом приоритетом операции. Операции с высшим приоритетом выполняются первы- ми. Операции Visual Prolog может выполнять все четыре основные арифметические операции (сложение, вычитание, умножение и деление) между целыми и вещественными чис- лами. Тип результата приведен в табл. 16.1.
Гпава 16. Арифметические вычисления и сравнения 381 Таблица 16.1. Арифметические операции Операнд 1 Оператор Операнд 2 Результат Целое +, * Целое Целое Вещественное +, * Целое Вещественное Целое * Вещественное Вещественное Вещественное +, * Вещественное Вещественное Целое или вещественное / Целое или вещественное Вещественное Целое Div Целое Целое Целое Mod Целое Целое В случае смешанной целочисленной арифметики, включающей и знаковые и без- знаковые числа, результат знаковый. Размер результата будет равен большему из размеров двух операндов. Таким образом, результатом операции между ushort и long будет long; результатом операции между ushort и ulong будет ulong. Порядок вычислений Арифметические операции вычисляются в следующем порядке: □ если выражение содержит подвыражение в скобках, подвыражение вычисляется первым; □ если выражение содержит операции умножения (*) или деления (/, div или mod), эти операции выполняются слева направо; □ если выражение содержит операции сложения (+) и вычитания (-), они выпол- няются также слева направо. Порядок операций показан в табл. 16.2. Таблица 16.2. Порядок операций Операция Приоритет х, /, mod, div 2 + (унарные) 3 В выражении а = 1 + 6/(11 + 3)* z, предположим, что z имеет значение 4, ибо переменные должны быть связаны до вычисления. Вычислим это выражение: 1. (11 + 3) — первое вычисляемое подвыражение, т. к. оно заключено в скобки, оно вычисляется как 14.
382 Часть IV. Программирование на Visual Prolog 2. Затем вычисляется 6/14, т. к. / и * вычисляются слева направо. В результате по- лучим 0.428571. 3. Далее 0.428571*4 дает 1.714285. 4. Наконец, вычисляя 1 + 1.714285, получаем значение выражения 2.714285. а получит значение 2.714285, которое принадлежит вещественному домену. Следует поупражняться в управлении вещественными числами. В большинстве слу- чаев они не представлены точно, и маленькие ошибки могут накапливаться, выдавая непредсказуемые результаты. Примеры некоторых вычислений представлены ниже в этой главе. Функции и предикаты Visual Prolog имеет полный набор встроенных математических функций и предика- тов, которые используют целые и вещественные значения. Полный их список при- веден в табл. 16.3. Таблица 16.3. Арифметические предикаты и функции Visual Prolog Имя Описание . . — -- , 4 - • --------------------- — — - .... — - - - . — . X mod Y Возвращает остаток от деления (модуль) х на у X div Y Возвращает частное от деления х на Y abs(X) Если значение х— положительная величина value, abs(X) возвра- щает это значение; в противном случае — l*value cos (X) Возвращает косинус своего аргумента sin (X) Возвращает синус своего аргумента tan (X) Возвращает тангенс своего аргумента arctan (X) Возвращает арктангенс вещественного значения, с которым связан X exp(X) Возводит е в степень X 1п(Х) Логарифм х по основанию е log(X) Логарифм х по основанию 10 sqrt(X) Корень квадратный из х random(X) random(X, Y) round(X) trunc(X) val(domain, X) Присваивает X случайное вещественное число; 0 <= X < 1 Присваивает y случайное целое число; 0 <= Y < X Округляет значение х. Результат вещественный Усекает X. Результат вещественный Явное преобразование между числовыми доменами
Гпава 16, Арифметические вычисления и сравнения 383 QЗамечание Тригонометрические функции требуют, чтобы х был величиной, представляющей угол в радианах. Генератор случайных чисел Для генерации случайных чисел в Visual Prolog предусмотрены два стандартных пре- диката. Один из них возвращает случайное вещественное число в диапазоне от О до 1, другой возвращает случайное целое число в диапазоне от 0 до данного ч^сла. Кроме того, случайная числовая последовательность может быть переинициализиро- вана. Предикат random/1 Эта версия random возвращает случайное вещественное число RandomReal, которое удовлетворяет условию: О <= RandomReal < 1. random/1 имеет формат: random(RandomReal) % (о) Предикат random/2 Эта версия random имеет два аргумента, его формат: random(MaxValue, Randomlnt) % (if о) Этот предикат ставит в соответствие Randomlnt случайное целое, удовлетворяющее условию: О <- Randomlnt < MaxValue Предикат random/2 работает значительно быстрее, чем random/1, т. к. random/2 ис- пользует только целочисленную арифметику. Предикат randominit/1 randominit инициализирует генератор случайных чисел и имеет формат: randominit(Seed) % (1) По умолчанию случайное начальное значение генерируется как функция системного времени, и аргумент Seed предиката randominit устанавливает начальное значение. Основное назначение randominit — предоставить повторяемую последовательность псевдослучайных чисел для статистического тестирования. Заметьте, что обе — це- лочисленная и вещественная — версии random используют одинаковое начальное значение и базовый генератор чисел. В качестве примера приведем программу ch09e01.pro (листинг 16.1), которая исполь- зует random/1 для выбора трех имен из пяти случайным образом.
384 Часть IV. Программирование на Visual Prolog predicates person(integer, symbol) rand_int_l_5(integer) rand_jperson (integer) clauses personfl,fred). person(2,tom). person(3,mary). person(4,dick). person(5,george). rand_int_l_5(X):- random(Y), X=Y*4+1. rand_jperson (0) , rand__person (Count) rand_int_l_5 (N), person(N,Name), write(Name),nl, NewCount=Count-1, rand_person(NewCount). goal rand__person (3) . Целочисленная и вещественная арифметика Visual Prolog поддерживает предикаты и функции модульной арифметики, целого деления, квадратные корни и абсолютные значения, тригонометрические и транс- цендентные функции, округление (вверх или вниз) и усечение. Они приводятся в табл. 16.3 и поясняются ниже. Функция mod/2 Функция mod вычисляет остаток от деления х на y (где X и Y — целые). X mod Y % (i, i) Выражение z = х mod Y ставит в соответствие z результат — остаток от деления х на Y. Например: Z = 7 mod 4 % Z будет равно 3 Y = 4 mod 7 % Y будет равно 4 Функция div/2 Функция div вычисляет целое частное от деления х на Y (где X и Y — целые). X div Y % (i, i)
Глава 16; Арифметические вычисления и сравнения 385 Выражение z = х div Y ставит в соответствие z целую часть результата. Например; Z = 7 div 4 % Z будет равно 1 Y - 4 div 7 % Y будет равно О Функция abs/1 Функция abs возвращает абсолютное значение своего аргумента. abs(X) % (i) Выражение z = abs(X) ставит в соответствие z (если оно свободно) результат, или возвратит успех/неуспех, если z уже определено. Например; Z - abs(-7) % Z будет равно 7 Функция cos/1 Функция cos возвращает значение косинуса своего аргумента. cos(X) % (i) № Выражение z = cos(X) ставит в соответствие z (если оно свободно) результат, или возвратит успех/неуспех, если z уже определено. Например; Pi - 3.141592653, Z = cos(Pi) % Z будет равно -1 Функция sin/1 Функция sin возвращает значение синуса своего аргумента. sin(X) % (i) Выражение z = sin(X) ставит в соответствие z (если оно свободно) результат, или возвратит успех/неуспех, если z уже определено. Например: Pi = 3.141592653, Z = sin(Pi) % Z будет почти равно О Функция tan/1 Функция tan возвращает значение тангенса своего аргумента. tan(X) % (i) Выражение z = tan(X) ставит в соответствие z (если оно свободно) результат, или возвратит успех/неуспех, если z уже определено. Например; Pi = 3.141592653, Z = tan(Pi) % Z будет равно почти О
386 Часть IV. Программирование на Visual Prolog Функция arctan/1 Функция arctan возвращает арктангенс от вещественного значения, с которым свя- зано х. arctan(X) % (i) Выражение z = arctan (X) ставит в соответствие z (если оно свободно) результат, или возвратит успех/неуспех, если z уже определено. Например: Pi = 3.141592653, Z = arctan(Pi) % Z будет равно 1.2626272556 Функция ехр/1 Функция ехр возвращает значение е в степени значения, с которым связано х. ехр(Х) % (i) Выражение z = ехр(Х) ставит в соответствие z (если оно свободно) результат, или возвратит успех/неуспех, если z уже определено. Например: Z = ехр(2.5) % Z будет равно 12.182493961 Функция 1п/1 Функция In возвращает значение натурального логарифма от х (по основанию е). 1п(Х) % (i) Выражение z = 1п(Х) ставит в соответствие z (если оно свободно) результат, или возвратит успех/неуспех, если z уже определено. Например: Z == In (12.182493961) % Z будет равно 2.5 Функция 1од/1 Функция log возвращает значение логарифма по основанию 10 от х. log(X) % (i) Выражение z = 1од(Х) ставит в соответствие z (если оно свободно) результат, или возвратит успех/неуспех, если z уже определено. Например: Z = log(2.5) % Z будет равно 0.39794000867 Функция sqrt/1 sqrt возвращает квадратный корень от х. sqrt(X) % (i) Выражение z = sqrt (X) ставит в соответствие z (если оно свободно) результат, или возвратит успех/неуспех, если z уже связано. Например: Z = sqrt(25) % Z будет равно 5
Гпава 16. Арифметические вычисления и сравнения 387 Функция round/1 round возвращает округленное значение х. round(X) % (i) Функция round округляет х до ближайшего целого, но не производит преобразова- ние типа. Например: Zl = round(4.51) % Zl будет равно 5.0 Z2 = round(3.40) % Z2 будет равно 3.0 Оба Z1 и Z2 — вещественные значения, как показано выше; только дробные части аргументов round округляются до ближайшего целого. Функция trunc/1 Функция trunc усекает х справа до десятичной точки, отбрасывая дробную часть. Как и round, trunc не выполняет преобразование типов: trunc(X) % (i) . Например: Z = trunc(4.7) % Z будет равно 4.0, Z — вещественное число. Функция val/2 Функция val производит преобразование числовых доменов и обнаруживает любые возможные переполнения. Формат: Result = val(returnDomain,Expr), здесь Exp г будет вычислено (если это выражение), результат преобразован к типу returnDomain и присвоен Result. Visual Prolog также включает функцию cast, кото- рая преобразовывает домены без какой-либо проверки (подробнее см. ел. 17). Упражнение Используйте тригонометрические функции Visual Prolog для выдачи таблицы сину- сов, косинусов и тангенсов на экран. Левая колонка таблиц должна содержать вели- чину угла в градусах, начиная от 0 до 360 градусов с шагом в 15 градусов. Замечание Так как тригонометрические функции выражаются в радианах, чтобы получить вели- чины в левой колонке, вы должны перевести радианы в градусы. Degrees = Radians * 180/3.14159265... .
388 Часть IV. Программирование на Visual Prolog Сравнение Visual Prolog может сравнивать арифметические выражения так же, как символы, строки и идентификаторы. Следующее выражение в Visual Prolog эквивалентно вы- ражению: "Сумма х плюс 4 меньше 9 минус у". X + 4 < 9 - Y Оператор отношения "меньше чем" (<) показывает отношение между двумя выраже- ниями: х + 4 и 9 — Y. Visual Prolog использует инфиксную нотацию, которая означает, что оператор распо- лагается между операндами (х+4) вместо того, чтобы предшествовать им (+(Х,4)). Полный ряд отношений, разрешенных в Visual Prolog, приведен в табл. 16.4. Таблица 16.4. Операторы отношения Идентификатор Отношение Идентификатор Отношение Меньше Меньше или равно Равно о или >< Больше Больше или равно Не равно Равенство и предикат равенства В Visual Prolog такие операторы, как N = N1 — 2 показывают отношение между тре- мя объектами (n, N1 и 2) или отношение между двумя объектами (и и величиной N1 - 2). Если N еще свободно, оператор может быть удовлетворен присвоением ъ значения выражения N1 ~ 2. Это приблизительно соответствует тому, что в других языках программирования называется оператором присваивания. Заметим, что по- скольку N1 является частью вычисляемого выражения, оно всегда должно быть опре- делено (т. е. должно быть связано со значением). Когда вы для сравнения вещественных величин используете предикат равенства (=). нужно позаботиться о том, чтобы приближенное представление вещественных чисел не привело к непредсказуемым результатам. Например, цель 7/3 *3 = 7 потерпит неудачу (точный результат зависит от точности вычисления чисел с пла- вающей точкой, используемой на вашей платформе). Программа ch09e02.pro (лис- тинг 16.2) иллюстрирует еще один пример. gij; I М vv»**v*v«*>*v ‘ I <'** < *<'* < чИПШ vav.v<<<av<а Я.ft « a1 'aa « « • , 1И « t. f f f f я « « « . — —»»! aaMa.....a»»»a a, a« a * .a <a a a *a а ц »a a a .a a, >> > a, .« a...... v. v •» av .4 •«'••• v I »'" »»»-*» J»*, f .iiii . . predicates test(real,real)
Глава 16, Арифметические вычисления и сравнения 389 clauses test(Х,Х) : -! , write("ok\n"). test(X, Y):- Diff = X-Y, write(X,"<>",Y,"\nX-Y = ",Diff,'\n'). goal X=47, YM .7*10, test (X,Y) . Результат будет следующий: 47047 X-Y = 7.1054273576E-15 Следовательно, сравнивая два вещественных числа на равенство, вам всегда следует проверять, чтобы эти два числа находились в определенном диапазоне друг относи- тельно друга. Приведем следующий пример: Программа ch09e03.pro (листинг 16.3) показывает, как обработать приблизительное равенство. Она представляет собой итеративную процедуру вычисления квадратного корня для определения решений квадратного уравнения: А*Х*Х + В*Х + С = О Наличие решений зависит от значения дискриминанта D, определяемого как D - В*В - 4*А*С В данном случае: 1. D > 0 — означает, что существует два различных решения. 2. D = О — означает, что существует единственное решение. 3. D < 0 — означает, что решений нет, если х — вещественное (могут быть одно или два комплексных решения). ...►'iiliiiril, ..., t i 1 ' «««j .'I'|l|t.ft i i Jfc i Бi фi i фi ill',.Б.. И.1 ) |>«l | I >,l II'f'i "I if i <'( I.. ».f I f ' fl i >'Г ' i i i ' ’ll I I I |> :Шистйнг';16'.3;;Прргра1Йма:;сКр.9^(Й1£гоД':|;1 if if i'I i i< i i I 11 ’I' Га rff и i I">"I 111''I "I >i 11 Г'Г', I , , ri'ii """"<"'1 >,>|1'>'>""1'|'<'<1<1| <i<iIiIIIiIi|IIIi<<<iii<<<<"i'i'iiri<<i<>>r>f^>>i'««Ai 11, 1111111 I > I'ii >'I I I'II’I’1! I'*111 predicates solve(real, real, real) reply(real, real, real) mysqrt(real, real, real) equal(real, real) clauses solve (А, В, C) : - D=B*B-4*A*C, reply(A, B, D), nl.
390 Часть IV. Программирование на Visual Prolog reply D) D < О, write("No solution"), i reply(A,B, D):- D=0, X=-B/(2*A),write("x=", X), ! . reply (A, B, D) : - mysqrt (D, D, SqrtD) , Xl=(-B+SqrtD)/(2*A), X2 = (-B - SqrtD)/(2*A)f write("xl = ", XI," and x2 = ", X2). mysqrt(X,Guess,Root) NewGuess = Guess-(Guess*Guess-X)/2/Guess, not(equal(NewGuess,Guess)), i * / mysqrt(X,NewGuess,Root). mysqrt(_,Guess,Guess)'. equal(X,Y): - X/Y >0.99999, X/Y < 1.00001. Чтобы решить квадратное уравнение, эта программа вычисляет квадратный корень из дискриминанта D. Программа вычисляет квадратные корни по итеративной фор- муле, где новое значение (NewGuess) квадратного корня от х может быть получено из предыдущего значения (Guess): NewGuess = Guess-(Guess*Guess-X)/2/Guess Каждая итерация немного приближается к квадратному корню от х. Когда условие equal (X, Y) удовлетворяется, дальнейшего приближения достичь нельзя — вычисле- ние заканчивается. Теперь программа может решить квадратное уравнение, исполь- зуя значения XI и Х2 XI = (-В + sqrtD)/(2*А) Х2 = (-В - sqrtD)/(2*А) Упражнения 1. Загрузите программу ch09e03.pro и проверьте с помощью Test Goal следующие цели: solve (1, 2, 1) . solve(1, 1, 4) . solve(1, -3, 2) .
Гпава 16, Арифметические вычисления и сравнения 391 Решениями должны быть: х - -1 No solution xl = 2 and х2 = 1 соответственно. 2. Целью этого упражнения является эксперимент с предикатом mysqrt в программе ch09e03.pro. Для того чтобы можно было следить за промежуточными вычисле- ниями, добавьте в качестве первой подцели в первое предложение mysqrt: write(Guess). Испытайте такую цель: mysqrt(8, 1, Result). Затем замените предложение для equal фактом: equal(X, X). и повторно проверьте цель. Поэкспериментируйте немного со свойствами equal. Например, попробуйте: equal(X, Y) X/Y < 1.1, X/Y > 0.9. Visual Prolog имеет встроенную функцию квадратного корня sqrt. Например: X = sqrt(0) присвоит х квадратный корень от значения D. Перепишите программу ch09e03.pro с использованием sqrt и сравните полученные ответы с ответами первоначаль- ной версии. Сравнение символов, строк и идентификаторов Кроме числовых выражений вы можете также сравнивать простые символы, строки и идентификаторы. Рассмотрим следующие сравнения: 'а' < ’Ь' % Символы "antony" > "antonia" % Строки Pl = peter, Р2 = sally, Pl > Р2 % Литералы (идентификаторы) Символы Используя соответствующее значение кода ASCII для каждого символа, Visual Prolog преобразует 'а' < 'Ь’ в эквивалентное арифметическое выражение 97 < 98. Отме- тим, что надежными являются только 7-битные ASCII сравнения (т. е. буквы верх- него и нижнего регистров a—Z, цифры и т. д.). 8-битные символы, используемые для некоторых национальных символов, не всегда оказываются переносимыми между различными платформами.
392 Часть IV. Программирование на VisualProlog Строки Когда строки или идентификаторы сравниваются, результат зависит от сравнения символов на соответствующих позициях. Результат равен тому, который вы получи- ли бы при сравнении первых символов в том случае, если эти два символа не равны. Если же они равны, Visual Prolog сравнивает следующую соответствующую пару символов и возвращает результат в том случае, если на сей раз они не равны. В про- тивном случае проверяется третья пара и т. д. Сравнение заканчивается, когда либо найдены два различных символа, либо достигнут конец любой из строк. Если конец достигнут, и пара различных символов не найдена, более короткая строка считается меньшей. Сравнение "antony" > "antonia" ©ценится как true (истинное), т. к. первая пара различных символов содержит в первой строке букву у (значение 79 в коде ASCII), а в другой строке букву i (ASCII значение 69). Аналогично, сравнение "аа" > "а" истинно. Выражение "peter” > "sally" будет ложным, т. к. определяется сравнением значе- ний ASCII первых букв слов peter и sally. Символ р располагается перед символом s в алфавите, поэтому р имеет меньшее значение кода ASCII. Таким образом, выра- жение будет оценено как ложное, ч Идентификаторы Идентификаторы не могут непосредственно сравниваться из-за их синтаксиса. В предыдущем примере см. разд. "Сравнение символов, строк и идентификаторов" данной главы (pl = peter, Р2 ...,) идентификатор peter не может непосредственно сравниваться с идентификатором sally. Они должны быть связаны с переменными, которые сравниваются или записаны как строки.
ГЛАВА 1 7 Более сложные приемы программирования Полагая, что, поработав с примерами изученных глав, вы приобрели необходимые навыки, изложим в этой главе более сложные аспекты программирования на Visual Prolog. Рассмотрим сначала, как можно контролировать анализ потока параметров при помощи стандартных предикатов free и bound, как пользоваться ссылочным типом (reference) и как отделять переменные этого типа от других переменных. Мы также изучим более сложные вопросы, касающиеся доменов, включая бинарный (binary) домен, указатели на предикаты и функции, а также возвращаемые функция- ми значения. В конце главы — "работа над ошибками", динамическое усечение, сво- бодное преобразование типов и, кроме того, вопросы, связанные со стилем про- граммирования и повышением эффективности программ. Анализ потока параметров При вызове предиката аргументы, значения которых известны, рассматриваются как входные аргументы (i), а неизвестные — как выходные аргументы (о). Список вход- ных и выходных аргументов для данного предиката называют потоком параметров (flow pattern). Если, например, предикат имеет два параметра, то существуют четыре варианта по- тока параметров: (i, i) (i, о) (о, i) (о, о) При компиляции программ в Visual Prolog выполняется глобальный анализ потока параметров. Он начинается с основного целевого утверждения, а затем выполняется псевдооценка всей программы. При этом поток параметров определяется для каждо- го обращения к каждому предикату в программе. Анализ потока параметров весьма прост; фактически вы его выполняете, когда пишете свою программу. Рассмотрим некоторые примеры: goal cursor(R, С)f R1 = R + 1, cursor(Rl, C).
394 Часть IV. Программирование на Visual Prolog При первом обращении к предикату cursor переменные кис являются свободными; это означает, что предикат cursor вызывается с потоком параметров cursor (о, о). Вы знаете, что переменные являются свободными, т. к. они встречаются впервые. В выражении R1 = R + 1 переменная R трактуется как связанная, поскольку она приходит из предиката cursor. Если бы она была свободной, то появилось бы сооб- щение об ошибке. После вычисления этого выражения переменная R1 также стано- вится связанной. При последнем обращении к предикату cursor обе переменные R1 и с уже известны, так что они трактуются как входные параметры; при этом вызов предиката будет иметь поток параметров cursor (i,i). Для каждого потока параметров, с которым вызывается пользовательский предикат, анализатор потока проходит через предложения предиката с переменными из головы предиката, определяя, являются ли они входными или выходными переменными анализируемого потока параметров. В качестве иллюстрации рассмотрим следующий фрагмент программы: L % Для запуска этого примера вам следует в эксперте приложений VDE % (VDE’s Application Expert) установить настройки Target в "DOS” % и "Textmode" и использовать команду меню Project 1 Run а predicates changeattrib(Integer, Integer) clauses changeattrib(NewAttrib, OldAttrib) attribute(OldAttrib), attribute(NewAttrib), goal changeattrib(112, Old), write("Hello") , attribute(Old) , write(" there"), readchar(_). В разделе goal первое обращение к предикату changeattrib выполняется с потоком параметров changeattrib(i,o) (поскольку 112 известно, a old — нет). Это означает, что в предложении для changeattrib переменная NewAttrib будет входным парамет- ром, a OldAttrib — выходным параметром. Поэтому при анализе потока параметров первой подцели attribute (OldAttrib) предикат attribute будет вызываться с пото- ком параметров attribute (о), тогда как при втором обращении предикат attribute будет вызываться с потоком параметров attribute (i) . И наконец, в целевом утверждении параметр old для предиката attribute будет входным параметром, поскольку Old является выходным параметром для changeattrib. Составной поток параметров Если аргумент предиката — составной терм, то можно определить составной поток параметров, в котором соответствующий аргумент является как входным, так и вы-
Гпава 17. Более сложные приемы программирования 395 ходным. Предположим, есть база данных с информацией о странах. Для удобства добавления новых данных желательно, чтобы каждый элемент информации принад- лежал своей собственной альтернативе домена (листинг 17.1). diagnostics domains cinfo = area(string,ulong) ; population(string,ulong); capital(string,string) predicates country(cinfo) clauses country(area("Denmark”,16633)). country(population("Denmark",5097000)). country(capital("Denmark”,"Copenhagen”)). country(area("Singapore",224)). country(population("Singapore",2584000)). country(capital("Singapore","Singapore”)). Вот различные потоки параметров, с которыми можно вызывать country: goal country(С), country(area(Name, Area)), country(population("Denmark",Pop)), country(capital("Singapore","Singapore”)). % (o) % area(o,o) % population(i,o) % (i) Заметьте, что т. к. в последнем вызове все термы известны, то поток параметров по умолчанию — простой входной (i). Загрузите и испытайте цель из приведенного выше примера с утилитой Test Goal (см. разд. "Тестирование примеров данного руководства" гл. 8). В окне Messages визу- альной среды разработки вы увидите таблицу с информацией об отмеченных выше потоках параметров: Predicate Name Type Determ Size Domains — flowpattern goal$000$country$l goal$000$country$2 goal$000$country$3 goal$000$country$4 local nondtm 168 local nondtm 72 local nondtm 108 local nondtm 416 cinfo — о cinfo — area(o,o) cinfo — population(i,о cinfo — i Если домены, вовлеченные в сложный поток параметров, являются ссылочными, различие между известными и неизвестными аргументами становится ’смазанным*'. Мы вернемся к этому примеру позднее (см. разд. "Объявление ссылочных доменов" данной главы).
396 Часть IV. Программирование на Visual Prolog Задание потоков параметров для предикатов Иногда имеет смысл явно задавать потоки параметров для предикатов. Например, если вы знаете, что ваши предикаты имеют смысл лишь при определенных потоках параметров, то вам следует задать эти потоки, т. к. в этом случае анализатор потоков распознает неправильное использование этих предикатов. Именно для этого вы по- сле определения доменов аргументов предиката ставите тире и перечисляете допус- тимые потоки параметров: predicates frame_text_mask(string,string,slist) — (i,o,o)(o,i,o) Контроль потока параметров В том случае, когда анализатор потоков определяет, что стандартный предикат вы- зывается с несуществующим потоком параметров, выдается сообщение об ошибке, что может облегчить обнаружение неправильных потоков при создании собственных предикатов, которые обращаются к стандартным предикатам. Так, например, если напишем: Z = X + Y где переменные х и у не связаны, то при анализе потока параметров будет выдано сообщение об ошибке, говорящее о том, что такой поток параметров не существует для данного предиката. Для контроля подобных ситуаций вы можете применить стандартные предикаты free и bound. Предположим, что вы хотите создать предикат для сложения plus, к которому мож- но было бы обращаться со всеми возможными потоками параметров. В программе сЫ0е02.рго (листинг 17.2) приведен пример такого предиката. ............................................................................................................................................................................................ ] Листинг 17.2. Программа ch10e02.pro ............................................................................................*****«*«*V*. 1 |*«V|Vlr4 I *>.*.* diagnostics predicates plus(integer, integer, integer) num(integer) clauses plus(X,Y,Z) bound(X),bound(Y),Z^X+Y. plus(X,Y, Z) :- bound(Y),bound(Z),X=Z-Y. plus(X,Y,Z):- bound(X),bound(Z),Y=Z-X. plus(X,Y,Z) . free (X) , free (Y) ,bound(Z) ,num(X) , Y==Z-X. plus(X,Y,Z) free(X) , free(Z),bound(Y),num(X),Z=X+Y. % (i,i,o) % (o,i,i) % (i,o,i) % (o,o,i) % (o,i,o)
Гпава 17. Более сложные приемы программирования 397 plus(X,Y,Z):- free(Y),free(Z),bound(X),num(Y),Z=X+Y. % (i,o,o) plus(X,Y,Z) free(X)f free(Y),free(Z)fnum(X),num(Y), Z=X+Y. % (o,o,o) % Генератор чисел стартует с О num(O) . num(X) num (А) , X = A+l. Ссылочные типы При проведении анализа потока параметров осуществляется проверка того, что всем выходным переменным, указанным в голове предложения, присваиваются значения в теле предложения. Если переменная не получает значения в предложении, то она должна рассматриваться как ссылочная переменная. Ниже приведен пример подобной ситуации: predicates р(integer) clauses Р(Х) . goal p(V), V - 99, write(V). В целевом утверждении goal предикат р вызывается с выходным параметром, одна- ко, в предложении для р аргумент X является свободным. Если при анализе потока параметров обнаруживается подобная ситуация, то система проверяет тип данной переменной. Если тип объявлен как ссылочный (reference), то проблем не будет; в противном случае, Visual Prolog попытается самостоятельно переопределить тип как ссылочный. Если это возможно (см. список ссылочных типов в окне Messages VDE), компилятор генерирует предупреждающее сообщение. Если же это невоз- можно, например в программах, содержащих несколько исходных модулей, то гене- рируется сообщение об ошибке. Замечание J Начиная с версии 5.2, компилятор Visual Prolog по умолчанию генерирует ошибку при попытке использовать стандартный домен Visual Prolog как ссылочный. В этом примере базовый домен используется как ссылочный. Следовательно, попытка вы- звать Test Goal (с параметрами VDE по умолчанию) на этом примере вызовет ошиб- ку типа "Basic domain becomes reference domain" (Базовый домен становится ссы- лочным доменом), на integer домен. Поэтому, чтобы запустить Test Goal на этом примере, вы должны явно сказать компилятору, что вы "позволяете базовым доме- нам становиться ссылочными доменами". Это можно сделать, определив опцию компилятора в командной строке:
398 Часть IV. Программирование на Visual Prolog Чтобы передать эту опцию из VDE в командную строку вызова компилятора во вре- мя запуска Test Goal, необходимо задать -R+ в элементе редактирования Pre- defined Constants в диалоговом окне Compiler Options. В этом случае компилятор не будет генерировать ошибку. Настоятельно рекомендуем всегда явно объяв- лять все ссылочные домены в разделах domains. Когда переменная в предложении не связана, то предложение не может вернуть ее значение. Вместо этого предложение вернет ссылочный указатель, куда позднее может быть занесено фактическое значение переменной. При этом требуется, чтобы все переменные данного типа рассматривались одинаково; вместо простой передачи значений параметров, относящихся к этому типу, в качестве аргументов для пара- метров ссылочного типа будут передаваться указатели. Если составной домен стано- вится ссылочным, то все его поддомены также должны стать ссылочными, посколь- ку они также должны иметь возможность содержать свободные переменные. Если вы просто объявите сложный домен ссылочным, то компилятор автоматически узна- ет, что все поддомены тоже относятся к ссылочному домену. Объявление ссылочных доменов Когда анализатор потоков обнаруживает свободную переменную, он выдает только предупреждение, что предложение возвращает несвязанную переменную. Если вы игнорируете это предупреждение, то домен переменной будет автоматически рас- сматриваться как ссылочный домен. Все его поддомены также будут объявлены компилятором ссылочными. Так как код обрабатывается одинаково для всего домена, то идея интерпретировать стандартные домены как ссылочные, не самая удачная. Вместо этого вам нужно объявить домен, который будет ссылочным на желаемый базовый домен. Например, в следующем отрывке кода пользовательский домен ref integer объявлен ссылочным на домен integer. Любое появление типа refinteger будет рассматриваться как ссылочный домен, но всякое появление любого другого целого домена будет рас- сматриваться как соответствующий целый домен: domains refinteger = reference integer predicates p(refinteger) clauses P(_). Заметим, что если базовый домен, например integer, описывается как ссылочный, то переменные, принадлежащие этому базовому домену, рассматриваются как ссыл- ки на значения этого базового домена. Например, целые переменные будут содер- жать не целые числа, а ссылки на целые. Если предикаты в каком-то модуле (на- пример, в независимой библиотеке С) не знают, что целые аргументы являются не простыми целыми значениями, а ссылками на них, тогда вызовы этих предикатов из других модулей могут быть некорректными. В результате такие предикаты могут вернуть неверные значения или сгенерировать ошибку во время исполнения, По- этому, по умолчанию компилятор генерирует сообщение об ошибке при попытке
Глава 17, Более сложные приемы программирования 399 использования базовых доменов как ссылочных. Генерацию этой ошибки компиля- тором можно отключить указанием опции командой строки -R (см. разд. "Visual Prolog Command Line Compiler" в интерактивной системе помощи). He следует отклю- чать эту проверку в проектах, вызывающих внешние глобальные функции, которые применяют базовые домены в аргументах, например, если ваш проект вызывает функции С из внешних библиотек. Попытки непосредственно использовать стан- дартный домен как ссылочный в проектах, основанных на VPI, обычно приводят к ошибкам во время исполнения. Следует всегда явно объявлять домены, которые могут стать ссылочными, в разделах domains. Это обязательно требуется в проектах, содержащих несколько модулей: если глобальные домены должны ссылаться на несвязанные значения, компилятор не допустит автоматического преобразования этих доменов в ссылочные. Глобальные домены и предикаты описаны ниже (см, разд. "Модульное программирование" данной главы). Замечание J Базовые домены file, reg, db_selector, bt_selector и place не могут стано- виться ссылочными. Ссылочные типы и массив trail Применение ссылочных типов вызывает ряд дополнительных проверок и унифика- ций и, в общем случае, увеличивает время выполнения программы. С другой сторо- ны, их использование облегчает решение целого ряда задач. При использовании ссылочных типов в Visual Prolog применяется массив trail. Он используется для запоминания, когда ссылочные переменные становятся связанны- ми. Это необходимо, поскольку при откате в точку между созданием и связыванием ссылочной переменной, последняя должна быть освобождена снова. Для обычных переменных такая проблема неактуальна, т. к. их точки создания и связывания оди- наковы. В массиве trail каждая запись занимает 4 байта (размер 32-битного указа- теля), однако использование массива оптимизировано: запись не будет в него поме- щена, если между созданием и связыванием переменной нет ни одной точки отката. Массив trail при необходимости автоматически увеличивает свой размер. Его мак- симальный размер составляет 64 Кбайт в 16-битных версиях Visual Prolog и практи- чески не имеет границ в 32-битных версиях. Применение ссылочных доменов Правильным является применение ссылочных доменов лишь в тех местах, где это действительно необходимо, во всех же остальных случаях следует использовать обычные домены. В Visual Prolog вы можете преобразовывать переменные ссылочно- го домена в переменные обычного домена везде, где это необходимо. Вы можете создать, например, предикат из единственного факта, который преобразует ссылоч- ный целый домен в обычный целый домен: domains refint = reference integer
400 Часть IV. Программирование на Visual Prolog predicates conv(refint,integer) clauses conv(X, X). Преобразование переменной из ссылочного домена в обычный происходит автома- тически, если переменная используется как со ссылочным, так и с обычным доме- нами, как это выполняется в предложении conv, в котором х преобразуется из refint в integer. Выше показан пример, где вам не нужно писать специального ко- да для преобразования из ссылочного в обычный домен. ( Замечание ) Ссылочной переменной должно быть присвоено фактическое значение прежде, чем переменная будет преобразована к обычному типу. Аналогично, если вы пытаетесь преобразовать переменную из одного ссылочного типа в другой (например, из reference integer в reference char), вы должны быть уверены, что переменная является связанной. Иначе будет выдано сообщение о том, что свободные переменные в данном контексте не разрешены. Обращайте внимание на автоматическое преобразование типов при получении но- вой свободной ссылочной переменной путем обращения к предикату free или соз- дании свободной переменной предикатом равенства equal (=). Заметьте, что когда компилятор Visual Prolog создает новую несвязанную переменную, ему необходимо знать, какому домену эта переменная принадлежит. Иначе компилятор рассматрива- ет эту переменную принадлежащей к первому найденному ссылочному домену; если же ни один из ссылочных доменов не объявлен, компилятор генерирует ошибку. (Обратите внимание, что подход к решению этой проблемы вполне может изменить- ся в будущих версиях Visual Prolog без какого-либо дополнительного уведомления об этом.) Итак, напишем: predicates р(refinteger) — (о) clauses Р(Х) Y - X, bind_integer(X), ... Создавая новую свободную переменную при помощи предиката равенства =, не сле- дует создавать несвязанную переменную посредством вызова предиката free с неиз- вестным доменом, например: goal free(X), bind_integer(X), ... % нельзя С помощью ссылочного типа вы можете получать значения переменных, которым эти значения будут присвоены несколько позже, а также создавать структуры, в ко- торых поля будут заполняться после их создания. Приведем пример (листинг 17.3) для того, чтобы поближе познакомиться с работой ссылочного типа: попробуйте ввести ряд целевых утверждений с применением уже знакомых предикатов member и append.
Глава 17. Более сложные приемыпрограммирования 401 Листинг 17.3. Программа сМОеОЗ.рго : hM''Ua«i""« «V Нм'МмгН ««>« Ыв«п« fvi *♦•»* *4 Ь»*'и .р'1'»И>|Г.Гм1г**'»‘ыьМма'.*'»М.«.*'гЧ>>«111к<>4«6ьНа11»1А.Н.^(Н1><а «-Чй ''rv'4i а»г **<-ъ*«а«**«^« **» v*a,X**4v*a*«« diagnostics domains refinteger = integer reflist = reference refinteger* predicates member(refinteger, reflist) append(reflist, reflist, reflist) clauses member(X,[X|_]). member(X,[_|L]):- member(X,L). append ( [] ,L,L) . append([X|LI],L2,[X|L3]):- append(LI, L2, L3). Загрузите программу, затем запустите ее с помощью Test Goal и введите следующие целевые утверждения: member(1,L). member(X,L), Х=1. member(1,L), member(2,L). X=Y,member(X,L),member(Y,L), X=3. member(1,L), append(L,[2,3],LI). append(L,L,LI), member(l,L). % Все списки с 1 % Аналогично % Начинается с 1 и содержит 2 % Начинается с X и содержит Y % Начинается с X и заканчивается [... 2,3] % Содержит 1 не менее двух раз Вы обнаружите, что полученные ответы совпадают с тем, что вы предполагали по- лучить. Ссылочные домены или поиск с возвратом Ссылочная переменная может быть несвязанной и при этом все же иметь значение во время ее использования в вызове предиката. В примере chlOeOl.pro (см. лис- тинг 17.1) это произойдет в том случае, если вы, например, захотите найти все стра- ны, названия которых совпадают с названиями их столиц: samecaps:- country(capital(С,С)), write(С,'\n’), fail. Здесь переменная с дважды используется как выходная, хотя в действительности в коде сказано, что две переменные в предикате capital должны иметь одинаковые значения, как только одна из них получит значение. Поэтому обе переменные соз- даются и связываются перед вызовом. Для этого их домен преобразуется в ссылоч- ный. Обе переменные во время вызова в действительности известны, тем самым, представляя входной поток параметров.
402 Часть IV, Программирование на Visual Prolog Внимание! Опасно позволять стандартным доменам становиться ссылочными. В приведенном выше примере нужно объявить подходящий ссылочный домен. Однако это приведет к дополнительным расходам ресурсов при доступе к предикату country, и, вероятно, более эффективным будет использование поиска с возвратом для нахождения особых случаев, когда названия страны и ее столицы совпадают. country(capital(Со,Са)), Со = Са, Верно это или нет, зависит от размера базы данных, количества вызовов, использо- вания аргументов после вызовов и т. д. Применение ссылочных типов при реализации бинарных деревьев Ранее было показано (см, гл. 13), как бинарные деревья могут применяться для реа- лизации быстрой и эффективной сортировки. Однако сортировка может быть реали- зована более элегантно с помощью ссылочных типов. Поскольку невозможно изме- нить листья дерева, когда им уже присвоены значения, возникает необходимость копирования узлов при создании дерева. Когда нужно отсортировать большой объем данных, это копирование может приводить к ошибке переполнения памяти. Ссы- лочный тип позволяет избежать переполнения за счет того, что листья деревьев ос- таются свободными переменными (куда впоследствии будут внесены поддеревья), и не придется копировать дерево в том месте, где будет вводиться новый узел. Рассмотрим, как работает предикат insert в программе chl0e04.pro при оценке це- левого утверждения. В этой программе (листинг 17.4) с помощью предиката insert формируется бинарное дерево на основе ссылочного домена tree. Й ма 1 ch Юе 04. pro diagnostics domains tree = reference t(val, tree, tree) val = string predicates insert(val, tree) clauses insert (ID,t (ID,_,_) ) . insert(ID,t(IDl,Tree,_)) ID<ID1, !, insert(ID,Tree). insert(ID,tTree)) insert(ID,Tree).
Гпава 17. Более сложные приемы программирования 403 goal insert("tom",Tree), insert("dick",Tree), insert("harry",Tree), write("Tree=”,Tree), nl, readchar(_). Первая подцель, insert ("tom",Tree), соответствует первому правилу и сложный объект, с которым связана Tree, принимает вид: t("tom", J Хотя последние два аргумента в t являются свободными, объект t передается даль- ше в следующую подцель: insert("dick", Tree) Это, в свою очередь, связывает Tree со значением: t("tom", t("dick", _, _) , J Наконец, с помощью подцели insert("harry", Tree) Tree получает значение t("tom", t("dick", t("harry", _, _)), _) что и является результатом выполнения всего целевого утверждения. Попытайтесь подробно проследить за этим процессом, используя отладчик Visual Prolog. Запустите отладчик из VDE командой Project | Debug. Когда появится окно отладчика, выберите в меню отладчика команду View | Local Variables, а затем ис- пользуйте команду Run | Trace Into для наблюдения за значениями переменных во время работы программы. Более подробные инструкции см. в разд. "Отладчик Visual Prolog" гл. 21. На вкладке Target диалогового окна Application Expert вам следует вы- брать следующие установки: Platform = Windows32, UI Strategy = Textmode. Сортировка с помощью ссылочных типов В этом разделе мы расширим предыдущий пример с бинарным деревом chl0e04.pro, чтобы показать, как можно использовать ссылочные домены и преобразовывать переменные из ссылочного в обычный домен. Следующий пример (листинг 17.5) определяет предикат, который способен сортировать список значений. ^Листинг 17.5 Программа ch10e05.pro lijit . .....'»).4^1 4.4.44 4 4 4 4 4 4 .4 4 4'4. 4 | 4.4 * -4 ..... 4.4.4.4... .1 ))4,)4')11< ...... .44 .*4 .4 .4 4 * ...4 4 * *4 4 4 4 * * < 4.4.4.4 4 )'4 4 .4 44) 4 .....4 .44,1 4 4 4'4 4 .4 4 4 4 4 4 4 4 4 ) ) ) ) . 4.) < .. .. > > «> ) 4 .1 diagnostics domains tree ~ reference t(val, tree, tree) val = integer list - integer*
404 Часть IV. Программирование на Visual Prolog predicates insert(integer,tree) instree(list,tree) nondeterm treemembers(integer,tree) sort (list,list) clauses insert(Vai,t(Val,_,_)):-!. insert(Vai,t(Vail,Tree,_)):- VaKVall, !, insert(Vai,Tree). insert(Vai,t(_,_,Tree)):- insert(Vai,Tree). instree([],_) . instree([HIT],Tree):- insert(H,Tree), instree(T,Tree). treemembers(_,T):- free(T),!,fail. treemembers (X, t (_, L,_) ) : - treemembers(X,L). treemembers (X, t (Refstr,_,_) ) :- X = Refstr. treemembers (X, t (_,_, R)) :- treemembers (X, R) . sort (L,LI) : - instree(L,Tree), findall(X,treemembers(X,Tree),LI) . goal sort([3,6,1,4,5], L) , write("L-",L),nl. В этом примере обратите внимание на то, что ссылочный тип используется только в дереве. Все остальные аргументы относятся к обычному типу. В окне Messages среды визуальной разработки будет отображен результат диагностики: REFERENCE DOMAINS tree val Функции и возвращаемые значения Синтаксис Visual Prolog позволяет рассматривать предикаты как функции, имеющие возвращаемое значение, в отличие от обычных предикатов, использующих выходные аргументы. Но разница здесь не только синтаксическая. Так как возвращаемые зна-
Гпава 17. Более сложные приемы программирования 405 чения хранятся в регистрах, функции Пролога могут как возвращать значения в дру- гие языки, так и принимать возвращаемые из других языков значения (подробнее см. гл. 24). Объявление функции выглядит аналогично объявлению обычного предиката, только перед именем функции указывается возвращаемый домен: predicates unsigned triple(unsigned) Предложения для функции должны иметь дополнительный последний аргумент для сопоставления с возвращаемым значением при успехе: clauses triple(N,Tpl):- Tpl - N*3. goal TVal = triple(6), write(TVal). Возвращаемое значение может принадлежать любому, не обязательно стандартному, домену. Если вы объявляете функцию, не принимающую аргументов, нужно вызывать ее с пустой парой скобок, чтобы отличать ее от строкового символа. Функция в приме- ре возвращает час дня: predicates unsigned hour() clauses hour(H):- time (H,. ее правильно вызывать так: . . .f Hour = hour()f ... а неправильно — так: . . ., Hour = hour, ... В последнем случае hour будет рассматриваться как текстовая строка "hour", и ком- пилятор выдаст ошибку типа при попытке использовать hour. Также рекомендуем использовать пустую пару скобок в объявлении функций и пре- дикатов, не имеющих аргументов. Иначе произойдет синтаксическая ошибка, т. к. компилятор не поймет, что является именем предиката, а что — именем домена. Если, к примеру, у вас есть домен с именем key, а также предикат с именем key, тогда объявление: predicates key mypred можно интерпретировать двумя способами: □ как объявление предиката с именем key и предиката с именем mypred; □ как объявление предиката с именем mypred, возвращающего key.
406 Часть IV. Программирование на Visual Prolog Если же вы напишете predicates key О mypred() то неоднозначности не будет. ( Замечание J) Когда предикат объявлен функцией, возвращающей значение, его нельзя вызывать как обычней предикат Пролога, используя дополнительный аргумент в качестве вы- ходного аргумента. В этом случае его нужно вызывать как функцию. Причина в том, что функции сохраняют свои возвращаемые значения в регистрах, а это означает, что код, скомпилированный до и сразу после вызова функции, отли- чается от кода, окружающего вызов обычного предиката. По той же причине функ- ции, вызывающие самих себя, не позволяют оптимизировать хвостовую рекурсию. Например, если вы пишете функцию neg для отрицания каждого элемента в списке: domains ilist = integer* predicates ilist neg(ilist) clauses neg ([],[]). neg([Head[Tail]f [NHead|NTail]);- NHead = -Head, NTail = neg(Tail), Это не будет оптимизированная хвостовая рекурсия, в то время как neg в качестве предиката: domains ilist = integer* predicates neg(ilist,ilist) clauses neg ([],[]). neg([Head I Tail], [NHead|NTail]):- NHead = -Head, neg(Tail,NTail). Это оптимизированная хвостовая рекурсия, поэтому к использованию функций сле- дует подходить в известной мере осторожно. Их главная цель — предоставить воз- можность получать и возвращать значения из процедур, написанных на других языках.
Гпава 17. Более сложные приемы программирования 407 Отметим, что функции с арифметическими возвращаемыми значениями должны быть детерминированными, если они используются в арифметических выражениях. Управление детерминизмом Visual Prolog Большинство языков программирования являются детерминированными. Это озна- чает, что любое множество входных значений вызывает единственно возможное множество команд, используемых для определения выходных значений. Например, в языке С вызванная функция может выдать лишь единственное множество выход- ных значений, тогда как Visual Prolog, напротив, поддерживает недетерминирован- ный вывод, основанный на недетерминированных предикатах. Целью управления детерминизмом является сокращение используемой памяти и времени выполнения. Фактически, когда детерминированное предложение заверша- ется успешно, соответствующее пространство стека может быть отдано сразу же, тем самым освобождая занятую память. Существует множество причин, по которым программисту следует интересоваться детерминизмом, в частности вопросы оптими- зации программ. Visual Prolog обладает своей строго типизированной системой детерминизма и сис- темой его проверки, которая заставляет программиста объявлять следующие два аспекта поведения предикатов (и фактов): □ может ли вызов предиката завершиться неуспешно; □ какое количество решений предикат может выдать. В терминах исполнения программ Visual Prolog режим детерминизма определяет следующие свойства поведения предикатов: □ может ли предикат быть неуспешен? (Fail — F)\ □ может ли предикат быть успешен? (Succeed — S)\ □ установит ли Visual Prolog точку возврата (отката) на вызов этого предиката? (BacktrackPoint — ВР). В табл. 17.1 приведены режимы детерминизма предикатов, которые поддерживает Visual Prolog. Таблица 17.1. Режимы детерминизма предикатов Количество решений, которые предикат может выдать 0 1 >1 Не может быть erroneous procedure multi неуспешен {} {S} {S, BP} Может быть Failure determ nondeterm неуспешен {F} {F, S} {F, S, BP} Используя ключевые слова из этой таблицы в объявлениях предикатов и предикат- ных доменов, программист может объявить для предикатов шесть различных режи- мов детерминизма.
408 Часть IV. Программирование на Visual Prolog □ multi {Succeed, BacktrackPoint} Ключевое слово multi определяет недетерминированные предикаты, которые мо- гут совершать откаты назад и генерировать множественные решения. Предикаты, объявленные с ключевым словом multi, не могут быть неуспешны и всегда вы- дают как минимум одно решение. □ nondeterm {Fail, Succeed, BacktrackPoint} Ключевое слово nondeterm определяет недетерминированные предикаты, которые могут совершать откаты назад и генерировать множественные решения. Предика- ты, объявленные с ключевым словом nondeterm, могут быть неуспешны. □ procedure {Succeed} Ключевое слово procedure определяет предикаты, называемые процедурами. Про- цедуры всегда успешны и не порождают точек отката. Процедуры всегда имеют одно и только одно решение (но возможны ошибки во время исполнения). Компилятор всегда проверяет и выдает предупреждения о недетерминированных предложениях в процедурах. Компилятор по умолчанию проверяет и выдает ошибку, если он не может гаран- тировать, что процедура никогда не завершится неуспехом. □ determ {Fail, Succeed} Ключевое слово determ определяет детерминированные предикаты, которые мо- гут быть успешными или неуспешными, но не могут иметь точек отката. Таким образом, предикат, объявленный с ключевым словом determ, имеет не более од- ного решения. Когда предикат объявлен с ключевым словом determ, компилятор всегда проверяет и выдает предупреждение, если в предикате есть недетермини- рованные предложения. Ключевое слово determ также употребляется в объявле- ниях предикатов баз данных в разделах фактов. О erroneous {} Предикат, объявленный с ключевым словом erroneous, не порождает решения и не может закончиться отказом (fail). Visual Prolog предоставляет встроенные erroneous предикаты exit/0, exit/1, errorexit/O и errorexit/1. Они имеют эффект ошибки во время выполнения. Это означает, что если вызов такого предиката окружен предикатом trap, то вы- зов активизирует этот trap (обратите внимание, что в VP1 каждый обработчик события окружен внутренним trap). О failure {Fail} Предикат, объявленный с ключевым словом failure, не порождает решения, но может завершаться отказом (неуспешно — fail).
Гпава 17. Более сложные приемы программирования 409 Самым обычным примером предикатов failure является встроенный предикат Если предикат объявлен с ключевым словом failure, компилятор по умолчанию проверяет и выдает предупреждение о возможных недетерминированных предло- жениях в этом предикате. Вызов failure предиката принуждает программу запустить поиск с возвратом к ближайшей точке отката или прервать программу с эффектом, идентичным ошибке на этапе выполнения. Следующий пример демонстрирует различие меж- ду предикатами failure и erroneous: predicates failure failure_l(integer) erroneous erroneous 0() clauses erroneous 0() exit(). % Этот предикат всегда успешен % Этот предикат не всегда успешен erroneous 0(). Заметьте, что все стандартные предикаты Visual Prolog имеют внутреннее определе- ние режима детерминизма nondeterm, multi, determ, procedure, failure или erroneous. Применяя эту классификацию к объявлениям предикатов баз данных, которые мож- но объявить в разделе фактов, получим такие варианты решений (табл. 17.2). Таблица 17.2. Режимы детерминизма фактов Количество возможных решений 0 1 >1 Не может завершиться неуспешно 1 1 single {S} — Может завершиться неуспешно determ (F, S} Nondeterm {F, S, BP} Используя ключевые слова из табл. 17.2 в объявлениях фактов, можно объявить три различных режима детерминизма для фактов (предикатов баз данных). □ nondeterm {Fail, Succeed, BacktrackPoint} Предикат nondeterm определяет, что база данных может содержать любое количе- ство фактов для предиката базы данных. Этот режим — режим детерминизма по умолчанию для предикатов баз данных.
410 Часть IV. Программирование на VisualProlog □ determ {Fail, Succeed} Предикат determ определяет, что база данных в каждый момент может содержать не более одного факта для предиката базы данных, объявленного с ключевым словом determ. □ single {Succeed} Предикат single определяет, что база данных всегда содержит один и только один факт для предиката базы данных, объявленного с ключевым словом single. В табл. 17.2 терм “Не может завершиться неуспешно" в отношении к single- фактам означает, что, будучи вызванным со свободными аргументами, single-факт базы данных всегда выдает решение (завершается успешно). Система проверки режимов детерминизма Visual Prolog Visual Prolog предлагает уникальные возможности управления детерминизмом, осно- ванные на объявлениях режимов детерминизма предикатов и фактов. Все стандарт- ные предикаты Visual Prolog определены как nondeterm, multi, determ, procedure, failure или erroneous. По умолчанию, компилятор проверяет предложения предикатов и вычисляет режи- мы детерминизма всех определенных пользователями предикатов; компилятор выда- ет предупреждения/ошибки, если не может гарантировать, что предикат соответству- ет объявленному режиму детерминизма. □ По умолчанию, компилятор проверяет определенные пользователем предикаты, объявленные со словами determ, procedure, failure или erroneous, и выдает предупреждения для предложений, которые могут дать в результате недетермини- рованные предикаты. Существует два вида недетерминированных предложений: • если предложение не содержит отсечение и существует более одного предло- жения, которые могут соответствовать одинаковым значениям входных аргу- ментов для некоторого потока параметров; • если предложение вызывает недетерминированный предикат и за этим вызо- вом предиката не следует отсечение. Из-за второй причины недетерминизм имеет тенденцию быстро распространять- ся по программе, если его не пресечь (буквально) одним или более отсечениями. □ По умолчанию компилятор проверяет определенные пользователем предикаты, объявленные с ключевыми словами procedure, multi и erroneous, и выдает пре- дупреждения/ошибки, если не может гарантировать, что предикат никогда не за- вершается отказом (fail). Внимание! Компилятор может проверять только необходимые условия для неуспешного за- вершения (но не необходимые и достаточные).
Гпава 17. Более сложные приемы программирования 411 Следовательно, компилятор может иногда генерировать предупрежден ия/ошибки для предикатов • (объявленных с ключевыми словами procedure, multi и erroneous), которые, фактически, никогда не завершатся неуспехом. Например: domains charlist = char* predicates procedure str_chrlist(string,charlist) — (i,o) clauses str_chrlist[]). str__chrlist (Str, [H | T]) : - frontchar(Str,H,Strl), str__chrlist (Strl,T) . Предикат front char может завершиться неуспехом, если первый параметр str является пустой строкой. Компилятор не настолько '‘умен”, чтобы обнаружить, что str во втором предложении предиката str_chriist не может быть пустой строкой. Для этого примера компилятор сгенерирует предупреждение типа ’’Possibility for failure in a predicate declared as procedure, multi or erroneous ” (Воз- можность неуспешного завершения в предикате, объявленном как procedure, multi ИЛИ erroneous). Проверку режима детерминизма пользовательских предикатов можно отключить, сняв флажок Check Type of Predicates (в диалоговом окне VDE Compiler Options), или опцией компилятора командной строки -upro-, но это опасный стиль програм- мирования. Вместо этого мы рекомендуем вам изменить код. Так, в этом примере вы можете изменить порядок предложений: str_~chrlist(Str,[Н|Т]):- frontchar(Str,Н,Strl) , I, str__chrlist (Strl, T) . str_chrlist(_,[]):-!. Объявление процедур позволяет избежать многих маленьких ошибок, например, забывания последнего "ловушечного" предложения, "ловящего” все неучтенные в основных предложениях случаи. Существует два правила, которыми вы должны пользоваться при написании преди- катов, объявленных с ключевыми словами multi, procedure или erroneous. □ Если какое-либо из предложений предиката может завершиться неуспехом, то последнее "ловушечное" предложение должно быть определено в предикате, как в примере для str_chrlist. □ Для любого возможного (в соответствии с объявленными доменами) множества входных аргументов должно существовать предложение, имеющее голову, кото- рая сопоставляется этому множеству, иначе компилятор сгенерирует предупреж- дение.
412 Часть IV. Программирование на Visual Prolog Например, в следующем отрывке третье предложение предиката р может быть поте- ряно, если предикат объявлен без ключевого слова procedure, но компилятор обна- ружит это, если предикат объявлен как procedure: domains boolean = integer % b_True = 1, b_False = 0 predicates procedure p(boolean) clauses p(b^False):- I, ... . p(b_True): — I , ... . p(_): — dlg_error("An illegal argument value”). Заметьте, что компилятор обрабатывает erroneous предикаты особым способом, предоставляя возможность использовать их в последних "ловушечных" предложениях (для обработки ошибочных ситуаций) в предикатах других типов. Например, "лову- шечное" предложение в предыдущем примере можно переписать так: p(_J : — errorexit (error_vpi_package__bad_data) . Предикаты как аргументы До сих пор мы обсуждали только статические вызовы предикатов. Это означает, что предикаты, вызываемые как подцели, определены статически в исходном коде. Од- нако во многих случаях, во избежание больших объемов копируемого кода, жела- тельно вызывать из одного и того же места различные, в зависимости от предыду- щих событий и вычислений, предикаты. Для этого Visual Prolog поддерживает поня- тие предикатных значений. Вы можете объявить предикатный домен и передавать предикатные значения (указатели на предикаты) этого домена как переменные. Основное использование этой возможности в Visual Prolog — передача предикатов- обработчиков событий. Предикатные значения Предикатные значения ~ это предикаты, которые можно рассматривать как значения в том смысле, что их можно: □ передавать в качестве параметров и возвращать из предикатов и функций; □ хранить в фактах; □ хранить в переменных; □ сравнивать на идентичность. Естественно, как и "простые" предикаты, предикатные значения могут быть вызваны с соответствующими аргументами. Предикатные значения объявляют как экземпляры предикатных доменов. Если вы объявили предикатный домен (например, pred_Dom) в разделе доменов: domains pred_dom ~ procedure (integer, integer) — (if о)
Глава 17. Более сложные приемы программирования 413 то можете объявить одно или более предикатных значений, принадлежащих этому домену. Синтаксис объявления предикатных значений таков: predicates predValue_l: pred_Dom predValue_2: pred_Dom Здесь predValue_l, predValue_2 — это имена предикатных значений, a predj)om — это предикатный домен, объявленный в разделе доменов. Этот предикатный домен pred_Dom затем можно задать как домен для аргументов других предикатов. На- пример: predicates variantjprocess (pred_Dom PredName, integer InVar, integer OutVar) Можно передать эти предикаты (предикатные значения) predValue_l, predvalue_2 как значения аргумента PredName в предикат variant_process. Предикат variantjprocess тогда сможет совершать "векторный" вызов. Предикатные значения можно использовать как любые другие элементы программы. В частности, они могут появиться как части сложных термов, создавая объектно- ориентированные возможности, где каждый объект несет с собой набор подпро- грамм для управления самим собой. Предикатные значения, однако, отличаются от остальных значений Visual Prolog в следующих аспектах: □ для предикатных значений не существует констант; □ предикатные значения не имеют постоянного представления (текстовое пред- ставление предикатного значения — это просто шестнадцатеричное число, т. е. значение указателя на адрес памяти). Замечание J Предикатные значения представляют собой низкоуровневый механизм. Фактическое значение такого предикатного значения — это просто адрес, и поэтому оно дейст- вительно только в той программе, где было создано. Таким образом, вы можете хранить и получать предикатные значения через факты баз данных, но если вы попытаетесь использовать предикатное значение, порож- денное не в текущей программе, то могут произойти самые непредсказуемые по- следствия. Предикатные значения используются по-разному. Одно из наиболее важных приме- нений предикатных значений — это предикаты (функции) обратного вызова. Предикат обратного вызова — это предикат, используемый для обратных вызовов из некоторого используемого объекта к пользователю этого объекта. Например: □ обратный вызов от сервера к клиенту; □ обратный вызов от сервиса к пользователю этого сервиса; □ обратный вызов из подпрограммы к пользователю этой подпрограммы.
414 Часть IV. Программирование на Visual Prolog Обратные вызовы хорошо использовать дня одной или обеих следующих целей: □ управление асинхронными событиями; □ предоставление динамической (продвинутой) параметризации. При работе с асинхронными событиями программа регистрирует обратный вызов с некоторым источником события. Затем этот источник события активизирует об- ратный вызов, когда бы ни произошло событие. "Данные готовы" — типичный при- мер такого асинхронного события при асинхронном взаимодействии. Другой весьма типичный пример — это обработчик событий Windows. В качестве примера динамической параметризации возьмем инструмент, который создает определенный тип окна. Пусть это окно имеет возможность изменять форму своего курсора (указателя мыши), когда тот входит в определенные участки окна. Оно должно работать в разных ситуациях, и потому не может знать, какой курсор использовать в каких участках окна. Фактически, выбор курсора зависит от многих факторов, о которых окно не имеет "понятия". Поэтому окно "оставляет" право вы- бора курсора программе, и делает это посредством вызова предиката обратного вы- зова. Через него окно "спрашивает" окружающую программу, какой использовать курсор, когда мышь входит в определенный участок. Поскольку окно совершает та- кой вызов каждый раз, когда мышь заходит в определенный участок окна, ему не нужно всякий раз получать один и тот же курсор, выбор курсора может динами- чески зависеть от факторов, внешних для окна. Предикатные домены Форма объявления предикатных доменов такая: [global] domains PredDom = DetermMode [ReturnDom] (ArgList) [- [FlowPattern]] [Language] Здесь PredDom объявляет имя предикатного домена, DetermMode задает режим детер- минизма одним из следующих ключевых слов: {procedure | determ I nondeterm | failure I erroneous I multi} Помните, что режим детерминизма должен быть задан в объявлениях предикатных доменов. Далее, ReturnDom определяет домен возвращаемого значения, если вы объявляете предикатный домен для функций. ArgList определяет домены аргументов в форме: [ arg_l [, arg_2 ]* ] Здесь arg_N имеет форму: Domain_Name [ Argument_Name ] Domainjiame может принадлежать любому стандартному или пользовательскому до- мену. Имя аргумента Argument_Name (если оно задано) компилятор игнорирует. Внимание! Скобки, окружающие список аргументов ArgList, необходимо давать всегда, даже если ArgList пуст.
Гпава 17. Более сложные приемы программирования 415 Flowpattern имеет форму: (flow [, flow ]*) где flow есть { i | о I functor FlowPattern I listflow }, a listflow есть '[' flow [f flow ] * [ 1 I ’ { i I о ( listflow } ] ’]' Символ (-) перед FlowPattern обязателен (если FlowPattern задан). Поток параметров Flowpattern определяет, как использовать каждый из аргумен- тов. Он должен быть буквой i для входного аргумента и о — для выходного. Функтор и поток параметров нужно указывать для сложных термов (например, (i, о, myfunc(i,i), о) ИЛИ listflow ([i, myfunc (i, о), о])). Внимание! —rJ Можно указывать только один поток параметров. Если он не указан явно, то неявно принимается поток параметров по умолчанию со всеми входными аргументами. Заметьте, что такой поток параметров по умолчанию может стать причиной сообще- ний об ошибках типа "This flow pattern does not exist*' (Такой поток параметров не существует). Language имеет форму: language { pascal I stdcall | asm I с I syscall I prolog} Спецификация language сообщает компилятору, какое использовать соглашение о вызовах. Она требуется только тогда, когда предикатные значения, которые объяв- лены как экземпляры предикатного домена, будут переданы в подпрограммы, напи- санные на других языках (например, С, Delphi и др.). Соглашение о вызовах по умолчанию — pascal. Заметьте разницу с объявлениями предикатов, где по умолчанию используется prolog. Ограничение Предикатные значения, имеющие соглашение о вызовах prolog, нельзя вызывать из переменных. То есть, если такое предикатное значение передается в перемен- ной, а полученное предикатное значение вызывается с соответствующими аргумен- тами, то компилятор генерирует ошибку. Здесь мы используем: □ квадратные скобки Для указания необязательных элементов и фигурные скобки для указания того, что один из элементов, разделенных символами |, необходимо использовать; □ пару одинарных кавычек для указания того, что символ, окруженный ими, а именно • I ’, ’ [ ’ и 1 ] является частью синтаксиса Visual Prolog; □ символ звездочка (*) для указания произвольного количества непосредственно предшествующих элементов (ноль или более раз).
416 Часть IV. Программирование на Visual Prolog Сравнение с объявлением предикатов В противоположность объявлениям предикатов, в объявлениях предикатных доменов: П может быть определен только один поток параметров; П если поток параметров не задан явно, то неявно принимается поток по умолча- нию со всеми входными аргументами; П режим детерминизма DetermMode всегда нужно задавать перед списком аргумен- тов ArgList или перед возвращаемым доменом ReturnDom (в объявлениях преди- катных доменов для функций). Режим детерминизма нельзя определить (переоп- ределить) перед потоком параметров; П скобки списка аргументов нужно давать всегда (даже если список пуст); П соглашение о вызовах для предикатных доменов по умолчанию pascal, в то вре- мя как для предикатов по умолчанию используется prolog. Приведем несколько примеров. Объявление предикатного домена для детерминированных предикатов (функций), принимающих целое (integer) значение в качестве входного аргумента и возвра- щающих целое значение: domains list_process = determ integer (integer) — (i) Этот предикатный домен теперь называется list_process. Чтобы объявить функцию square, принадлежащей этому предикатному домену, используется синтаксис: predicates square: list_process Предложение для square похоже на обычное предложение, но т. к. оно объявлено как функция, ему нужно возвращаемое значение: clauses square(E,ES):- ES = E*E. Развивая вышесказанное, объявление предикатного домена ilist_p для детермини- рованных предикатов, принимающих в качестве входных аргументов список целых (ilist) и предикатное значение (предикатного домена list_process), а также воз- вращающих список целых, может быть таким: domains ilist = integer* list_jprocess = determ integer (integer) — (i) ilist_p = determ (ilist,list_process,ilist) — (i,i,o) Теперь рассмотрим программу сЫОеОб.рго (листинг 17.6). • »ii *' »** ii *:**i iii'tii'i **'irt'*.**'i'***i*i* ,'if »»»*****•<*<* 'Л «•ii <*if <*Ho M » ii iff*>•* nf# »#il. ji. ** *'*> • • • ЛиЙИгий<ii • i r* < * * * • t «• »*• * • r ,'* ^ ***•*••**•* • * «¥* »»»»«»»» tn H M i i'* ** ***** ** domains ilist = integer*
Гпава 17. Более сложные приемы программирования 417 list_process = determ integer (integer) — (i) ilist—p = determ (ilist,list_process,ilist) — (i,i,o) predicates list—square: list_process list_cube: List_process il_process: ilist_p clauses list_square(E,ES):- ES = E*E. list__cube (E, EC) : - EC = E*E*E. iljprocess([]/_/[])• il—process([Head I Tail],L~Process, [P_HeadIP_Tail]):- P—Head = L—Process(Head), il process(Tail,L Process,? Tail). goal List = [-12,6,24,14,-3], il_process(List,list_square,P_Listl), write("P—Listl = ",P—Listl,'\n'), il_process(List,list—cube,P_List2), write("P—List2 = ", P_List2, ' \n') • Здесь объявляют две функции — list_square и list_cube, принадлежащие преди- катному домену list-process, — и предикат il_process, создающий новый список целых путем применения предиката, обрабатывающего элемент списка (он передает- ся как предикатное значение в аргумент L-Process) к каждому элементу первона- чального списка. Объявление домена iiist_p представлено только для иллюстрации; il_process также можно было бы объявить, используя: predicates il_process(ilist,list_process,ilist) поскольку на него нет ссылки, как на переменную. В показанной цели предикат ii_process вызывают дважды: сначала при создании списка квадратов, применяя функцию list square, а затем при создании списка кубов, используя функцию list cube. Скомпилируйте и запустите эту программу, вы получите: P-Listl = [144,36,57 6, 196,9] P_List2 = [-1728,216,13824,2744,-27] Другая программа ch!0e07.pro (листинг 17.7), представляющая собой усовершенство- ванную программу сЫОеОб.рго, иллюстрирует использование списков предикатных значений. domains ilist ~ integer*
418 Часть IV. Программирование на Visual Prolog list—process - determ integer (integer) — (i) listj?_list == list_jprocess* elem_process = determ (integer,integer,integer) — (i,i,o) elemj?—list - elem_process* predicates list_same: list—process list—square: list—process list_cube: list_jprocess elem_add: elemjprocess e 1 em_ma x: el em__p г о c e s s elemjnin: eleiri—process il—process(ilist,list—process, ilist) il—post—process(ilist,elemjprocess,integer) apply_elemprocess(ilist,elem_p_list) apply_listprocess (ilist, list_jp_list, elem_p_list) string Ipname(list_process) string epname(elem_process) clauses Ipname(list—same,list_same). % Отображение предикатных значений Ipname(list—square,list—square). % на функторы предикатов Ipname(list—cube,list—cube). epname(elem_add,elem_add). epname (elem—min, eleiri—min) . epname (eleiri—max, eleni—max) . elem_add (El, E2, E3) E3 = E1+E2. elem_max(El,E2,El)El >= E2, !. elem_max(_,E2,E2). elem_min(El,E2,El)El <= E2, !, elem_min(_,E2,E2). list—same (E,E). list—square(E,ES):- ES - E*E. list—cube(E,EC):- EC = E*E*E. il_process([],_,[]). il_process([Head|Tail],E_Process,[P—Head IP—Tail]) P_Head = E—Process(Head), il_process(Tail,E_Process,P_Tail). il_post_process([E],_,E):-!. il_post—process([H|T],L_Process,Result) i 1—post__process (T, L—Process, R1) , L—Process (H, Rl, Result) . apply_elemprocess(_,[]). apply—elemprocess(P—List, [E_Process IE_Tail]) rl—post—process(P—List,E_Process,PostProcess),
Глава 17. Более сложные приемы программирования 419 NE_Process = epname(E_Process), write(HE_Process,": Result = ”, PostProcess, 1\n, apply_elemprocess(P_List,E_Tail). apply_listprocess (_, [],__) . apply__listprocess (I_List, [L_Process IL_Tail],E_List) il_process (I-List, L_Process, P_List) ( HL_Process = Ipname(L_Process), write('\nNL_ProcessXnProcessed list = ",P_List, "\nPost-processing with:\n"), apply__elemprocess (P_List, E_List) , apply_listprocess (I_List, L_Tail, E_List) . goal List = [-12,6,24,14,-3], write("Processing ",List," using:\n"),nl, apply_listprocess(List, [list_same,list_square,list_cube] , [elem_add', elem_max, elem_min] ) . Если вы запустите ее, то получите на выходе следующее: Processing [-12,6,24,14,-3] using: list_same: Processed list = [-12,6,24,14,-3] Post-processing with: elem_add: Result =29 elem_max: Result =24 elem_min: Result = -12 list_square: Processed list = [144,36,576,196,9] Post-processing with: elem_add: Result = 961 elem_max: Result = 576 elem_min: Result = 9 list_cube: Processed list = [-1728,216,13824,2744,-27] Post-processing with: elem_add: Result = 15029 elemjnax: Result = 13824 elem_min: Result = -1728 Бинарные домены Visual Prolog имеет специальный бинарный домен для хранения двоичных данных, так же как и специальные предикаты для доступа к индивидуальным элементам би- нарных термов. Бинарные термы в основном используются для хранения данных,
420 Часть IV. Программирование на Visual Prolog которые не имеют другого допустимого представления, таких как растровые изобра- жения и другие произвольные блоки памяти. Существуют предикаты для чтения бинарных термов из файлов и записи их в файлы. Это будет обсуждаться в гл. 19. С помощью встроенного предиката преобразования term bin преобразование из би- нарных термов в термы Пролога осуществляется элементарно. Отличается простотой и обработка бинарных элементов передаваемых и возвращаемых из других языков. Также просто и эффективно имплементируются массивы. Бинарные элементы — это низкоуровневый механизм, его основная цель — обеспе- чить простое и эффективное взаимодействие с другими, нелогическими объектами и другими языками; они ведут себя иначе в отношении поиска с возвратом, нежели остальные термы Пролога. Бинарные элементы освободятся, если вернуться к точке, предшествующей их созданию, но даже если вы не вернетесь к настолько удаленной точке, изменения, произведенные над термом, не исчезнут. Мы проиллюстрируем это на примере в конце этого раздела. Реализация бинарных термов Бинарный терм — это всего лишь последовательность байт, которой предшествует слово (на 16-битных платформах) или двойное слово (на 32-битных платформах), в котором хранится ее размер. Терм бинарного типа (переменная, переданная в вызове функций на другом языке) указывает на фактическое содержание, но не на размер. Поле размер включает про- странство, занятое самим полем рис. 17.1. Размер Байты Указатель Рис. 17.1. Бинарный терм На бинарные термы накладывается обычное 64 Кбайт ограничение размера (на 16-битных платформах). Текстовый синтаксис бинарных термов Бинарные термы можно читать и писать в текстовом формате, а также определять (в текстовом формате) в исходном коде Visual Prolog. Их синтаксис такой: $ [Ы/Ь2, . . . ,bn] Здесь ы, Ь2 и др. — это индивидуальные байты терма. Когда бинарный терм опре- делен в исходном коде в программе, байты можно записать, используя любой под- ходящий беззнаковый целый формат: десятичный, шестнадцатеричный, восьмерич- ный или символьный. Однако текстовое представление бинарных термов, созданных и преобразованных во время выполнения программы, обязательно шестнадцате- ричное, без предшествующих индивидуальным байтам "Ох". Программа ch!0e08.pro (листинг 17.8) иллюстрирует это.
Глава 17. Более сложные приемы программирования 421 ****** •»!♦»»** » »»•••»•»*•• |Щ1Ш .»« ..Ui.UuiioH »Й»4 <•»»*< »*?*”4?*»>**«М*“«***»**,* .. » »* * » » f 1 •» » * » » 1 » » » ». 1 1 « ». 1 » * » « 1 »» » )« ».i f « *1 »« *» й »» й » Й «.« »«"»>*«»>" I'«*. I »**«>» * i i i » i »*».» i *!►»•* i » i i * ».P *.*.»• i.* i Листинг 17.8. Программа ch10e08.pro ( | H »M »»»• | > »i« »> > »l > > > »l | > »M ) »l H »»| I | i H H | I I »| Н н I »l | «Н (I ‘ »» »“ Ы ‘ H < III < н »al » , h ‘ Ml i (I H »» it »»ll4l|H||ll||»lll|HH'ni|tll||lil|l|l||l »'l |Hal I »l |H| I*|| (*(»» goal write("Text form of binary term: "f $ Г'в',105,0ol54,0x73, ’e’,0],’\n') . Загрузите и запустите эту программу, и Visual Prolog ответит: Text form of binary term: $[42, 69,6C,73, 65,00] Таким образом, вам нужно быть внимательным при использовании предиката readterm для чтения бинарных термов во время выполнения программы. Создание бинарных термов Ниже мы приведем стандартные предикаты Visual Prolog для создания бинарных термов. Предикат makebinary/1 Этот предикат создает и возвращает бинарный терм с заданным числом байт и уста- навливает его содержимое в бинарный нуль. . . ., Bin ~ makebinary(10), ... Число байт должно быть чистым размером, не включающим в себя размер поля раз- мера. Предикат makebinary/2 Этот предикат также имеет двухарную версию, позволяющую задать размер эле- мента. . .., USize = sizeof(unsigned), Bin = makebinary(10,USize), ... Этот код создает бинарный терм с размером, заданным числом элементов (10, как в приведенном выше примере, умноженным на размер элемента sizeof (unsigned)) и устанавливает его содержимое в нуль. Предикат composebinary/2 Этот предикат создает бинарный терм из существующих указателя и длины. Он по- лезен при преобразовании указателей в произвольные блоки памяти, возвращаемые функциями других языков. Предикат composebinary принимает два аргумента, а воз- вращает бинарный терм. . .., Bin = composebinary(StringVar,Size), ... Предикат composebinary принимает копию StringVar в качестве входного аргумен- та, так что изменения созданного бинарного терма Bin не затронут StringVar, и на- оборот. Предикат getbinarysize/1 Этот предикат возвращает размер (в байтах) бинарного терма, не включающий в себя размер поля размера перед данными. ..., Size = getbinarysize(Bin)f ...
422 Часть IV. Программирование на Visual Prolog Доступ к бинарным термам Существует восемь предикатов для доступа к бинарным термам: четыре для установ- ки данных и четыре для получения данных. Обе группы совершают проверку диапа- зона, основанную на размере бинарного терма, заданном индексе и размере требуе- мого элемента (byte, word, dword или real). Попытка получить или установить эле- менты вне диапазона бинарного терма является ошибочной. Обратите особое внимание на то, что индексы (номера элементов) начинаются с 0; первый элемент бинарного терма имеет индекс о, а последний элемент A-элемент- ного бинарного терма имеет индекс N-1. Предикаты get*entry/2 Это один из предикатов getbyteentry, getwordentry, getdwordentry или getrealentry, получающих и возвращающих заданный элемент как byte, word, dword или real соответственно. . .., SomeByte = getbyteentry(Bin,3), ... Предикаты set*entry Эти предикаты дополняют get*entry, они устанавливают заданный элемент byte, word, dword или real. ..., setbyteentry(Bin,3,SomeByte), ... Унификация бинарных термов Бинарные термы можно унифицировать, как любые другие термы в головах предло- жений, или используя предикат равенства equal (=): ..., Bini = Bin2 f . . . Если какой-либо из термов свободен во время унификации, термы будут сопостав- лены и станут указывать на один и тот же бинарный объект. Если оба терма связаны во время сопоставления, они будут проверены на равенство. Сравнение бинарных термов Результатом сравнения двух бинарных термов будет: □ если термы имеют различный размер, то большим считается тот, у которого раз- мер больше; □ если термы имеют одинаковый размер, то сравниваются побайтно, как беззнако- вые значения; сравнение прекращается, когда найдутся два различных байта, и результат их сравнения будет результатом сравнения бинарных термов. Например, $[1,2] больше, чем $[100], и меньше, чем $[1,3]. Рассмотрим пример, представленный в программе сЫ0е09.рго (листинг 17.9). Он демонстрирует особенности работы с бинарными термами.
Гпава 17. Более сложные приемы программирования 423 • ...................... ............... «И* >, >«,J ,<<.>«<(,! >>,!>>«>>«>, « I.I I I I I I < ... I .« .... I », j Листинг 17.9. Программа ch10e09.pro I,... ......«J........... ..«..I............ >>>>>.,>. .to. .. ..... it predicates c onp_un i f y_b i n comp_unify (binary, binary) access(binary) error_handler(integer ErrorCode, unsigned Index, binary) clauses comp_unif y__bin: - Bin ~ makebinary(5), comp_unify (Bin,_), comp_unify($ [1,2], $[100] ) , comp_unify($[0],Bin), comp_unify ($ [1,2,3] ,$[1,2,4]) . comp_unify(В, B):—!, write(B," = ",B,’\n’). comp_unify(Bl,B2) Bl > B2,!, write(Bl," > ",B2, r\nr). comp_unify(B1,B2) write (Bl,” < ",B2,f\n'). access(Bin):- setwordentry(Bin,3,255), fail. % Изменения не исчезают во время поиска с возвратом! access(Bin):- Size ~ gerbinarysize(Bin), X = getwordentry(Bin,3), write("\nSize=",Size,” X=",X," Bin=",Bin,'\n'). error_handler(ErrorCode, Index, Bin) write (’’Error ”,ErrorCode, " setting word index ”, Index," of ",Bin, '\n', "Press any char to terminate execution\n"), readchar(_). goal % Иллюстрация сравнения и сопоставления бинарных термов comp_uni fy_bin, % Создание бинарного терма размером 4 слова Wordsize = sizeof(word), Bin = makebinary(4,WordSize) , access(Bin), % Иллюстрация проверки диапазона; номера элементов начинаются с 0 write("Run-time error due to wrong index:\n”), Index = 4, trap(setwordentry(Bin,Index,0),E, error_handler(E,Index,Bin)).
424 Часть IV. Программирование на Visual Prolog Этот пример использует предикат trap, который будет обсуждаться ниже (см. разд, "Ошибки и исключительные ситуации" данной главы). Преобразование термов в бинарные термы Аргументы составного терма могут быть ’’разбросаны0 по всей памяти, в зависимо- сти от того, каким доменам они принадлежат. Простые типы хранятся прямо в са- мой записи терма, в то время как сложные типы (к которым получают доступ через указатель и которые размещаются в глобальном стеке) необязательно находятся око- ло терма, в котором они появляются. Сопоставление переменной терма с другой переменной создает лишь копию указателя на терм. Использование предиката termstr (он обсуждается в гл. 20) дает возможность кон- вертирования терма в строку и обратно, но это достаточно неэффективно, когда нужна всего лишь копия содержимого терма. Предикат term__bin решает эту проблему. Предикат term_bin/3 Этот предикат осуществляет преобразование между термом любого домена и блоком бинарных данных, сохраняя содержимое терма и указатели. Сохраненные указатели будут использованы при преобразовании бинарных данных обратно в терм, позволяя вновь создавать любые указатели на сложные термы, которые содержит данный терм. Предикат term_bin выглядит так: term_bin (domain, Term, Bin) % (i,i,o) (i,__, i) domain — это домен, которому принадлежит или должен принадлежать Term, a Bin — это бинарный терм, хранящий содержимое Term. В программе chlOe 11.pro (листинг 17.10) демонстрируется преобразование между термом и его бинарным представлением. Домены и выравнивание были выбраны явно для упрощения описания, т. к. они отличаются для 16- и 32-битных платформ. Выравнивание термов обычно уместно только при взаимодействии с другими язы- ками, что подробно будет описано в гл. 24. [ Листинг 17.10. Программа ch10e11.pro ....................... * > !»!• г.« ... .... *>»»> ь /. >>> ь jrtf ».»»».t.» ».. i.Vi.i.k, . «.' . b pvi „«’«Pt .. domains dom = align dword cmp(string,short) goal T = cmp("Bilse", 31) , term_bin(dom, T, B) , write (’’Binary form of " , T, ” : \n”, B) , term_bin(dom,Tl,B), write("\nConverted back: ",Tl,’\n’).
Глава 17. Более сложные приемы программирования 425 Запустив это, вы получите: Binary form of cmp(”Bilse",31): $[01,00,00,00,OA,00,00,00, IF,00, 42, 69, 6C,73, 65,00, 04,00,00, 00, 01, 00,00,00] Converted back: cmp("Bilse",31) Здесь не нужно особенно беспокоиться о фактическом формате, в основном мы имеем дело с деталями имплементации, которые могут меняться. Опишем кратко содержимое бинарной информации (рис. 17.2). $[01,00,00,00,0А,00,00,00,1F,00,42,69,6С,73,65,00,04,00,00,00,01,00,00,003 I I I I I I I I I I I I I I I II functor | 31 ”Bilse”\0 offset of # of ptrs I ptr to fix in fixup 0-relative (array, but ptr to string only one element here) Рис. 17.2. Бинарное представление терма Если терм содержит элементы домена symbol, бинарный терм будет содержать до- полнительную информацию для вставки символов в символьную таблицу при вос- создании терма из бинарного представления. Visual Prolog использует предикат term_bin для хранения элементов во внутренней базе данных и при посылке термов по каналу сообщений в другую программу. Если несколько программ разделяют внешние базы данных, то использование задейство- ванными доменами неодинакового выравнивания может привести к ошибке. Модульное программирование Способность системы работать с программами, разбитыми на модули, — это еще одна положительная черта Visual Prolog. Вы можете писать, компоновать и компи- лировать модули раздельно, а затем связывать их вместе, чтобы создать одну выпол- няемую программу. Если вам надо изменить программу, то достаточно только отре- дактировать и перекомпилировать отдельные модули, а не всю программу в целом. Это вы непременно оцените при написании больших программ. Кроме того, мо- дульное программирование имеет еще и такое преимущество — по умолчанию все имена предикатов и доменов являются локальными. Это значит, что различные модули могут использовать одинаковые имена для разных целей. Visual Prolog ис- пользует две концепции для управления модульным программированием: глобальные объявления и проекты. Глобальные объявления По умолчанию все имена, используемые в модуле, являются локальными. Visual Prolog-программы взаимодействуют через границы модулей при помощи предикатов, определенных в разделах global predicates и в классах. Домены, определенные
426 Часть IV, Программирование на Visual Prolog в этих глобальных разделах, должны быть определены как глобальные домены, либо должны быть стандартными доменами. Начиная с версии 5.2, Visual Prolog предоставляет усовершенствованное управление глобальными объявлениями. А именно: П главный модуль проекта (с разделом цели goal) должен содержать объявления всех глобальных доменов (и разделов глобальных фактов), объявленных во всех подмодулях проекта; П любой другой проектный модуль содержит объявления только тех глобальных доменов, которые используются в этом модуле; □ глобальные объявления могут помещаться после локальных; □ если изменяется какое-либо глобальное объявление, то должны быть перекомпи- лированы только модули, включающие это объявление. Глобальные домены Вы делаете домен глобальным, записав его в разделе global domains. Во всем ос- тальном глобальные домены аналогичны локальным. Visual Prolog версии 5.2 предоставляет усовершенствованное управление глобальны- ми доменами. Теперь не требуется, чтобы все модули содержали одинаковые объявления всех глобальных доменов в определенном порядке (в PDC Prolog и в версиях Visual Prolog до 5.2 для проверки этой идентичности использовалась специальная утилита chkdoms.exe). Теперь же вам нужно следовать лишь двум, гораздо менее строгим правилам: П только главный модуль проекта (с разделом цели) должен содержать объявления всех глобальных доменов (и разделов глобальных фактов), объявленных во всех подмодулях проекта; П любой другой проектный модуль содержит объявления только тех глобальных доменов, которые используются в этом модуле. Это дает следующие принципиальные преимущества: П возможность создания и использования заранее скомпилированных библиотек (использующих глобальные домены); □ уменьшение времени компиляции: если изменяется какое-либо глобальное объ- явление, то должны быть перекомпилированы только модули, включающие это объявление; □ программы могут использовать больше доменов, т. к. модуль может включать только те глобальные домены, которые действительно используются в этом модуле. В соответствии с этими правилами, PDC-компоновщик во время компоновки про- веряет, включает ли главный проектный модуль объявления всех глобальных доме- нов, объявленных в этом проекте. Если он обнаружит глобальный домен Domain- Name, который объявлен в подмодуле FileName и не объявлен в главном модуле, то он сгенерирует сообщение об ошибке вроде этого: FileName — undefined name: $ global $dom$DomainName
Гпава 17. Более сложные приемы программирования 427 Обратите внимание, что PDC-компоновщик сравнивает только имена глобальных доменов и не гарантирует того, что глобальный домен имеет такие же объявления в других проектных модулях; эта целостность целиком на ответственности програм- миста. Если же вы перепутаете объявления, могут появиться проблемы во время ис- полнения программы: зависание компьютера под DOS или нарушение защиты (pro- tection violation) на 32-битных платформах. Самый простой способ гарантировать корректность — поместить (включить) все объявления глобальных доменов в один файл (например, ProjectName.inc), который вы затем сможете включить во все модули, где это необходимо, директивой include: include "ProjectName.inc” Среда визуальной разработки Visual Prolog предоставляет гибкий автоматический механизм для управления включением объявлений глобальных доменов в модули проекта. Ядро этого механизма — диалоговое окно File Inclusion for Module, которое активизируется при создании нового модуля. Для небольших проектов вы можете использовать упрощенную стратегию обеспечения включения всех глобальных доме- нов в каждый проектный модуль. Для этого вам нужно: 1. В диалоговом окне File Inclusion for Module: • установить флажок Create <ModuleName>.DOM для каждого исходного моду- ля, который может привносить новые глобальные домены; • установить флажок Include <ModuleName>.DOM для задания того, что include утверждение для <ModuleName>.DOM должно быть сгенерировано в файле <ProjectName>.INC. 2. Поместить объявления всех глобальных доменов, экспортированных из модуля, в соответствующий файл <ModuleName>.DOM. Поскольку директива для файла <ProjectName>.INC помещается во все модули про- екта, все модули будут содержать одинаковые объявления глобальных доменов. В больших проектах вы можете реализовать более гибкую стратегию "где исполь- зуются" для подключения глобальных доменов. Вместо подключения файла <ModuleName>.DOM в файл <ProjectName>.INC вы можете выборочно подключать файлы <ModuleName>.DOM только в те модули, которые реально импортируют глобальные домены, объявленные в этих файлах. Если следовать этим правилам подключения и именования файлов, то построитель программ (Make facility) будет автоматически обнаруживать изменения файлов, содержащих глобальные объявле- ния, и совершать перекомпиляцию необходимых модулей, основанную на времен- ных отметках. Глобальные секции фактов Раздел facts будет глобальным, если перед ключевым словом facts (можно исполь- зовать и устаревшее ключевое слово database) вставлено ключевое слово global. ( Замечание Размещать обязательно требующиеся инициализирующие предложения для single-фактов из глобальных секций фактов (баз данных) можно только после раз- дела goal в главном модуле проекта.
428 Часть IV. Программирование на Visual Prolog Поскольку Visual Prolog автоматически генерирует глобальный домен, соответст- вующий имени каждой глобальной секции фактов (базе данных), то все перечислен- ные правила управления глобальными доменами следует применить и к глобальным секциям фактов. Обратите внимание, что техника "безопасного" программирования требует, чтобы вместо глобальных фактов вы использовали глобальные предикаты, оперирующие локальными фактами. Глобальные предикаты Объявления глобальных предикатов отличаются от объявлений обычных (локаль- ных) предикатов, поскольку они должны содержать описание потока (потоков) па- раметров, при помощи которых может быть вызван данный предикат. Если шаблон не определен, все аргументы считаются входными. Ключевое слово global перед ключевым словом predicates означает, что все преди- каты и предикатные значения, объявленные в разделе, глобальны для проекта. Каждое объявление в разделе global predicates должно следовать синтаксису: global predicates [DeterminismMode] [ReturnDomain] PredicateName [(ArgList)] [- [Flowpatterns]] [Language] [ObjNameSpec] Синтаксис для объявления глобальных предикатных значений таков: predicateName : PredicateDomain [ObjNameSpec] Здесь: □ PredicateName — определяет имя объявленного глобального предиката или пре- дикатного значения; □ PredicateDomain — является глобальным предикатным доменом, который должен быть объявлен заранее в разделе глобальных доменов; □ DeterminismMode — определяет режим детерминизма предикатов одним из сле- дующих ключевых слов: {procedure j determ I nondeterm | failure | erroneous I multi} Этот режим детерминизма используется для всех заданных потоков параметров предикатов, если другие режимы детерминизма не заданы перед отдельными по- токами параметров. Если режим детерминизма предикатов не задан явно, то принимается режим детерминизма по умолчанию (тот, который определен в поле Default Predicate Туре диалогового окна Compiler Options среды визуальной разработки или задан опцией компилятора -z[Value] в командной строке). Изначально режимом де- терминизма по умолчанию является determ; □ ReturnDomain — определяет домен возвращаемого значения, если вы объявляете функцию; □ ArgList — определяет домены для аргументов предиката в форме: [ arg_l [, arg_2 ] * ]
Гпава 17. Более сложные приемы программирования 429 Здесь arg_N имеет такую форму: Domain_Name [ Argument_Name ] Domain_Name может быть любым стандартным или пользовательским доменом. Необязательный параметр Argument_Name определяет мнемоническое имя аргумента. Это имя должно быть корректным именем в Visual Prolog. Пролог поддерживает эту возможность для улучшения читабельности кода и рассматривает Argument_Name лишь как комментарий. Скобки, окружающие список аргументов ArgList, могут быть опущены, если ArgList пустой. Flowpatterns имеет следующий формат: [[FlowDetermMode] FlowPattern] [[,] [FlowDetermMode] FlowPattern]* Flowpattern имеет такой формат: (flow [, flow ]*) где flow — { i I о I functor FlowPattern I listflow }, a listflow — '[' flow [, flow ]* [ 'I' { i I о | listflow } ] ']' Символ (-) обязателен, если какой-либо Flow Patterns определен. Поток параметров определяет, как использовать каждый аргумент. Он должен быть i для входных аргументов и о — для выходных. Функтор и поток параметров долж- ны быть даны для составного терма (т. е. (i,o,myfunc(i,o) ,о)), или list flow (т. е. [ i, myfunc (i, о), о]). Внимание! Несколько потоков параметров могут быть явно заданы для глобального предиката. Если ни один поток параметров не задан явно, то неявно принимается поток пара- метров по умолчанию со всеми входными аргументами. При переобъявлении тестированных и хорошо работающих локальных предикатов в глобальные, применение этого неявного определения потока параметров может стать причиной сообщений об ошибке типа "This flow pattern does not exist" (Такой поток параметров не существует). FlowDetermMode — одно из ключевых слов: {procedure | determ | nondeterm I failure I erroneous | multi} Необязательно объявлять отдельные режимы детерминизма для каждого из потоков параметров. Если FlowDetermMode определен перед потоком параметров, то для этого потока параметров он переопределит режим детерминизма, заданный перед преди- катным именем, или установит режим детерминизма по умолчанию (диалоговое окно Compiler Options). Language имеет следующий формат: language { pascal | stdcall I asm | с I syscall | prolog}
430 Часть IV. Программирование на Visual Prolog Спецификация языка (language specification) сообщает компилятору, какое использо- вать соглашение о вызовах. Это требуется только в тех случаях, если предикат будет применяться в функциях, написанных на других языках (С, Delphi и др.). Соглашение о вызовах по умолчанию — это prolog. Обратите внимание на различие с объявлением предикатных доменов, где по умолчанию используется pascal. ObjNameSpec имеет следующий формат: as "ObjectName" ObjNameSpec можно использовать для определения открытого (публичного) объект- ного имени Obj ectName, переопределяя имя, присвоенное по умолчанию компилято- ром Visual Prolog. В основном ObjNameSpec применяется при компоновке модулей, написанных на других языках (см. гл. 24). Здесь мы используем: □ квадратные скобки для указания необязательных элементов, а фигурные скобки для указания того, что один из элементов, разделенных символами |, должен быть использован; □ пару одиночных кавычек для указания того, что символ, заключенный между ними (как то ' | ', 1 [ ’ и ’ ] ’), является частью синтаксиса языка Visual Prolog; □ символ звездочка (*) для указания произвольного числа непосредственно пред- шествующих элементов (нуль или более раз). Приведем несколько примеров. В следующем объявлении глобального предиката name и home — строкового типа, а аде — целого; все аргументы предиката firstjpred могут быть как связанными (i, i, i), так и свободными (о, о, о): first^pred(name,home,аде) - (i,i,i) (о,о,о) Вот объявление предиката с составным потоком параметров для списка целых или с простым выходным потоком параметров: pl(integerlist) - (fi,о,i|о]), (о) Если поток параметров не задан явно в объявлении глобального предиката: my__converter(string, integer) тогда принимается поток параметров по умолчанию со всеми входными аргументами (i, i), как будто объявление было таким: my_converter(string, integer) - (i,i) Для каждого потока параметров может быть объявлен собственный режим детерми- низма. Например: procedure append(ilist,ilist,ilist) - (i,i,o) determ (i,i,i) nondeterm (o,o,i) В этом примере предикат append имеет режим детерминизма procedure с единствен- ным потоком параметров (i, i,o) . Будучи вызванным с потоком параметров (i,i,i), ОН будет determ, а с (o,o,i) — nondeterm.
Гпава 17, Более сложные приемы программирования 431 Наконец, это объявление определяет составной поток параметров для объекта, объявленного как func(string, integer), возникающего из домена с именем mydom: pred (mydom) — (func(i,o)) (func(o,i)) ______Замечание j Если какое-либо определение глобального предиката изменено, то нужно переком- пилировать только те модули, которые ссылаются на этот предикат. А если вы изменяете поток параметров предиката, то необходимо изменить код вызовов, ис- пользующих этот предикат. Не важно, в каком модуле появляются предложения для глобального предиката, но, как и в случае локальных предикатов, все предложения должны появляться вместе. Проекты Если вы используете VDE, то эксперт приложений автоматически управляет созда- нием новых проектов и добавлением/удалением проектных модулей. Построитель программ (Make facility) VDE автоматически совершает операции компиляции и компоновки, необходимые для создания целевого модуля из исходных модулей проекта. Следовательно, нужно объяснить здесь всего две особенности, которые будут важны в случае использования командной строки для вызова компилятора: □ Как Visual Prolog-проект использует таблицу символов. Все Visual Prolog-модули, включенные в проект, разделяют единую внутреннюю символьную таблицу, которая хранит все термы symbol-домена, используемые во всех проектных модулях. Символьная таблица генерируется в так называемом SYM-файле. SYM-файл — это объектный файл с расширением sym. По умолча- нию, если имя проекта — ProjectName, то имя SYM-файла — ProjectName.SYM. Это имя можно изменить опцией SYM File Name в диалоговом окне Compiler Options. Когда компилятор командной строки компилирует исходный файл ModuleName.PRO, он по умолчанию принимает, что имя SYM-файла — ModuleName.SYM. Это имя по умолчанию можно изменить опциями компи- лятора командной строки -r<ProjectName> и -M<SymFileName>. □ Visual Prolog-программы должны иметь цель (содержать Goal-секцию). По умолчанию компилятор проверяет, имеет ли компилируемый файл раздел це- ли (Goal), и, если не имеет, — генерирует ошибку. Но в многомодульных проек- тах только один (главный) модуль проекта содержит раздел цели. Чтобы скомпи- лировать другие модули проекта, компилятор нужно проинформировать о том, что эти модули являются частями проекта и именно поэтому не содержат раздел цели. Это делается опцией компилятора командной строки -г[ProjectName], Пусть, например, при компиляции файла ModuleName.PRO компилятор получа- ет опцию: - гр го j ectName
432 Часть IV. Программирование на Visual Prolog тем самым компилятор "узнает", что: • компилируемый файл ModuleName.PRO является модулем проекта ProjectName и, соответственно, не должен содержать раздел цели goal; • имя SYM-файла — ProjectName. SYM. При некоторых обстоятельствах необходимо использовать опцию M<SymFileName> для переопределения имени SYM-файла, заданного опцией r<ProjectName>. На- пример, при компоновке из среды Visual C++ существует требование, чтобы все объектные файлы имели расширение obj. Ошибки и исключительные ситуации Обеспечение корректной обработки ошибок, безопасности и надежности программ становится все более важным требованием во всех средах разработки. В этом разделе мы рассмотрим стандартные предикаты Visual Prolog, обеспечивающие широкие возможности по обработке ошибок, возникающих во время выполнения программ. Эти возможности включают отслеживание ошибок во время выполнения программ, контроль за прерываниями, обработку исключительных ситуаций. Если вы посмотрите на Visual Prolog-файл с сообщениями об ошибках PROLOG.ERR, то увидите все номера ошибок, соответствующие как ошибкам вре- мени компиляции, так и времени выполнения. Все номера до 10 000 включительно зарезервированы для кодов ошибок пользовательских программ, и вы можете при необходимости изменять и расширять файл ошибок. Кроме того, в каталоге INCLUDE вы найдете файл ERROR.CON, содержащий объявления констант для всех кодов ошибок этапа исполнения программы (в диапазоне от 1000 до 10 000). Вместо использования непосредственных номеров ошибок советуем (в целях безо- пасности) применять в приложениях этот файл для кодов ошибок. Обработка исключительных ситуаций и отслеживание ошибок Краеугольным камнем в обработке ошибок и исключительных ситуаций выступает предикат trap, который может "ловить" ошибки на этапе выполнения и исключи- тельные ситуации, вызванные предикатом exit. exit/О и exit/1 Вызов предиката exit имеет эффект, идентичный ошибке на этапе выполнения. exit(} % (без аргументов) exit(ExitCode) % (i) Если предикат exit не имеет аргумента, то значение аргумента равно нулю, т. е. вызов эквивалентен exit(O). Если обращение к exit сопровождается обращением к предикату trap, то значение из exit будет передано в trap. Поведение exit вне предиката trap зависит от платформы. Обработчики событий VPI отслеживают ошибки и исключительные ситуации сами, и "пойманный" exit приведет к сообщению об ошибке.
Гпава 17. Более сложные приемы программирования 433 Вызов предиката exit вне предиката trap на textmode-платформах ведет к заверше- нию программы с возвращаемым кодом, равным значению, используемому в вызове exit. Максимальное значение, с которым процесс может завершиться, — 254; зна- чение 255 зарезервировано для системного вызова Visual Prolog. Но никаких прове- рок не осуществляется. errorexit/O и errorexit/1 Вызов предиката errorexit вызывает ошибку на этапе выполнения с установкой внутренней информации о номере ошибки: errorexit() % (без аргументов) errorexit(ErrorNumber) % (i) Вызов предиката имеет эффект, идентичный ошибке на этапе выполнения, но, в отличие от предикатов exit, он устанавливает внутренний номер ошибки ErrorNumber, который можно получить при обращении к предикату lasterror/4. lasterror(LastErrorNumber, ModuleName, IncludeFileName, Position) Этот Предикат вернет LastErrorNumber - ErrorNumber. Обратите внимание, что для получения правильной позиции (Position) все модули проекта должны быть скомпилированы с errorlevel > 0. Вызов предиката errorexit/O эквивалентен вызову: errorexit(1000) Если вызов предиката errorexit помещен в предикат trap, то вызов errorexit вер- нется назад к этому trap, и ErrorNumber будет передан в переменную кода ошибки этого trap. Предикат trap/3 Для обработки исключений и прерываний, связанных с ошибками, применяется предикат trap, который имеет три аргумента. Первый и третий аргументы являются обращениями к предикатам, а второй аргумент представляет собой переменную. Формат предиката следующий: trap(PredicateCall, ExitCode, PredicateToCallOnError) Рассмотрим, например, такой фрагмент: trap(menuact(Pl, Р2, РЗ), ExitCode, error(ExitCode, Pl)), ... Если в процессе выполнения предиката menuact, включая все внутренне вызываемые подцели, возникнет ошибка, то переменной Exitcode будет передан код ошибки, а затем для обработки ошибки будет вызван предикат error. При этом выполнение trap после возвращения из error завершится неудачей. Если manuact завершится успешно, то выполнение программы будет продолжено после возвращения из trap, который в этом случае не выполняет никаких действий. Перед вызовом предиката error система восстановит в стеке, глобальном стеке и области trail те значения, которые содержались в них перед обращением к преди-
434 Часть IV. Программирование на Visual Prolog кату menuact. Это означает, что вы можете применять trap для предотвращения пе- реполнения памяти. Если прерывание разрешено (break on) и оно возникает (если пользователь нажал комбинацию клавиш <Ctrl>+<Break> в процессе выполнения предиката, включен- ного в предикат trap), то trap позволяет обнаружить прерывание и переменной ExitCode присваивается значение 0. Рассмотрим пример работы предиката trap в программе chl0e!2.pro (листинг 17.11). 1 * «'’ММ.' ................................................iii kitk.iUd ["Листинг 17.11. Программа ch10e12.pro ' .h.m.mn .................................. include "error.con" domains file = inpfile predicates ioehand(integer,file) getline(file,string) clauses ioehand(err_notopen,File): -!, write(File," isn’t open\n"), exit (1) . ioehand(Err, File) write("Error ",Err," on ",File, '\n') , exit (1) . getline(File,Line) readdevice(Old), readdevice(File) , readln(Line) , readdevice(Old). goal trap(getline(inpfile,First),Err,ioehand(Err,inpfile)), write(First). Предикат errormsg/4 Вы можете применять предикат errormsg для доступа к файлам, имеющим такую же структуру, как и файл сообщений об ошибках PROLOG.ERR: errormsg(File name, ErrorNo, ErrorMsg, ExtraHelpMsg) % (i,i,o,o) Ниже приведен пример типичного использования errormsg в предикате, позволяю- щем получить описание ошибки по ее коду: predicates error(integer) main /*.... */
Гпава 17. Более сложные приемы программирования 435 clauses error(O) % discard break. error(E) errormsg("prolog.err", E, ErrorMsg, _} , write("\nSorry; the error\n", E, " : ", ErrorMsg), write ("\nhas occurred in your program.’’}, write("\nYour database will be saved in the file error.sav"), save("error.sav"). goal trap(main, ExitCode, error(Exitcode)). Сообщения об ошибках Visual Prolog включает несколько директив компилятора, которые можно использо- вать для управления отчетностью об ошибках на этапе выполнения программ. Эти директивы позволяют вам выбирать: □ нужно ли генерировать код для проверки переполнения целых чисел; □ уровень детализации в сообщениях об ошибках на этапе выполнения; □ нужно ли генерировать код для проверки переполнения стека. Вы можете поместить эти директивы компилятора в начало программы или выбрать их в диалоговом окне Compiler Options. Директива компилятора errorlevel Visual Prolog обладает механизмом, который определяет позицию в коде, где про- изошла ошибка на этапе выполнения. Для этого Visual Prolog генерирует код, кото- рый перед вызовом предиката сохраняет позицию исходного кода, в которой факти- чески происходит выполнение. Уровни детализации сообщений об ошибках и со- хранения позиций исходного кода можно выбрать директивой компилятора errorlevel. Ее синтаксис: errorlevel = d где d принимает значение 0, 1 или 2, представляющие следующие уровни: □ О — этот уровень генерирует наименьший и наиболее эффективный код. Ни одна позиция исходного кода не сохраняется. При возникновении ошибки сообщается лишь ее номер; □ 1 — это уровень по умолчанию. При возникновении ошибки Visual Prolog ото- бражает место ее возникновения (имя модуля и включенного файла). Место об- наружения ошибки в исходном файле также отображается (выраженное в коли- честве байт от начала файла); П 2 - на этом уровне сообщаются некоторые ошибки, проигнорированные на уровне I, включая переполнение стека, кучи, следа и т. д. Перед каждым вызо- вом предикатом генерируется код, сохраняющий исходную позицию. Если вы получили сообщение об ошибке с указанием этой позиции, вы можете загрузить программу в редактор и активизировать пункт меню Edit | Go То Line
436 Часть IV. Программирование на Visual Prolog Number, в котором нужно ввести номер позиции — и курсор передвинется в то место, где имела место ошибка. Директива errorlevel контролирует в каждом модуле уровень детализации отчетно- сти об ошибках в нем. Однако, если уровень отчетности в главном модуле выше, чем в остальных, система может сгенерировать некорректную информацию об ошибке. Если, например, ошибка возникнет в модуле, скомпилированном с errorlevel = О, и этот модуль скомпонован с главным модулем, скомпилированным с errorlevel равным 1 или 2, то система не сможет показать корректное местонахождение ошиб- ки — вместо этого она укажет на позицию некоторого, ранее выполненного кода. Предикат lasterror/4 Этот предикат возвращает информацию о самой последней ошибке и имеет формат: lasterror(ErrNo,Module,IncFile,Pos) % (1,1,1,1) где ErrNo — номер ошибки, Module — имя исходного файла, incFile — имя вклю- чаемого файла, и Pos — позиция в исходном коде, в которой произошла ошибка. Однако программа должна быть откомпилирована с errorlevel 2, чтобы информа- ция в случае переполнения памяти была релевантна. Для обычных ошибок бывает достаточно errorlevel, равного 1. Главная цель предиката lasterror — упростить отладку в случаях прерываний под- целей, но он также может служить основой получения информации о причинах про- блем, возникающих в коммерческом ПО. При использовании lasterror код может предоставлять вам как краткие сообщения об ошибках, так и точную информацию о происшедшем. Обработка ошибок при чтении термов Если вы обращаетесь к предикатам consult или readterm и при чтении строки воз- никает ошибка, то выполнение предиката завершится неудачей. К синтаксическим ошибкам можно отнести следующие: □ строка не имеет окончания; □ несколько термов расположены на одной строке; □ вместо числа в строке стоит символьный тип; □ название предиката содержит заглавные буквы; □ символьный тип не заключен в двойные кавычки и т. д. Когда в Visual Prolog был введен предикат consult, то первоначально предполага- лось, что он будет применяться только для чтения файлов, полученных с помощью предиката save, а не для чтения файлов, созданных пользователем. Для облегчения ввода файлов, отредактированных пользователем, были введены два предиката — readtermerror и consulterror. Вы можете применять их для получения информации о том, какого рода ошибки возникают при использовании предикатов readterm и consult.
Глава 17. Более сложные приемы программирования 437 Если ошибки, возникающие при выполнении consult и readterm, обрабатываются с помощью предиката trap, то предикаты consulterror и readtermerror позволят исследовать и устранить синтаксическую ошибку. Предикат consulterror/3 Этот предикат выдает информацию о строке, содержащей синтаксическую ошибку. consulterror(Line, LinePos, Filepos), % (о,о,о} Параметр Line связан со строкой, содержащей синтаксическую ошибку, Line Роз указывает на позицию ошибки в строке, a FilePos — на позицию строки в файле. Рассмотрим это на примере программы chlOe 13.pro (листинг 17.12). constants helpfile = "prolog.hip" errorfile = "prolog.err" domains dom = f(integer) list = integer* facts — mydba pl(integer, string, char, real, dom, list) predicates handleconsulterr(string, integer) clauses handleconsulterr(File, Err) Err>1400, Err<1410, ! , retractall(_, mydba), consulterror(Line, LinePos, _), errormsg(errorfile, Err, Msg, _), str_len(Blanks,LinePos), write("Syntax error in ", File,'\n',Line,'\n',Blanks,"A\n",Msg,'\n’), exit(1). handleconsulterr(File,Err) errormsg(errorfile,Err,Msg,_), write("Error while trying to consult ", File,":\n",Msg,’\n’), exit(2) . goal File="faulty.dba", trap(consult(File, mydba), Err, handleconsulterr(File,Err)), write("\nSUCCESS\n"). Файл ’’faulty.dba" нужно скопировать в подкаталог \ЕХЕ вашего проекта, если про- грамма запускается командой Run, или в подкаталог \OBJ, если вы используете Test Goal.
438 Часть IV. Программирование на Visual Prolog Предикат readtermerror/2 Этот предикат выдает информацию о строке, прочитанной readterm, содержащей синтаксическую ошибку. readtermerror(Line, LinePos), % (0,0) Параметр Line связан со строкой, содержащей синтаксическую ошибку, a LinePos указывает на позицию ошибки в строке. Динамическое отсечение В Прологе традиционно применяется статическое отсечение. Одной из проблем, связанных с отсечением, является то, что отсечение воздействует на предикаты, рас- положенные за символом и только в тех предложениях, куда этот символ был по- мещен (в исходном тексте программы). При этом отсутствует возможность передачи отсечения в качестве аргумента в другой предикат, в котором отсечение будет вы- полняться при наличии определенных условий. Другой проблемой, связанной с тра- диционным отсечением, является невозможность отсечь поиск дальнейших решений подцели в предложении, не отсекая при этом точки возврата для последующих предложений данного предиката. Visual Prolog обладает механизмом динамического отсечения, который реализован при помощи двух стандартных предикатов — getbacktrack и cutbacktrack. Этот меха- низм позволяет решить обе указанные выше проблемы. Предикат getbacktrack устанавливает текущий указатель в вершину стека точек отката (backtrack points). В дальнейшем вы можете удалить все точки отката, помещенные выше этого места, с помощью предиката cutbacktrack. Далее приведены примеры, иллюстрирующие применение этих двух предикатов. 1. Предположим, имеется база данных, в которой содержатся сведения о конкрет- ных людях и их доходе, а также о наличии у них друзей. facts person(symbol, income) friends(symbol, symbol) Если вы определите счастливого человека, как человека, у которого либо много друзей, либо много денег, то это можно записать в виде следующих предло- жений: happy_person(has__friends (Р) ) person(Р, _), friends(Р, _) . happy_persori (is__rich (Р) ) person(Р, Income), not(rich(Income)). Если человек имеет более чем одного друга, то первое предложение будет выда- вать несколько решений. В данном случае вы можете добавить еще один преди- кат have_friends (р, р), в котором будет использоваться отсечение или, вместо этого, применить динамическое отсечение.
Гпава 17. Более сложные приемы программирования 439 happy_person (has_friends (Р)) : - person(Р, _), getbacktrack(ВТОР)г friends (Р, __) , cutbacktrack(ВТОР). Хотя предикат friends может выдавать несколько решений, эта возможность от- секается с помощью вызова cutbacktrack. В дальнейшем, при неудачном завер- шении в предикате person будет выполняться поиск с возвратом. 2. Наиболее полезно применение динамического отсечения в том случае, когда вы передаете указатель поиска с возвратом в другой предикат, который выполняет отсечение по условию. Указатель относится к целому типу unsigned и может пе- редаваться с помощью аргументов типа unsigned. В качестве иллюстрации рассмотрим программу, которая выдает числа до тех пор, пока не будет нажата какая-либо клавиша. predicates number(integer) return_numbers(integer) checkuser(unsigned) clauses number(0). number(N) number(Ml), N = Nl+1. return_numbers(N) :- getbacktrack(BTOP), number(N), checkuser(BTOP), checkuser(BTOP) :- keypressed, cutbacktrack(BTOP). checkuser (_). Компилятор не распознает предикат cutbacktrack, когда выполняет проход, свя- занный с анализом предложений на детерминизм. Это означает, что вы можете получить предупреждающее сообщение Non-deterministic clause (Недетермини- рованное предложение) при использовании директивы check_determ, даже если вы используете предикат cutbacktrack. Динамическое отсечение надо использовать очень обдуманно. Весьма несложно разрушить структуру программы динамическим отсечением, и неаккуратной его использование неизменно ведет к проблемам, которые очень сложно отследить. Преобразование типов В большинстве случаев нет необходимости смешивать разные типы и совершать их преобразования, но иногда все же приходится это делать, взаимодействуя с другими языками или программируя на системном уровне. Для этого существует функция
440 Часть IV. Программирование на Visual Prolog cast» которая преобразовывает "что угодно" к "чему угодно". При этом над обраба- тываемыми значениями никакие проверки не производятся, и самые разрушитель- ные последствия могут произойти именно при попытке использования некорректно преобразованных переменных. Формат функции cast следующий: Result - cast(returnDomain,Expr) где значение Expr вычисляется (если это численное выражение), преобразуется к типу returnDomain и унифицируется с Result. Например, указатель на нулевую строку (указатель на символ со значением 0; это не пустая строка, являющаяся указателем на байт со значением 0) может быть создан следующим образом: NullPtr = cast(string,0) Не пытайтесь записать результирующую строку, используя предикат write, т. к. вы, вероятнее всего, получите ошибку защиты (protection violation) или зависание си- стемы. Заметим, что функция cast в обычных Пролог-программах практически не исполь- зуется . Стиль программирования В этом разделе вначале мы изложим ряд правил хорошего стиля программирования на Visual Prolog и дадим несколько практических советов, как и когда использовать предикаты fail и cut. Правила эффективного программирования Тщательный выбор объектов позволяет создавать программы, которые одновременно являются эффективными и не связаны с большими трудозатратами. Правило 1 Лучше использовать больше переменных и меньше предикатов. Зачастую декларативный стиль Пролога приводит к значительно менее эффективно- му коду, нежели другие (не декларативные) подходы. Так, например, если написать предикат для перестановки в обратном порядке элементов списка: reverse(X, Y) reversel ((], X, Y) . % Более эффективно reversel(Y, [], Y). reversel(XI, [U|X2], Y) reversel([UIXI], X2, Y). то для него требуется меньший объем стека, чем для конструкции, в которой ис- пользуется дополнительный предикат append: reverse([], []). % Менее эффективно reverse([U|X], Y) reverse(X, Yl), append(Yl, [U], Y). append((], Y, Y). append([UIX], Y, [U|Z]) append(X, Y, Z) .
Глава 17. Более сложные приемы программирования 441 Правило 2 Необходимо эффективно гарантировать невыполнение предиката в случае, если решения не существует. Допустим, мы хотим записать предикат singlepeak, с помощью которого мы можем просмотреть список целых чисел и определить, есть ли среди них максимум при равномерном нарастании и убывании чисел. Утверждение singlepeak([1, 2, 5, 7, 11, 8, 6, 4]). будет выполняться успешно, а утверждение: singlepeak([1, 2, 3, 9, 6, 8, 5, 4, 3]). будет выполняться неуспешно. В определении 1 singlepeak не учитывается правило 2, поскольку наличие в списке одного максимума определяется только тогда, когда с помощью предиката append список будет поделен на все возможные комбинации: % Определение 1 — не удовлетворяет правилу 2 singlepeak(X) append(Xl, Х2, X) , up(XI), down(X2). up[_J . up([U, V|Y]) U<V, up([V|Y]). down([]). down([U]). down([U, V|Y]) U>V, down([V|Y]). append([], • Y, Y). append([U|X], Y, (U|ZJ) append(X, Y, Z) . С другой стороны, следующее определение 2 распознает неуспешное выполнение предиката в самый начальный момент: % Определение 2 — удовлетворяет правилу 2 singlepeak([]). singlepeak([U, V|Y]) U<V, singlepeak([V|Y]). singlepeak([U, VjY]) U>V, down([V|Y]). down([ ] ) . down( [ U ] ) . down([U, V|Y]) :- U>V, down([V j Y]). В определении 3 предикат singlepeak еще больше сокращается за счет применения правила 1. % Определение 3 — удовлетворяет правилу 1 singlepeak([], _) . singlepeak([Н|[]], _) . singlepeak([U, V|W], up) U<V, singlepeak([V|W], up). singlepeak([U, V|W], _) U>V, singlepeak([V|W], down).
442 Часть IV. Программирование на Visual Prolog Таким образом, при использовании определения 3 вызов singlepeak(Y, up) выполняется успешно, если Y содержит список с одним максимумом, добавляемым к списку целых чисел, расположенных в порядке возрастания значений, а вызов singlepeak(Y, down) также выполняется успешно, если Y содержит список целых чисел, расположенных в порядке убывания значений. Правило 3 Доверяйте больше работы механизму унификации в Visual Prolog. На первый взгляд, описание предиката equal для проверки равенства двух списков должно выглядеть следующим образом: equal ((] , (] ) . equal((U|Х], [U|Y]) equal(X, Y). Но в этом нет необходимости. Используйте определение equal(X, X). или унификацию посредством =, а механизм унификации Visual Prolog сделает все остальное. Правило 4 Для повторяющихся операций используйте поиск с возвратом, а не рекурсию. Поиск с возвратом позволяет уменьшить размер стека. Задача заключается в исполь- зовании комбинации repeat... fail вместо рекурсии. Этот метод является очень важным. Давайте рассмотрим его подробнее в следующем разделе. Использование предиката fail Для того чтобы повторно оценить некоторую последовательность подцелей, зачастую бывает необходимо ^вводить предикат, подобный приведенному ниже предикату run с предложениями следующего вида: run : - readln(X), process(X, Y), write(Y), run. Это определение вызывает ненужные дополнительные затраты, связанные с исполь- зованием рекурсии, которая не может быть устранена системой, если предикат process (X, Y) недетерминироанный. В этом случае применение комбинации repeat.. .fail позволяет избежать хвостовой рекурсии.
Гпава 17. Более сложные приемы программирования 443 Введя предикат repeat. repeat :- repeat. мы можем переопределить предикат run без использования хвостовой рекурсии сле- дующим образом: run repeat, readin(X), process(X, Y), write(Y), Предикат fail вызывает поиск с возвратом к предикату process и, в конечном сче- те, к повторному выполнению предиката repeat, которое всегда завершается успеш- но. Но как вернуться из комбинации repeat ... fail? В тех случаях, когда вам нужно бесконечное выполнение (run:- run) с завершением только при возникновении каких-то исключительных условий, в неинтерактивных программах вы можете использовать предикат exit, а в интерактивных — нажимать клавишу <Вгеак>. В остальных случаях, если у вас имеется явное условие завершения, заме- ните предикат fail на проверку на завершение. run: - repeat, getstuff(X) , process(X,Y), putstuff(Y), test_for_completion(Y), t • Детерминизм и недетерминизм: использование отсечений Директива компилятора check determ является весьма полезной в тех случаях, когда необходимо определить места для использования отсечения, поскольку эта директи- ва позволяет отметить те предложения, которые порождают недетерминированные предикаты. Если вы хотите сделать эти предикаты детерминированными, то вам не- обходимо ввести в них отсечение, предотвращающее поиск с возвратом (что и по- рождает недетерминизм). Общим правилом в таких случаях является введение отсечения со смещением влево (к голове предложения) настолько, насколько это возможно без ущерба для логики работы программы. Всегда помните следующие два правила, согласно которым компилятор выявляет недетерминированные предложения: □ если в предложении нет отсечения, и присутствует еще одно предложение, кото- рое удовлетворяет таким же аргументам в голове предложения; □ в теле предложения стоит обращение к недетерминированному предикату и после этого обращения отсутствует отсечение.
Классы и объекты Visual Prolog включает в себя объектный механизм, объединяющий парадигмы логи- ческого и объектно-ориентированного программирования (ООП). Несколько небольших примеров программ на Visual Prolog, использующих объектно- ориентированное программирование в каталоге <CDROM>:\RUN\OoP\EXAMPLES находятся на прилагаемом CD-диске. Для того чтобы система могла рассматриваться как объектно-ориентированная, не- обходимо, чтобы она удовлетворяла четырем критериям. Это: П инкапсуляция; П классы; П наследование; П индивидуальность. Инкапсуляция Необходимость инкапсуляции и модульности хорошо известны. Инкапсулированные объекты помогают создавать более структурированные и читаемые программы, по- скольку объекты могут рассматриваться как "черные ящики". Посмотрите на слож- ную программу и найдите часть, которую вы можете объявить и описать. Инкапсу- лируйте ее в объект, постройте интерфейс и продолжайте в том же духе, пока не будут объявлены все подпроблемы, составляющие программу. Когда вы разобьете всю программу на объекты и удостоверитесь, что каждый из них работает корректно, вы сможете абстрагироваться от них. ООП также известно как программирование, управляемое данными. Фактически, вы позволяете объектам делать работу самим. Они содержат методы, которые вызыва- ются при создании объектов, при их удалении и, вообще, при любых обращениях к объектам. Методы могут вызывать и методы других объектов.
Глава 18. Классы и объекты 445 Объекты и классы Способы хранения данных в традиционных языках программирования обычно труд- ны для понимания и не подходят для моделирования. С объектами работать гораздо проще, т. к. они близки к человеческому восприятию реальных вещей и сами по себе являются инструментом для моделирования. Объект — гораздо более сложная структура данных, чем список. На элементарном уровне, объект — это объявление согласованных между собой данных. Это объявле- ние может содержать предикаты, которые работают с этими данными. В соответст- вии с терминологией ООП эти предикаты называются методами. Каждый класс представляет собой уникальный тип объектов и операций (методов), способных соз- давать такие объекты, удалять и манипулировать ими. Класс — это определяемый пользователем тип объекта; класс может создавать объек- ты этого типа. Экземпляр — это фактическое состояние объекта. Вы можете опреде- лить столько экземпляров (объектов) класса, сколько вам необходимо. Пример: class automobile owner string brand string endclass Actual instance 1 Owner Beatrice brand Morris Mascot End Actual instance 2 Owner John brand Rolls Royce End Наследование ООП — мощный инструмент для моделирования. Объекты могут быть определены на наиболее подходящем уровне абстракции. Относительно этого уровня объекты- наследники могут быть определены на более низких уровнях, или объекты - родители — на более высоких уровнях. Объект может наследовать данные и методы от объектов, определенных на более высоких уровнях. Таким образом, объекты — наиболее простой путь к созданию модульных прогр амм. Индивидуальность Каждый объект уникален. Объекты изменяют свое состояние, и, т. к. состояния объ- ектов можно наблюдать только посредством их предикатов-членов (т. е. предикатов, объявленных внутри определения класса), объект идентичен лишь самому себе. Та-
446 Часть IV. Программирование на Visual Prolog ким образом, даже если состояния двух объектов одинаковы, объекты не являются идентичными, ибо мы можем изменять состояние одного объекта, не изменяя при этом состояния другого, и в этом случае объекты становятся не идентичными. Очень важной характеристикой объекта является то, что индивидуальность сохраня- ется, даже несмотря на изменение его атрибутов. Мы всегда получаем доступ к объ- екту через ссылку на него. К нему можно получить доступ и через множество раз- личных ссылок, а, т. к. объект идентичен только самому себе, мы можем организо- вать несколько ссылок на один и тот же объект. Классы Visual Prolog Определение класса в Visual Prolog требует двух вещей: П объявления (декларации) класса; П реализации класса. Объявление класса определяет интерфейс класса, т. е. то, что видимо извне класса. Реализация класса содержит предложения Пролога, задающие конкретную функ- циональность данного класса. Таким образом, объявление интерфейса класса и фактическое определение предло- жений класса разделены. Объявление класса часто располагается в заголовочных файлах (обычно с расширениями ph), которые могут быть подключены к модулям, использующим класс. Реализация класса может быть расположена в любом модуле проекта, включающем объявление класса. Объявления классов Краткий синтаксис объявления класса такой: class class-name [: parentclass-list ] domains domain_declarations [static] predicates' predicate_declarations [static] facts fact declarations endclass [class-name] Раздел объявления класса начинается ключевым словом class и завершается ключе- вым словом endclass. Необязательный parentclass-list определяет родительский класс (классы), от которого класс class-name наследует домены, предикаты и факты Если какой-либо родительский <класс определен, то класс class-name называется производным классом, class-name после ключевого слова endclass писать необяза- тельно. По умолчанию права доступа к методам и данным, объявленным в разделе объявле- ния класса, являются открытыми (public), т. е. глобальны для всего проекта; следе-
Глава 18. Классы и объекты 447 вательно, такие домены, предикаты и факты могут быть доступны извне класса (и унаследованных классов). Если объявлениям разделов предикатов и фактов не предшествует ключевое слово static (см. разд. "Статические предикаты и факты" данной главы), объявленные факты будут принадлежать объектам (экземплярам класса), а предикаты будут иметь невидимые аргументы, идентифицирующие объекты, к которым они принадлежат. Реализация класса Краткий синтаксис реализации (имплементации) класса: implement class-name [: parentclass-list ] domains domain_declarations [static] predicates predicate_declarations [static] facts fact_declarations clauses clause_definitions endclass [class-name] Предложения класса определяют в разделе, который начинается ключевым словом implement и завершается ключевым словом endclass. Реализация класса может со- держать несколько разделов доменов, предикатов, фактов и предложений, class- name после ключевого слова endclass писать необязательно. Объявления, сделанные в разделе реализации класса, работают так, будто они зада- ны в разделе объявления класса, с единственным отличием в том, что эти объявле- ния данных и методов будут видны только в самом разделе, т. е. будут иметь закры- тый (private) тип. Иначе говоря, область видимости имен, объявленных в разделе реализации класса, — это сам раздел реализации. Обратите внимание, что классы можно наследовать прямо в разделе реализации, тем самым инкапсулируя детали реализации класса. Базовые (родительские) классы, оп- ределенные в разделе реализации класса, не должны дублировать базовые классы, которые определены в разделе объявления этого класса. Заметьте, что использование глобальных фактов — не самая надежная техника про- граммирования. Поэтому мы рекомендуем вам не объявлять факты в разделе объяв- ления класса. Правильнее инкапсулировать факты в реализации класса и предостав- лять доступ к ним через открытые предикаты-члены. В соответствии с планами PDC, будущие версии Visual Prolog не будут поддерживать открытые факты. Экземпляры класса — объекты После объявления и реализации класса можно создать любое количество экземпля- ров (объектов) класса. Каждый новый объект создается вызовом конструктора new для этого класса. Вызов предиката конструктора создает новый объект (экземпляр
448 Часть IV. Программирование на Visual Prolog класса) и возвращает ссылку на созданный объект, которая далее может быть ис- пользована для совершения операций над этим объектом и вызова методов этого объекта. Синтаксис вызова конструктора некоторого класса для создания нового экземпляра класса ту__с таков: Object_identifier - my_с :: new(), Обычный синтаксис для определения предикатов и фактов созданного объекта: [Object_identifier : ] predicate_or_fact_name [ (arguments) ] Пример в программе chlleOl.pro (листинг 18.1). «Л,;!-**-*£ « 1 1 ЧЛ" 1 ".j .НМ.' 11 *?*. " 4*4 *11 'Н«'*Мд»Д»РЛМ>М1М*ПД1|у*Н*» "" *M*?A**.M> 1 Ч,".11 ? 1 ? .* Листинг 18.1. Программа ch11e01.pnr .... ••Tl »»» ......... I*»»».,. ^•«4 14 It ... class cCounter predicates inc () dec () integer getval () endclass implement cCounte r facts single count(integer) clauses count(0). inc: - count(X), Xl-X+1, assert(count(XI)). dec: - count(X), Xl-X-1, assert(count(XI)). getval(VAL):- count(Vai). endclass goal Obj_counter = cCounter::new, Initial - Obj_counter:getval(), Obj_counter:inc, NewVal = Obj_counter:getval(), Obj counter:delete. Запустив программу утилитой Test Goal, вы получите: Obj_counter=145464, Initial=0, NewVal~l 1 Solution
Гпава 18. Классы и объекты 449 В этом примере вызов Obj_counter = cCounter: :new() конструктора new класса cCounter создает новый экземпляр класса cCounter и возвращает ссылку (иденти- фикатор объекта) на созданный объект в переменной Obj_counter. В дальнейшем эта ссылка используется для вызова методов объекта и доступа к экземпляру факта count из этого объекта. Каждый экземпляр класса имеет свою собственную версию нестатических фактов. Базами данных, содержащими такие факты, можно манипулировать как обычными фактами с помощью предикатов баз данных: retract, assert, save, consult и др. Каждый класс имеет конструктор по умолчанию new (без параметров). Когда про- грамма вызывает конструктор по умолчанию для создания объекта, Visual Prolog автоматически инициализирует все структуры, требуемые для созданного объекта. Для предоставления конструкторам класса дополнительной функциональности (инициализация фактов указанными значениями и т. д.), программист может опре- делить явно заданные конструкторы класса (см. разд. "Пользовательские конструкто- ры и деструкторы" данной главы). Уничтожение объектов Объекты могут быть явно удалены вызовом деструктора delete объекта. Obj = customers::new()/ Obj:change_account(), Obj:delete(). После вызова деструктора delete объекта Obj, объект obj считается удаленным, и любая попытка его использования будет некорректна. Удаление объекта вызывает автоматическое удаление всех (нестатических) фактов из базы данных этого объекта и освобождение памяти, используемой этим объектом. Таким образом, мы настоятельно рекомендуем удалять ненужные объекты с целью освобождения ресурсов. Заметьте, что однажды созданный объект сохраняется после вызова fail. Это зна- чит, что fail не удаляет созданный объект и не освобождает память, занимаемую объектом. Для удаления объекта и освобождения занимаемых ресурсов вам необхо- димо явно вызвать деструктор класса delete. Каждый класс имеет деструктор по умолчанию delete (без параметров). Домены классов Объявление класса с именем class_name автоматически создает глобальный домен class name. Этот домен может быть использован для объявления в предикатах доме- нов аргументов, которые передают ссылки на объекты этого класса. class class_name endclass
450 Часть IV. Программирование на Visual Prolog predicates р (class_name) Передача идентификатора объекта в качестве параметра означает всего лишь переда- чу указателя на объект, что соответствует обычному стилю ООП-программирования. Часто домены, соответствующие именам классов, могут быть использованы как обычные глобальные домены, и большинство соответствующих правил к ним при- менимо: □ домен, соответствующий имени класса ciass_name, должен быть видим только в тех модулях проекта (объявление класса class_name должно быть включено в мо- дули), которые явно используют этот домен; □ домены, соответствующие именам абстрактных классов, должны быть видимы в главном модуле проекта (см. разд. "Абстрактные классы"данной главы). Но домены, соответствующие именам неабстрактных классов, могут быть не видимы в главном модуле, если главный модуль их не использует. Домены, соответствующие именам классов, могут быть использованы для преобра- зований типов объектов с помощью предиката val. Обычно val проверяет правиль- ность преобразований типов во время компиляции; но когда val преобразовывает объект от родительского класса к производному классу (потомку), то оставляет про- граммисту некую свободу и проверяет правильность только во время выполнения. Если val обнаруживает, что преобразование неправильно, он генерирует ошибку на стадии выполнения. Производные классы и наследование Что делать, если вам нужен объект, который очень похож на уже имеющийся объ- ект, но обладает некоторыми дополнительными свойствами? Вам нужно всего лишь унаследовать новый класс, используя класс похожего объекта в качестве базового. Например, вы объявляете производный класс D, и перечисляете родительские классы Pl, Р2 через запятую в списке родительских классов: class D : Pl, Р2 ... Производный класс D наследует открытые (public) домены, предикаты и факты ука- занных родительских классов Pl, Р2. Унаследованные предикаты и факты становятся открытыми членами класса D, и на них можно ссылаться как на членов класса D. С другой стороны, D наследует только возможность использовать наследуемые доме- ны; к ним нельзя обращаться с именем класса D. Наследуемые домены, предикаты и факты могут быть переопределены в производ- ном классе. Глобальные предикаты также могут быть переопределены внутри класса, например, встроенные глобальные предикаты beep, concat, retract могут быть пе- реопределены внутри класса. Используя производный класс, можно пользоваться предикатами и фактами как класса родителя, так и производного класса. Но производный класс может переоп- ределить какие-либо предикаты и факты родительского класса. Члены базового
Глава 18. Классы и объекты 451 класса, переопределенные в производном классе, могут быть доступны из производ- ного с явным указанием имени базового класса в соответствии с синтаксисом: [base_class—name : : ] member_name [ (arguments) ] Пример производных классов приведен в программе chi le02.pro (листинг 18.2). ®ШЙинг18.2. Программа ch11e02.pro г..;..................................................... class ePerson predicates procedure add_name( string) — (i) procedure add_father( ePerson) — (i) procedure add—mother( ePerson) — (i) procedure write_info() endclass class cEmploye : ePerson predicates procedure add_company(string Name) -(i) procedure write_info() endclass implement ePerson facts name( string) father( ePerson) mother( ePerson) clauses add—name (Name) : - assert (name (Name) ) . add_father(Obj jperson) assert(father(Obj jperson)). add_mother(Obj jperson) assert(mother(Obj jperson)). write—info():- name(X), write(”Name=",X),nl, fail. write_info () : - father(F), write("Father:\n"), F:ePerson::write_infо() , fail. write_info():- mother(M),
452 Часть IV. Программирование на Visual Prolog write ("Mother: \n*’) , M:cPerson::write_info() , fail. write_info() . endclass implement cEmploye facts company(string Name) clauses add__company (Name) assert(company(Name)). write_info() : - ePerson::write_info()r fail. write_info() : - company(X), write("Company^", X) ,nl, write_info(). endclass goal F == ePerson::new(), F: add_name ("Arne"), 0 = cEmploye::new(), 0:add_name("Leo"), 0:add_father(F), O:add_company("PDC"), 0:write_info() , F:delete {), 0:delete () . Формальный синтаксис использования членов объекта: [Objectvariable:] [name_of_class::] name_of_member([arguments]) Объект может быть опущен внутри реализации класса или при вызове членов клас- са, объявленных статическими. Например, следующий вызов: memberename (arguments) будет воспринят как вызов соответствующего предиката-члена member_name этого класса (или его родителей) в случае, если он существует. Иначе, если в классе нет членов с данным именем, вызов будет воспринят как вызов предиката с таким име- нем, объявленного в разделе predicates или facts вне классов. Имена членов могут быть переопределены в иерархии классов. Таким образом, что- бы ссылаться на имена членов в предыдущем контексте, имя класса, указанное
Глава 18. Классы и объекты 453 в вызове какого-либо члена, может быть применено для явного указания используе- мого класса: [name_of_class: : ] name_of_member ( arguments) Виртуальные предикаты В Visual Prolog все предикаты, объявленные в разделах объявления классов, в соот- ветствии с терминологией C++ называются виртуальными методами. Виртуальные предикаты позволяют производным классам предоставлять различные версии преди- катов родительских классов. Вы можете объявить предикат в родительском классе и затем переопределить его в любом из производных классов. Предположим, что в родительском классе р объявляется и определяется открытый предикат who_am_i, а класс D, наследуемый от р, также имеет объявление и опреде- ление who_am_i. Если who_amj. вызван из объекта D, соответствующим вызовом-будет Dr :wtio_am_i, даже если доступ к wtio_am_i совершается через ссылку на Р. В качестве примера рассмотрите программу сЬПеОЗ.рго (листинг 18.3). class Р predicates test who_am_i() endclass class D : P predicates who_am_i() endclass implement P clauses test:- who_am_i(). who__am_i () : - write("I am of class P\n"). endclass implement D clauses who__am_i () : - write("I am of class D\n"). endclass goal 0 = D: : new, 0: testf 0:delete.
454 Часть IV. Программирование на Visual Prolog На выходе этой программы будет: I am of class D Заметьте, что, если вы определите предикат в производном классе с отличными от базового класса доменами или другим числом аргументов, Пролог будет рассматри- вать эти объявления как различные, и такой предикат виртуальным не будет. Статические предикаты и факты Предикаты-члены и факты-члены могут быть объявлены статическими. Использование ключевого слова static перед объявлением раздела predicates или facts означает, что все предикаты или факты данного раздела статические. На- пример: static facts single counter(integer) static predicates procedure get_counter(integer) Статические члены класса принадлежат самому классу, а не индивидуальным объек- там класса. Это означает, что если в классе объявлен статический член, то только один статический член существует для этого класса, а не по одному для каждого созданного объекта этого класса (как в случае нестатических объектных членов класса). Статические члены класса не имеют никаких ссылок на объекты данного класса. Открытые статические члены класса могут быть использованы в любом месте, где виден класс (в любых модулях, включающих объявление класса), даже если не соз- дано ни одного объекта этого класса. Вне разделов реализации классов, наследующих (транзитивно) статический член класса, к последнему можно обратиться с именем класса, используя синтаксис: class_naine :: static_class_jnember_name[ (arguments) ] Например, статический предикат get_counter можно вызвать так: goal • • • xclass::get_counter(Current_Counter), Естественно, к статическому члену (как и к нестатическим членам класса) можно обратиться через ссылки на объекты классов, содержащих данный статический член. Статические факты не генерируются для каждого экземпляра класса. Только одна версия статического факта существует для класса, и она доступна всем экземплярам класса (объектам). Это полезно, например, для подсчета количества экземпляров класса (объектов). Статические предикаты и их значения (см. разд. "Предикатные значения" гл. 17) не содержат внутреннюю ссылку на экземпляр класса, следовательно, "обычные” пре-
Гпава 18. Классы и объекты 455 дикатные домены могут быть использованы для объявлений статических предикат- ных значений. Пример в программе chlle04.pro (листинг 18.4). ( Листинг 18.4. Программа ch 11 е04.pro *'«*•*>«* * » f»V + ».ч,«»»*»а*мн»»мМа.«Нмой««Лм1 ....«о .«..U .V яя>>Р.1я+я,1к*.И *»*«! .11,*» class cCounter domains p_dom = determ (integer) — (o) % Предикатный домен predicates procedure new() static predicates st at ic_jp__va lue : p_dom endclass implement cCounter static facts — db single count(integer) clauses count(0). new() : - count(N)f Nl - N+l, assert(count(Nl)). static__p_value (N) count(N), write ("There are now ",N," instances of class cCounter\n\n") . endclass goal cCounter:: static_jp__value (_), % Статический предикат-член можно % вызвать до создания объекта О = cCounter::new()z О:static_p_value(_), % Определение статического предиката % по идентификатору объекта 02 = cCounter::new(), p__value = cCounter:: static_p__value, % Связывание статического % предикатного значения p_value(_I), % Вызов статического предикатного % значения через переменную О:delete, 02:delete. На выходе Test Goal для этой программы будет There are now 0 instances of class cCounter There are now 1 instances of class cCounter There are now 2 instances of class cCounter
456 Часть1]ЛПрограммирование на Visual Prolog 0=6763168, 02=6763190, Р value=430BFC, _I=2 1 Solution Ссылка объекта на себя (предикат this) Каждый нестатический предикат имеет невидимый для пользователя дополнитель- ный параметр: это указатель (идентификатор объекта) на фактический экземпляр класса (объект), которому этот предикат принадлежит. В предложении: implement х clauses inc: - count(X), Х1=Х+1, assert(count(XI)) . endclass объект полностью невидим. Если необходимо ссылаться на сам объект, например, для доступа к предикату в производном классе, то можно использовать встроенный предикат this. Предикат this предоставляет объекту доступ к любым предикатам- членам, определенным в соответствующем классе или в его родителях. Синтаксис для вызова предиката this следующий: this( Objectidentifier) % (о) Предикат this может быть использован только в нестатических (объектных) преди- катах-членах. Он имеет единственный выходной аргумент. Переменная object identifier в предикате this должна быть свободной; анонимная переменная (одиночный символ подчеркивания) здесь не разрешена. Домен аргумента this — это домен класса раздела реализации, в которой this вызывается. Например: implement х clauses inc: - this(Objectld), Objectld:x::count(X), X1=X+1, assert(count(Xl)). endclass Этот фрагмент кода идентичен по функциональности фрагменту кода, представлен- ному выше, с единственной разницей в том, что вы получаете идентификатор objectld самого этого объекта. Полученный идентификатор объекта object id может быть передан в качестве параметра другим предикатам.
Гпава 18. Классы и объекты 457 Области видимости класса Область видимости имени определена как часть программы, в которой вы можете получить доступ к имени. Имена предикатов, доменов и фактов могут быть переоп- ределены в классовой иерархии. Чтобы обратиться к имени из предыдущей области видимости, имя можно квалифицировать именем класса для указания, какой имен- но класс используется. Для такого явного указания класса используется следующая запись: class_name :: memberename(arguments) Пример: class parent predicates p (integer) endclass class child : parent predicates p(string, integer) endclass - % implementation не показана для краткости goal О - child::new, О : parent::p(99) % Доступ к предикату, определенному в родителе Классы как модули Другой способ явного задания области видимости — использовать классы со стати- ческими предикатами и статическими фактами как пакеты, как модули системы. Начиная с версии 5.2, Visual Prolog предоставляет возможность объявления доменов внутри классов, что позволяет использовать классы как модули. Открытые домены, объявленные в разделе объявления класса, глобальны и могут быть использованы извне класса. Замечание J Если класс объявляет только статические данные и методы, его можно рассматри- вать как модуль. Статические методы класса можно использовать как обычные глобальные методы (только помните, что обращаться к ним надо с именами классов). Преимущество создания таких модулей в том, что модуль будет иметь отдельное пространство имен (как результат определения с именем класса). Это означает, что вы можете выбирать имена в модуле более свободно. Это также обеспечивает постоянство имен всех методов в модуле. Другое преимущество в том, что объявления классов (кроме абст-
458 Часть IV. Программирование на Visual Prolog рактных классов) необязательно включать в главный модуль, даже если они содер- жат объявления открытых доменов. Пример в программе chlle05.pro (листинг 18.5). L Листинг 18.5. Программа сМ1е05.рго L^.. ...^ л. ... ........ ...................... class cList domains ilist ~ integer* static predicates append(ilist, ilist, ilist gen(integer) endclass implement cList clauses append([],L,L). append([H}L1],L2,[H|L3]) append(Ll,L2,L3). gen(0,[]). gen(N,[N|L]):~ N1=N-1, L = gen(Nl). endclass goal Ll - cList::gen(3), L2 = cList::gen(5), cList::append(Ll,L2,L3). Пользовательские конструкторы и деструкторы Система Visual Prolog сама выделяет и инициализирует память во время создания объекта. Но у вас может возникнуть желание определять, как именно создается объ- ект. Например, проинициализировать факты объекта заданными значениями, соз- дать окно на экране или открыть файл. Аналогично, программист может пожелать контролировать, как именно происходит удаление объекта, например, закрывать при этом окна или файлы. Для этой цели в разделе объявлений классов можно объявить явные конструкторы и деструкторы для объектов этого класса. Явные конструкторы создаются путем явных объявлений и описаний предикатов new в классе, а явные деструкторы — путем яв- ных объявлений и описаний предикатов delete. Пример — в программе chlle06.pro (листинг 18.6).
Гпава 18. Классы и объекты 459 ***•*• f*vjv****M*fp**^*4?**.**JW** ч *****/.?•*».«• ”.M»***.*M**»**^%i**:*t*M5*M,*f****t**“*,***^*-*’*’******t’**f*-******T*******’*-* **********’**•****•**’**********-*У’M*************F**'*”-*’*****’Г’-*”*'1' jutti r'"H »r rrrrr* rrrrO »r »r » г* **г» г> »ч» МГ-1Г» »»r»*** г»* * r*r »»» *r*rrr*r***r***r-H»r r r r Hltl, Gr >Г t J class cTest predicates procedure new(string, string) procedure delete() add(string) writeStrings() endclass cTest implement cTest facts - privateDB nondeterm strDB( string) clauses new(StrHello, StrToLoad) write(StrHello), assertz(strDB( StrToLoad)), write("Initial string is loaded into the databaseXn"), write("Constructor endedXn"). delete():- writef("Simple destructor startupXn"), write("Delete all strings from the databaseXn"), retractall (strDB (_J ) , write("Destructor endedXn"). add(STR) assertz(strDB(STR)). writeStrings:- strDB(StringFromDataBase), writef("%s\n", StringFromDataBase), fail. writeStrings. endclass cTest goal 0 = cTest::new( "Simple constructor startupXn", "This is the initial string.\n"),nl,nl, 0:add("Second String"), O:WriteStrings, O:delete. Конструкторы и деструкторы не могут завершаться неуспешно; они должны быть процедурами. Предикаты new и delete имеют свойства обычных предикатов-членов, но они имеют и свои особенности.
460 Часть IV. Программирование на Visual Prolog В предложениях предиката new можно вызывать конструкторы базовых классов, ис- пользуя следующий синтаксис: base_clasS—name::new() В классе может быть объявлено несколько конструкторов и деструкторов с разным количеством аргументов, принадлежащих различным доменам. Прежде чем ссылаться на объект класса, объект следует создать, вызвав какой-либо из конструкторов new, объявленных в классе. Компилятор это проверяет: □ если предикат new не объявлен явно в классе, используется конструктор new по умолчанию; □ если явный конструктор new объявлен в классе, конструктор new по умолчанию не может быть вызван явно; □ если конструктор или деструктор завершается ошибкой при исполнении, состоя- ние объекта не определено. Явно объявленный предикат new может быть вызван двумя разными способами: 1. Как конструктор объектов класса. Синтаксис таков: Created—Obj ect__Identifier = class_name : : new [ (arguments) ] Например: О = child::new, % Вызов new как конструктора При вызове конструктора предикат new создает новый объект (экземпляр класса) и возвращает ссылку на созданный объект. 2. Как обычный предикат-член класса. Синтаксис таков: [Object_Identifier :] [class—name :: ] new[(arguments)] Например: О:parent::new, % Вызов new как предиката-члена % родительского класса Когда new вызывается как предикат-член, он не создает нового экземпляра класса. Аналогично, явно объявленный предикат delete может быть вызван двумя разными способами: 1. Как деструктор объектов класса. Синтаксис таков: Object_Identifier : [class—name ::] delete[(arguments)] Например: O:parent::delete, 2. Как обычный предикат-член класса. Когда delete вызывается как предикат-член, он не уничтожает объект. Синтаксис таков: class—name :: delete!(arguments)] Пример: parent: .-delete () ,
Гпава 18. Классы и объекты 461 Абстрактные классы Абстрактный класс — это определение класса без реализации. Абстрактные классы используются только для наследования от них подклассов и необходимы для пред- ставления основных концепций (определения интерфейсов). Абстрактные классы задаются ключевым словом abstract, которое предшествует объявлению класса. Синтаксис таков: abstract class <class_name> [: <base_class_list>] {[protected] domains <domains_declarations>} {[protected] predicates <predicates_declarations>} endclass [<class__name>] Например, если вы хотите создать браузер, который может работать с различными типами данных, то при открытии ему надо передать объект, чьи методы браузер может вызывать для получения и передачи данных. Используя абстрактный класс, вы можете сообщить браузеру, какие предикаты он может вызывать. abstract class cBrowselnterface predicates string get_Current() next() prev() endclass class cDBinterface : cBrowselnterface predicates new ([3b__Selector, Chain) string get_Current() next() prev() endclass class cFilelnterface : cBrowselnterface predicates string get_Current() next() prev() endclass class eBrowser predicates new(cBrowselnterface) endclass Другой пример вы найдете в каталоге <CDROM>:\RUN\OoP\Test\ABSTRACT.PRO. • Если абстрактный класс наследует базовый, тот тоже должен быть объявлен абстрактным.
462 Часть IV. Программирование на Visual Prolog • В абстрактных классах нельзя объявлять факты и статические предикаты. • Абстрактные классы не могут быть заданы в списке базовых классов для реали- зации класса. • Объявления всех абстрактных классов должны быть включены в главный модуль. Начиная с версии Visual Prolog 5.2, классы системы Visual Prolog гораздо более со- вместимы с СОМ. Абстрактные классы предоставляют такую же VTABLE, как и СОМ-интерфейсы. Защищенные предикаты, домены и факты В Visual Prolog существует возможность объявить, имеется ли доступ к предикатам, доменам и фактам извне класса. По умолчанию, все предикаты, домены и факты, объявленные в разделе объявления класса, — открытые. Это означает, что они мо- гут быть вызваны из любого программного модуля, который включает объявление класса. Права доступа, устанавливаемые по умолчанию, можно изменить, если перед ключе- вым словом facts, predicates или domains в соответствующем объявлении раздела поставить ключевое слово protected. Защищенные данные и методы доступны толь- ко внутри класса, в котором они объявлены, и в классах, непосредственно унаследо- ванных от этого класса. Примером использования защищенных предикатов являются обработчики событий, предикаты которых могут переопределяться в производных классах, но вызывать которые извне не имеет смысла: class window protected predicates onUpdate(ret) onCreate(long) endclass Управление доступом в производных классах Важнейшим для построения иерархии объектов является наследование, — это то, что позволяет нам многократно использовать код. Определив методы на одном уровне, можно использовать их на более низких уровнях. Если класс наследует от других классов, мы говорим, что этот класс — производный. Visual Prolog поддерживает так называемое множественное наследование. Это означа- ет возможность наследовать класс от нескольких базовых классов сразу. Полученный производный класс, таким образом, наследует свойства сразу нескольких классов,
Гпава 18. Классы и объекты 463 прямым наследником которых он является. Заметьте, что это превращает иерархию наследования (или иерархию производных классов) в направленный (ацикличный) граф наследования. А в случае одиночного наследования иерархия наследования может быть представлена как направленное дерево иерархии классов. Когда вы объявляете производный класс (например, d), вы можете перечислить не- сколько базовых классов (например, pi, Р2, РЗ и Р4) через запятую в списке базовых классов как в объявлении, так и в реализации производного класса d. На- пример: class D : Pl, Р2 и implement D : РЗ, Р4 Когда базовые классы Р1 и Р2 заданы в объявлении производного класса d, то^э на- следует от классов Р1 и Р2 все открытые домены, предикаты и факты. Унаследован- ные предикаты и факты становятся открытыми членами класса d и на них можно ссылаться как на члены класса d. С другой стороны, d наследует только возможность использовать унаследованные открытые домены. К унаследованным доменам нельзя обращаться по имени D. Когда базовые классы РЗ и Р4 указаны в реализации класса d, d наследует от рз и Р4 все открытые домены, факты и предикаты, при этом они становятся закрытыми (локальными) для D и могут быть использованы только внутри раздела реализации D. Производный класс d не имеет доступа к закрытым именам, определенным в базо- вых классах. Переопределенные имена могут быть доступны, если, в случае необходимости, ква- лифицировать их с именем класса. Если имя какого-либо члена может быть доступно различными путями в направлен- ном ацикличном графе наследования, то выбирается самый длинный путь. Если два или более предиката в иерархии классов имеют одинаковое имя, но разное количество или тип аргументов, мы говорим, что это имя перегружено. В такой си- туации необходимо рассмотреть область видимости имени, которую мы здесь опре- делили как пространство, в котором доступен данный предикат. Каждое употребле- ние имени для любого члена класса должно быть однозначно. Если имя обозначает предикат, то вызов предиката должен быть однозначным относительно количества и типа аргументов. Доступ к члену базового класса неоднозначен, если выражение доступа указывает более чем на один член. Неоднозначность может быть устранена явным указанием имени соответствующего класса перед именем члена. Обычный синтаксис таков: [ObjectVariable: ] [name__of_class:: ] name_of_member ([arguments]) Все предикаты из разделов объявлений класса, кроме new и delete, виртуальны. И, наоборот, все предикаты, объявленные в разделе реализации, становятся не вир- туальными.
464 Часть IV. Программирование на Visual Prolog Объектные предикатные значения Visual Prolog поддерживает понятие объектных предикатных значении. Объектные предикатные значения — это обобщение предикатных значений (см. разд. "Преди- катные значения" гл. 17). Объектное предикатное значение — это объектный (неста- тический) предикат. Оно противоположно "обычным” предикатным значениям, ко- торые либо глобальные, либо локальные, либо являются статическими предикатами (предикатными значениями). Объектные предикатные значения объявлены как экземпляры объектных предикат- ных доменов (см. разд. "Объявления объектных предикатных доменов" данной главы). Извне объекта объектное предикатное значение выглядит как "обычное", т. к. объ- ект, которому оно принадлежит, инкапсулирован самим объектным предикатным значением. Таким образом, объектное предикатное значение состоит как из кода, так и из ссылки на объект, которому код принадлежит. Следовательно, вызов объ- ектного предикатного значения выглядит в точности так же, как и вызов "обычного" предикатного значения: он просто применяется к его аргументам. Но выполнение будет, тем не менее, происходить в контексте объекта, к которому относится объ- ектное предикатное значение. Итак, основная причина предпочтения объектных предикатных значений "обычным" состоит в том, что выполнение будет происходить в контексте указанного объекта. Для иллюстрации этой семантики рассмотрим пример; объявим объектный преди- катный домен: global domains objectIntInt = object procedure integer (integer) Домен objectintint объявляет объектные (нестатические) предикаты. Эти предика- ты (функции) имеют один аргумент домена integer и возвращают значение integer. Их режим детерминизма — procedure. А теперь давайте объявим предикат этого до- мена. Так как значение предиката такого домена должно быть объектным предика- том-членом, оно должно быть объявлено в классе: class cLast predicates last : objectIntInt endclass cLast Для иллюстрации того, что last действительно является объектным предикатом- членом, мы сделаем так, чтобы он возвращал значение в зависимости от состояния объекта. Фактически, мы "просим” его возвращать тот параметр, с которым он был последний раз вызван. Таким образом, мы сохраняем в факте параметр от одного вызова к другому: implement cLast facts % invariant: "lastparameter" holds the parameter value % from the previous invocation of ‘’last’’ % initially assume 0 as value from ’’last" invocation single lastParameter{integer Last)
Глава 18. Классы и объекты 465 clauses lastParameter(0). last(ThisParameter, LastParameter) lastParameter(LastParameter) , assert(lastParameter(ThisParameter)). endclass cLast До сих пор в этом примере интересен был лишь способ объявления предиката last. И прежде чем мы действительно используем last как объектное предикатное значе- ние, попробуем использовать его как нормальный объектный предикат-член: predicates testlО — procedure () clauses testl() : - 01 = cLast::new(), 02 = cLast::new()f _1 = Ol:last(l), _2 = 02:last(2), Vl = 01:last(3), V2 = 02:last(4), writef("Vl ^ %, V2 = %", Vl, V2) , nl, 01:delete(), 02:delete(). Если мы вызовем testl, он сначала создаст два объекта 01 и 02. Затем он вызовет last объекта 01 с параметром 1 и last объекта 02 с параметром 2. Оба объекта (01 и 02) будут хранить свой параметр в соответствующих экземплярах параметра lastParameter. Теперь, когда last будет вызван снова, мы получим 1 и 2, соответст- венно. Вызов testl даст на выходе: VI = 1, V2 = 2 Теперь сделаем то же самое, но на сей раз будем использовать предикаты как значе- ния, хранимые в переменных: predicates test2() — procedure () clauses test2() 01 = cLast::new(), 02 = cLast::new() , Pl = 01: last, % Pl и P2 связаны с экземплярами P2 - 02:last, % объектного предикатного домена objectIntint „1 = Pl(l), _2 = P2 (2), Vl = Pl (3), V2 = P2 (4) ,
466 Часть IV. Программирование на Visual Prolog writef(’’Vl = %, V2 = %", VI, V2) , nl, 01:delete(), 02:delete(). В первую очередь обратите внимание на то, что объектные предикатные значения Р1 и Р2 содержат и объект (01 или 02), и предикат (last). Вызов объектного предикат- ного значения, однако, полностью идентичен вызову "обычного" предикатного зна- чения, т. е. вы не применяете объектное предикатное значение к объекту, а просто вызываете его с соответствующими аргументами. Результаты выполнения test2 и testl в точности одинаковы: выполнение Р1 (1) сохранит 1 в факте last Parameter объекта 01. Таким же образом следующий вызов Р1 возвратит значение, сохраненное в факте lastparameter объекта 01. Аналогично Р2 ссылается на 02. Объектные предикатные значения как минимум так же полезны для функций обрат- ного вызова, как "обычные" предикатные значения (см. обсуждение функций обрат- ного вызова в описании предикатных значений гл. 17). Преимущество использования объектных предикатных значений (над "обычными" предикатными значениями) за- ключается в том, что вызов возвращается в указанный контекст, а именно указан- ному объекту, которому принадлежит этот вызов. Это позволяет работать с несколь- кими разными вызовами одного типа, ибо каждый вызов будет возвращаться в свой собственный контекст. Рассмотрим пример. Пусть мы имеем некоторые "сущности", которые хотят знать, когда некоторое значение изменится. (Для простоты пусть это будет целое значе- ние.) "Сущности" должны быть асинхронно оповещены об изменениях значения. Для этого они регистрируют "слушателя" (слушающий процесс) dataReady для ис- точника данных. В этом примере мы передаем новое значение вместе с оповещени- ем о готовности данных, но в случае более сложных данных мы могли бы заставить "слушателя" добывать данные самостоятельно. Мы представляем источник данных объектом класса cDataReadySource. Если наши данные состоят из частей, которые самостоятельно могут "стать готовыми", то тогда мы можем создать по экземпляру класса cDataReadySource на каждую часть данных, тем самым позволяя слушать только необходимые оповещения. Класс cDataReadySource поддерживает регистрирование и освобождение "слушателей". Он также имеет предикат для установления интересующего значения. "Слушатели" представлены объектными предикатными значениями (т. е. объектны- ми функциями обратного вызова). class cDataReadySource domains dataReadyListener = object procedure (cDataReadySource EventSource,integer NewValue) predicates addDataReadyListener(dataReadyListener Listener) — procedure (i) removeDataReadyListener(dataReadyListener Listener) — procedure (i) predicates setvalue(integer NewValue) — procedure (i) endclass cDataReadySource
Глава 18. Классы и объекты 467 Реализация весьма простая. Текущие зарегистрированные “слушатели" хранятся в фактах и, когда данные изменяются, мы оповещаем их об этом. implement cDataReadySource facts % Invariant: listener_db contains the currently registered % listeners (multiple registrations are ignored) listener_db(dataReadyListener Listener) clauses addDataReadyListener(Listener):- listener_db(Listener), % уже зарегистрированный । • addDataReadyListener(Listener):- assert(listener_db(Listener)). removeDataReadyListener(Listener) :- retractAll (listener__db (Listener)) . predicates datalsReady(integer NewValue) — procedure (i) clauses datalsReady(NewValue) this(This), listener_db(Listener), Listener(This, NewValue), fail. datalsReady (_). clauses setValue(NewValue) datalsReady(NewValue). endclass cDataReadySource Давайте попытаемся использовать этот класс. Пусть у нас есть система, которая подсчитывает число активных пользователей. Одно из мест, где используется этот счетчик, — это окно статуса, которое показывает значение счетчика. Допустим, есть глобальный предикат getUserCountSource, который возвращает объект cDataReadySource, соответствующий значению счетчика: global predicates cDataReadySource getUserCountSource() — procedure () Мы реализуем наше окно статуса как класс cstatusWindow. Объявление cstatusWindow не представляет интереса в этом контексте; нам важна только реали- зация. В разделе реализации, в конструкторе класса регистрируем '’слушателя" dataReadyListener с источником данных о счетчике пользователей. В деструкторе мы, естественно, освобождаем “слушателя". implement cstatusWindow predicates updateWindow(integer NewValue) — procedure (i)
468 Часть IV. Программирование на Visual Prolog clauses updateWindow(NewValue) ... % обновляющий окно код predicates onUserCountChanged : cDataReadySource::dataReadyListener clauses onUserCountChanged(^Source, NewValue) updateWindow(NewValue). clauses new() :- UserCountSource = getUserCountSource()f UserCountSource:addDataReadyListener(onUserCountChanged). delete() :- UserCountSource = getUserCountSource(), UserCountSource:removeDataReadyListener(onUserCountChanged). endclass cStatusWindow Как видим, не важно, сколько окон статуса мы создадим, все они будут обновляться при изменении счетчика пользователей, ч Объявления объектных предикатных доменов Объектный предикатный домен объявляет нестатические (объектные) предикаты- члены. Объектные предикатные домены можно использовать для объявлений объектных предикатов, которые могут быть использованы как объектные предикатные значения. Эти значения можно передавать в качестве аргументов другим предикатам. Объектные предикатные домены можно объявлять вне и внутри классов, но, в отли- чие от "обычных” предикатных доменов, объектные предикатные домены можно использовать для объявлений объектных предикатных значений только внутри клас- сов. Причина в том, что при передаче объектного предикатного значения в качестве аргумента, он должен содержать ссылку на фактический экземпляр класса (объект), которому принадлежит предикатное значение. Объявление объектного предикатного домена: [global] domains PredDom = object DetermMode [ReturnDom] (ArgList) [- fFlowPattern]] [Language] Здесь ключевое слово object обозначает объявление объектного предикатного доме- на. Это единственное отличие от синтаксиса объявления ’’обычных" предикатных доменов, детально описанного в разд. "Предикатные домены" гл. 17. Здесь мы лишь вкратце напоминаем используемые термины и указываем различия. PredDom объявляет имя предикатного домена. DetermMode указывает режим детерминизма. ReturnDom определяет домен возвращаемого значения для функций. ArgList опреде- ляет домен для аргументов. Flow Pattern задает поток аргументов. Он должен иметь
Гпава 18. Классы и объекты 469 значение i для входных аргументов и о — для выходных. Только один поток пара- метров может быть задан. Если он отсутствует, неявно принимается поток по умол- чанию со всеми входными аргументами. Синтаксис Language такой: language { pascal | stdcall [ asm | с I syscall I prolog} По умолчанию соглашение о вызовах — pascal. Обратите внимание на различие с объявлением предикатов, где по умолчанию — prolog. Ограничение Если объектное предикатное значение объявлено как экземпляр объектного преди- катного домена с соглашением о вызовах prolog, syscall, с или asm, тогда это объектное предикатное значение не может быть вызвано с аргументами, если оно передается в переменной. Например, объявление объектного предикатного домена для детерминированного предиката, принимающего в качестве аргумента целое и возвращающее целое, будет таким: domains list_process = object determ integer (integer) — (i) Программа chlle07.pro (листинг 18.7) демонстрирует использование объектного пре- дикатного домена obj_dom для объявления объектного предикатного значения opv_a. <Листинг:;18-.7г:П£0Грамма;рШ':1Йф£рфИ ... .........,«Л/t«»«««»«««« a /,,<г «'i* О «*•*•*♦• И«,««««*rin domains obj__dom ~ object procedure (string) — (i) % Объектный предикатный домен class cl_a predicates opv_a : obj_dom % Объявление объектного предикатного значения opv_a endclass cl_a implement cl_a clauses °Pv„a( S) :- write( S), nl. endclass cl_a class cl_b : cl_a predicates p_ns_b(obj_dom,string) static predicates p_s_b(obj_dom,string) % Статический предикат p_s_b endclass cl_b implement cl_b clauses p_ns_b(OP_value,S) OP value(S), nl.
470 Часть IV. Программирование на Visual Prolog p_s_b(OP_value,S) :- OP_value(S), nl. endclass cl_b predicates p_local(obj—dom, string) — procedure (i,i) clauses p_local(OP_value, S) OP_value(S). goal 0_a - cl_a::new(), 0_a:opv_a (" I am opv_a’"), write ("\t Object Predicate (OP) <opv_a> is called directly.")# nl,nl, OP_value__a = O__a:opv_a, OP_value_a("OP value <opv_a> is called as variable"), nl, p_local(0_a:opv_a, "OP <opv_a> is explicitly specified in predicate argument"),nl, р_1оca1(OP_va1ue_a, "OP value <opv_a> is passed to predicate in variable !"),nl, cl_b: :p__s_b (0_a: opv_a, "OP <opv_a> is specified as argument to static predicate!"),nl, 0_b = cl_b::new(), O_b:p__ns__b (OP__value_a, "OP value <opv_a> is passed in variable to non-static predicate!"), nl, 0_a:delete(), 0_b:delete(). Формальный синтаксис для классов Ниже приведен формальный синтаксис использования классов в Visual Prolog: <class> ::== <class_declaration> <class_implementation> <abstract_class> ::= <abstract_class_declaration> <class_declaration > : <class_declaration_begin> < с1as s_de cla rat i on_body> <class_end> <abstract_class_declaration ::= <abstract_class_declaration_begin> <abstract_class_declaration_body> <class end>
Гпава 18. Классы и объекты 471 <class__declaration_begin > :: = CIASS <class_header> <abstract__class_declaration_begin> : : ABSTRACT CLASS <class_header> <class_header> ::= <class_name> [: <base_class_list>] <class_name > ::= identifier <base_class_list > <base_class_name_l> [, <base__class__name_2>] * <class_declaration_body> ::= <class_declaration_section>* <abstract_class_declaration_body> ::= <abstract_class__declaration_section>* <class__declaration_section> : : = <class_declaration_domains__section> I <class_declaration_facts_section> I <class_declaration_predicates_section> <abstract_class_declaration_section> ::= <class_declaration_domains_section> I <abs_class_declaration_predicates_section> <class__declarat ion_domains_section> ; : = [PROTECTED] DOMAINS <domains_declarations> <class_declaration_facts_section > ::= [STATIC] [PROTECTED ] FACTS [ - <facts_section_name>] <facts_declarations> <facts_section_name> ::= identifier <class_declarationjpredicates_section [STATIC] [PROTECTED] PREDICATES <predicates_declarations> <abs_class_declaration_jpredicates_section> [PROTECTED] PREDICATES <predicates__declarat ions> <class_end> : : = ENDCLASS [ <class_name> ] <class_implementation> ::= <class_implementation_begin> ^class_implementation_body> <class end>
472 Часть IV. Программирование на Visual Prolog <class_implementation_begin> ::= IMPLEMENT <class_header> <class_implementation_body> : <class_implementation_section>* <class_implementation_section> ::= <class_implementation_domains_section> I <class_implementation_facts_section> | <class_implementation_joredicates_section> I <CLAUSES_section> <class_implementation_facts_section> ::= [STATIC ] FACTS [ — <facts_section_name>] <facts_declarations> <class_implementation_predicateS—section> ::= [STATIC] PREDICATES <predicates_declarations> Здесь: □ квадратные скобки указывают необязательные пункты; □ фигурные скобки указывают на произвольное число пунктов (ноль или более пунктов); □ символ (|) обозначает, что один из пунктов, разделенных этим символом, должен быть выбран; □ символ "звездочка" (*) указывает на произвольное число непосредственно пред- шествующих пунктов (ноль или более пунктов). Эти правила описывают только синтаксис использования классов в Visual Prolog и не касаются семантики.
ГЛАВА 1 9 Запись, чтение и файлы В этой главе мы впервые рассмотрим базовые встроенные предикаты для чтения и записи. Далее опишем, как в Visual Prolog работает файловая система, и покажем, как вы можете направлять ввод и вывод в файлы. Мы также рассмотрим домен file. Запись и чтение Рассмотрим стандартные предикаты ввода/вывода, включая предикаты, которые по- зволяют выполнять операции с файлами. Запись В Visual Prolog включены три стандартных предиката для вывода. Это: □ предикат write; О предикат nl; □ предикат writef. Предикат write может быть вызван с произвольным числом аргументов: write(Paraml, Param2, РагатЗ, . . ., ParamN) % (i,i,i,...,i) Эти аргументы могут быть либо константами из стандартных доменов, либо пере- менными. Если это переменные, то они должны быть входными параметрами. Стандартный предикат nl (от англ, new line — новая строка) всегда используется вместе с предикатом write. Он обеспечивает переход на новую строку на экране дисплея. Например, следующие подцели: pupil(PUPIL, CL), write(PUPIL," is in the ",CL,” class”), nl, write ("--------------------------------”)
474 Часть /И Программирование на Visual Prolog могут привести к выводу на экран такого результата: Helen Smith is in the fourth class — — — — — — — —, а цель: « • • • f write(’'List 1= ”, Ll, ", List2= ", L2) может дать: Listl- [cow,pig,rooster], List2= [1,2,3] . В свою очередь, если My_sentence связана с записью sentence(subject(john),sentence_verb(sleeps)) то, выполняя следующую программу: domains . sentence - sentence (subject, sent ;ce__verb) subject = subject(symbol); ........ sentence_verb = sentence_verb(verb); ...... verb = symbol clauses • • • • write( " SENTENCE = ", My_sentence). вы сможете увидеть на дисплее: SENTENCE = sentence(subject(john),sentence_verb(sleeps)) Обратите внимание на наличие в строках обратного слэша (\). Это управляющий символ. Чтобы напечатать непосредственно символ \ (обратный слэш), вы должны ввести два обратных слэша подряд. Например, для порождения строки пути к файлу в DOS A:\PROLOG\MYPROJS\3\4YFILE.PRO в программе на Visual Prolog нужно ввести а: \\prolog\\myprojs\\myfile.pro. За обратным слэшем может следовать какой-либо из специальных символов управ- ления печатью: □ 'п' — новая строка и возврат каретки; □ 11’ — табуляция; □ ' г' — возврат каретки. Также за обратным слэшем могут следовать до трех десятичных цифр, задающих специальный код ASCII. Однако не следует вставлять последовательность \0 в стро- ку, если в этом нет необходимости. Visual Prolog использует те же соглашения отно- сительно строк, завершающихся нулем, что и язык С. Будьте осторожны с опцией \г. Она устанавливает текущую позицию вывода обрат- но на начало текущей строки, но, если вы случайно сделаете это между печатью двух различных строк, это может произойти так быстро, что первая из них будет стерта
Гпава 19. Запись, чтение и файлы 475 прежде, чем вы ее заметите. Также, если вы печатаете что-то, что не умещается на одной строке, опция \г установит курсор на начало той же самой строки, на кото- рой этот вывод был начат. Предикат write сам по себе не предоставляет всю полноту функций управления пе- чатью для таких сложных объектов, как, например, списки, но написать программы, которые вам их предоставят, просто. Следующие четыре небольших примера демон- стрируют эти возможности. Примеры использования предиката write Эти примеры показывают, как можно использовать предикат write для того, чтобы получить возможность выводить на печать такие объекты, как списки и сложные структуры данных. 1. Программа сЫ2е01.рго (листинг 19.1) печатает список без открывающей [ и за- крывающей ] квадратных скобок. Листинг 19.1. Программа ch12e01.pro domains integerlist = integer* namelist = symbol* predicates writelist(integerlist) writelist(namelist). clauses writelist([]). writelist([HIT]):- write(H, " "h writelist(T). Обратите внимание, как эта программа использует рекурсию для обработки спи- ска. Попробуйте ввести в программу и исполнить такую цель: writelist ([1, 2, 3, 4]) . 2. В следующем примере программа сЫ2е02.рго (листинг 19.2) выводит элементы списка не более чем по пять элементов на строке. [ Листинг 19.2. Программаch12e02.pro г » Л Н• ai> tr г> г> »»>«*.« • *** •* £ 1Ги> 1Й V » tflrr»it* nAtoi *4*и» trVtf• »*ir*b*1 domains integerlist = integer* predicates writelist(integerlist) write5(integerlist,integer)
476 Часть IV. Программирование на Visual Prolog clauses writelist(NL):- nl, writes(NL,0)fnl. write5(TL,5):- !, nl, writes(TL, 0). writeS([H|T],N):-!, write(H," ”), N1=N+1, writeb(T,Nl). write5 ([],_) . Если вы дадите программе такую цель: writelist([2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]). Visual Prolog ответит: 2 4 6 8 10 12 14 16 18 20 22 3. Пример предиката, который выводит сложные структуры данных в более удоб- ном для чтения виде, приведен в программе chl2e03.pro (листинг 19.3). Про- грамма выводит объекты типа: plus(mult(х, number(99)), mult(number(3), x)) в виде: x*99+3*x Это называется инфиксной записью. . 4 VT^''***'"^*"'""***4 **,.* ***•_1 * М * * 1 4 ? ?* ! ? .* * 1 *.» » • М *.' * *•*<•»» “V* **$?'*.•*" !»»»»»« к кВ <* *»*^**.i |,ВВВ1к*1«Ь»ккк к'Вк'к В к в В * • В • В 'к *'• В к « В к '** * к в В ABVB 1Вв В В *В *В в *'*.* В k В к>кВ^ВВкк*^Вк*ВВкВк*'к*|ВкккВкГкв'кв1'*ВВ'Ь к,вАвВВВВВ «BBBABtBBBtiB'BB к* *>* lA^B IB *ВВ*вВВВВВ IWk*k к * кВ к * *• к В»к I** * * * * "кВ к в’ввЬк в+Ь».аВк В| domains ехрг == number (integer); х; log(expr); plus(expr, ехрг); mult(ехрг, ехрг) predicates writeExp(ехрг) clauses writeExp(x):- write('x'). writeExp(number(No)) write(No). writeExp(log(Expr)) write("log(”),
Глава 19. Запись, чтение и файлы 477 writeExp(Expr), write(’)'). writeExp (plus (Ul, U2)) :-* writeExp(Ul), write('+'), writeExp(U2). writeExp(mult(Ul,U2)) : - writeExp(Ul), write(1 *’), writeExp(U2). 4. Программа ch!2e04.pro (листинг 19.4) похожа на программу chl2e03.pro. Листинг19.4. Программа ch! 2e04.pro domains sentence - sentence(nounphrase, verbphrase) nounphrase = nounp(article, noun); name(name) verbphrase = verb(verb); verbphrase(verb, nounphrase) article, noun, name, verb = symbol predicates write_sentence(sentence) write_nounphrase(nounphrase) write_verbphrase(verbphrase) clauses write_sentence(sentence(S,V)) write_nounphrase(S), write_verbphrase(V). write_nounphrase(nounp(A,N)) write (A, ' ’ ,N, ’ .’ ) . write_nounphrase(name(N)) write(N,' '). write_verbphrase(verb(V)):- write(V,’ ’). write_jverbphrase (verbphrase (V, N) ) write(V,’ ’), write_nounphrase(N). Попробуйте программу chl2e04.pro с такой целью: write_sentence(sentence(name(bill), verb(jumps))). Упражнение Напишите программу на Visual Prolog, которая получала бы список адресов, содер- жащихся в программе в виде предложений: address("Sylvia Dickson", "14 Railway Boulevard","Any Town", 27240).
478 Часть IV. Программирование на Visual Prolog и выдавала бы их в виде, подходящем для почтового отправления: Sylvia Dickson 14 Railway Boulevard Any Town 27240 Предикат writef/* Предикат writef позволяет выполнить форматированный вывод; он имеет следую- щий формат: writef(FormatString, Argl,Arg2,Arg3, ...,ArgN) % (i,i,i,i,...,i) Аргументы Argl, ..Argn должны быть константами или связанными перемен* ными, принадлежащими стандартным доменам. Сложные домены форматировать нельзя. Строка форматирования содержит обычные символы и форматные специфика- торы; обычные символы печатаются без модификации, а форматные спецификаторы имеют следующую форму: %-m.pf Символы спецификаторов формата, следующие за знаком процента (%), являются необязательными и имеют значения, описанные в табл. 19.1. Таблица 19.1. Значения символов спецификаторов формата Символ Значение Дефис (-) Показывает, что поле выравнивается слева (по умолчанию выравнивается справа) m поле Десятичное число, определяющее минимальную длину поля р поле Десятичное число, описывающее либо точность представления с плаваю- щей точкой, либо максимальное число символов, печатаемых в строке f поле Описывает форматы, отличные от форматов, принятых по умолчанию для данного объекта. Например, поле f может описывать то, что целые числа должны быть напечатаны как беззнаковые или шестнадцатеричные числа Visual Prolog распознает спецификаторы формата поля f, описанные в табл. 19.2. Таблица 19.2. Спецификаторы формата поля f Спецификатор формата Описание f Вещественные в представлении с фиксированной точкой (типа 123.4 или 0.004321)
Гпава 19. Запись, чтение и файлы 479 Таблица 19.2 (окончание) Спецификатор формата Описание е Вещественные в экспоненциальном представлении (типа 1.234е2 или 4.321 е-3) g Вещественные в формате f или е (формат по умолчанию) d Целые, как знаковые десятичные числа D Целые, как знаковые длинные десятичные числа и Целые, как беззнаковые десятичные целые U Целые, как беззнаковые длинные десятичные целые о Целые, как восьмеричные числа О Целые, как восьмеричные длинные числа х Целые, как шестнадцатеричные числа X Целые, как шестнадцатеричные длинные числа с Целые, как символы (char) s Как строки (simbols и string) R Как числа ссылки во внешних базах данных (только для домена ref) В Как бинарные (только для домена binary) Р Как предикатное значение Домен ref, предикатные домены и предикатные значения описаны в гл. 17. Для целых доменов заглавная буква в спецификаторе формата обозначает, что соот- ветствующий объект принадлежит типу long. Если буква формата не обозначена, Visual Prolog автоматически подберет один из подходящих форматов. Примеры форматированного вывода: 1. Следующая программа chl2e05.pro (листинг 19.5) иллюстрирует действие различ- ных спецификаторов формата при форматированном выводе с помощью writef. & Листинг 19.5. Программа chi 2е05.рго goal А = one, В = 330.12, С = 4.3333375, D = "one two three", writef ГА = ’ %-7 1 * * * * * * В \nB - '%8.le’\n",А,В) , writef("A = \nB = '%8.4e’\n",A,B),nl,
480 Часть IV. Программирование на Visual Prolog writef("С = ’%-7.7g’ \nD - ’%7.7’\n",C,D), writef("C = '%-7.0f \nD = ’%O'\n",C,D), writef("char: %cf decimal: %d, octal: %o, hex: %x", ’a’, 'a*,’a', 'a'). После запуска эта программа создаст следующий вывод: А - 'one ' В = ' З.ЗЕ+02' А - 'one' В - 'З.ЗО12Е+О2' С = ’4.3333375' D == 'one two' С = '4 D = 'one two three' char: a, decimal: 97, octal: 141, hex: 61 2. Пример, показывающий, как можно использовать writef для форматного выво- да, приведен в программе chl2e06.pro (листинг 19.6). ^ Листинг 19.6. Программа ch12e06.pro : -< - ‘ .. л •?; ?'' ‘t.? ' •. • •:•? :. ?? • . . v-' <>•••• •' ’li': •••'- • : <'••< ' •• • 1 I Vifr 14 I V| I ltd ........ «.•• I »<»»»»» t »» 4 »» » ' » 4 » 1 1*1» I ... >»T facts person(string,integer, real) clauses person("Pete Ashton",20,11111.Ill). person("Marc Spiers",32,33333.333). person("Kim Clark",28,66666.666). goal % Name, выровненное влево, до 15 символов длиной % Age, выровненное вправо, длиной 2 символа % Income, выровненное вправо, 9 символов, % 2 символа после запятой, представление с фиксированной точкой person(N, А, I), writef("Name= %-15, Age= %2, Income^ $%9.2f \n",N,A,I), fail f true. Результат программы будет следующим: Name- Pete Ashton , Age- 20, Income^ $ 11111.11 Name= Marc Spiers , Age= 32, Income- $ 33333.33 Name= Kim Clark , Age= 28, Income= $ 66666.67
Гпава 19. Запись, чтение и файлы 481 Чтение Visual Prolog включает в себя несколько стандартных предикатов для чтения. Из них четыре основных: □ readln ~ для чтения всей строки символов; □ readint — для чтения целых значений; П readreal — для чтения вещественных значений; □ readchar — для чтения символьных значений. И дополнительно — readterm — для чтения любых термов, включая составные объ- екты . Все эти предикаты могут быть переориентированы для чтения из файлов. Другой, более специализированный предикат, относящийся к категории чтения, — это file str — для чтения всего текстового файла в строку. Предикат readln/1 Предикат readln читает текстовую строку, используя формат: readln(Line) % (о) Домен для переменной Line должен быть строкового типа. Перед тем как вы вызо- вете readln, переменная Line должна быть свободна, readln считывает до 254 сим- волов (плюс возврат каретки) с клавиатуры и до 64 Кбайт с других устройств. Если во время ввода с клавиатуры нажать клавишу <Esc>, то readln потерпит неудачу. Предикаты readint/1, readreal/1, readchar/1 Предикат readint считывает целое значение, используя формат: readint(X) % (о) Домен для переменной х должен быть целого типа, а х перед вызовом должна быть свободна. Предикат readint будет считывать целое значение с текущего входного устройства (возможно, с клавиатуры), пока не будет нажата клавиша <Enter>. Если считанная строка не соответствует синтаксису целых, readint терпит неудачу, и Visual Prolog вызывает механизм поиска с возвратом. Если во время ввода с клавиа- туры нажать клавишу <Esc>, то readint также терпит неудачу. Предикат readreal работает в соответствии со своим названием: он считывает веще- ственные числа (аналогично тому, как readint считывает целые). Предикат readreal использует следующий формат: readreal(X) % (о) Домен для переменной X должен быть вещественного типа, а х должна перед вызо- вом быть свободна. Предикат readreal будет читать вещественные значения с теку- щего устройства ввода, пока не будет нажата клавиша <Enter>. Если ввод не соот- ветствует обычному синтаксису вещественных чисел, то readreal терпит неудачу. Если во время ввода нажать клавишу <Esc>, readreal также терпит неудачу.
482 Часть IV, Программирование на Visual Prolog Предикат readchar считывает один символ с текущего устройства ввода, используя формат: readchar(ChaгРагam) % (о) Перед вызовом предиката readchar переменная CharParam должна быть свободной и принадлежать домену char. Если текущим устройством ввода является клавиатура, readchar ждет, пока будет нажат один символ, после чего возвращает его. Если во время ввода нажать клавишу <Esc>, то readchar терпит неудачу. Предикат readterm/2 Предикат readterm считывает сложные термы. Он имеет следующий формат: readterm(DornainName, Term) % (i, i) Вы вызываете readterm с двумя аргументами: именем домена и термом, readterm читает строку и превращает ее в терм указанного домена. Если строка не имеет вид объекта, сформированного предикатом write, то readterm даст ошибку. Стандарт- ный предикат readtermerror может быть использован для получения информации об ошибках, произошедших в readterm. Предикат readterm может использоваться для обработки термов в текстовых файлах. Например, вы можете создать собственную версию consult. Предикат file_str/2 Предикат file str читает символы из файла в строку, или создает файл и записыва- ет в этот файл строку. Он использует формат: file_str(Filename, Text) % (i, о), (i, i) Если перед вызовом file str переменная Text свободна, file_str читает символы из файла Filename, пока не встретит символ конца файла (обычно это комбинация клавиш <Ctrl>+<Z>). Содержимое файла Filename пересылается в переменную Text. Например, вызов: file_str(”Т.DAT", My_text) свяжет My text с содержимым файла T.DAT. При этом строка может содержать сим- волы возврата каретки. Читаемый файл не может превышать максимальную длину строки, которая равна 64 Кбайтам на 16-битных платформах. Если Myjtext связана с текстом из T.DAT, то file_str("Т.ВАК”, My_text) создаст файл с именем Т.ВАК, который содержит текст из T.DAT. Если Т.ВАК уже сущест- вует, он будет перезаписан. Рассмотрим несколько примеров использования стандартных предикатов чтения, демонстрирующих, как можно использовать стандартные предикаты чтения для ра- боты со сложными структурами данных и вводимыми списками. 1. Программа ch!2e07.pro (листинг 19.7) показывает, как извлекать сложные струк- туры данных из входных строк.
Глава 19. Запись, чтение и файлы 483 |Листинг 19.7. Программа ch12e07.pro ........................,....,,,.,..,.,»,..1. ...... „„...„«.и, .... ...Л......\ ,...,,.,,>,.,..,.4 domains person = p(name, age, telno, job) age = integer telno, name, job = string predicates readperson(person) run clauses readperson(p(Name,Age,Telno,Job)) write("Which name ? "), readln(Name), write("Job ? "), readln(Job), write("Age ? ”), readint(Age), write("Telephone no ? "), readln(Telno). run : - readperson(P),nl,write(P),nl,nl, writefls this compound object OK (y/n)"), readchar(Ch),Ch=’y’, i * • run : - nl,write("Alright, try again"),nl,nl,run. goal run. 2. В данном примере (листинг 19.8) показано использование предиката readint для чтения целых чисел и преобразования их в список. Он читает одно целое число на строке, пока вы не введете не целое (например, клавишу <Х>). После этого readint потерпит неудачу, и Visual Prolog выведет список на экран. М Ii* I .. . IM'l.lMt" » FS* ... ..•>...**А*”»***.О...... ...... .4. ............. ....... ...... ..Г. », ...» .1. т. •..»»»»»•• FH [ Листинг 19.8. Программа ch12e08.pro ..... .Л,;,.i.44.Л. ,..i, » л, ..........л domains list - integer* predicates readlist(list) clauses readlist([HIT]):— write("> "), readint(H),’, readlist(T). readlist([]).
484 Часть IV. Программирование на Visual Prolog goal (n * * * * * * * * * * * * * * * Integer List ***************** и , nl f write (" Type in a column of integers, like this:") , nl, write (" integer (press ENTER)\n integer (press ENTER)\n”), write(" etc.\n\n Type X (and press ENTER) to end the list.\n\n"), readlist(TheList),nl, write("The list is: ",TheList). Загрузите программу chl2e08.pro и запустите ее. После подсказки введите столбец целых чисел (например, 1234, 567, 89, 0), затем нажмите комбинацию клавиш <X>+<Enter> для окончания ввода. Упражнение Напишите и проверьте предложения для предиката readbnumb, который при вызове: readbnumb (IntVar) преобразует введенное пользователем 16-битное двоичное число вида "1111 0110 0011 0001" в соответствующее целое значение, которое присваивается IntVar. Проверьте вашу работу, написав программу, которая использует предикат readbnumb. Передача двоичных блоков Три стандартных предиката позволяют читать и записывать двоичные блоки или последовательности указанной длины. Все они используют стандартный двоичный домен. Тем самым эмулируется массив, в первом двойном слове (dword в 32-битных версиях Visual Prolog) которого хранится размер этого массива. Полное описание двоичных (binary) доменов см. в гл. 17. Предикат readblock/2 Предикат readblock имеет формат: readblock(Length,BTerm) % (i, о) где Length — количество байтов, которое надо прочитать, а втегт — возвращаемый двоичный терм. Как сказано в гл. 18, на содержимое двоичных термов ограничений нет, терм будет содержать все, что было прочитано из файла, включая нули и марке- ры окончания файла DOS (комбинация клавиш <CtrI>+<Z>). Текущим устройством ввода должен быть задан файл (см. разд. "Предикат readde- vice/Г данной главы). Если задано Length = 0, тогда readblock пытается считать максимально возможное количество байт с устройства ввода. Если Length больше реального числа оставшихся на чтение байт, readblock выдает ошибку.
Гпава 19. Запись, чтение и файлы 485 Предикат writeblock/2 Предикат writeblock дополняет readblock: writeblock(Length,BTerm) % (i, i) Как и в readblock, на содержимое втегт ограничений нет. Length определяет коли- чество байт для записи; Length = о задает запись всего терма. Для совместимости с предыдущими версиями Visual Prolog, где двоичные блоки пе- редавались как строки, writeblock может быть вызван со строковым аргументом вместо двоичного терма. В этом случае необходимо, чтобы Length указывал пра- вильное число байт для записи. Предикат file_bin/2 Предикат file bin читает весь файл в двоичный терм и наоборот. Он принимает 2 аргумента — имя файла и двоичный терм: file bin (FileName, BinTerm) % (i, о) (i, i) 11 Файловая система в Visual Prolog В этом разделе мы представим вам файловую систему в Visual Prolog и стандартные предикаты, обращающиеся с файлами. Познакомимся с переназначением ввода/вы- вода — эффективным методом связи ввода и вывода с различными устройствами. За небольшими исключениями файловая система работает одинаково в различных вер- сиях Visual Prolog. Visual Prolog использует current_read_device (текущее устройство чтения), с которого считывается ввод, и current_write_ device (текущее устройство записи), на которое посылается вывод. Как правило, текущим устройством чтения является клавиатура, а текущим устройством записи — экран дисплея. Однако вы можете назначить дру- гие устройства. Например, ввод может читаться из файла, хранимого во внешней памяти (возможно, на диске). Можно даже переопределить устройства текущего ввода и вывода во время исполнения программы. Независимо от того, какими устройствами чтения и записи вы пользуетесь, в про- грамме на Visual Prolog чтение и запись обрабатываются идентично. Для доступа к файлу вы должны сначала его открыть. Файл может быть открыт: □ для чтения; □ для записи; □ для добавления; □ для модификации. Файл, открытый для любого действия, отличного от чтения, должен быть закрыт после завершения операции. В противном случае внесенные в файл изменения мо- гут быть потеряны. Можно открыть несколько файлов одновременно. При этом ввод и вывод могут быть быстро переназначены между открытыми файлами. Открытие и закрытие файлов занимает намного больше времени, чем переназначения потоков данных между ними.
486 Часть IV. Программирование на Visual Prolog Когда Visual Prolog открывает файл, он связывает символическое имя с действитель- ным именем файла операционной системы и использует это символическое имя для направления ввода и вывода. Символические имена файлов должны начинаться с маленькой буквы и должны быть объявлены в описании домена file. Например: file = filel; source; auxiliary; somethingElse V r В любой программе разрешен только один домен file. Visual Prolog распознает пять встроенных альтернатив file, описанных в табл. 19.3. Таблица 19.3. Встроенные альтернативы домена file Альтернатива Описание keyboard Чтение с клавиатуры (по умолчанию) screen Запись в монитор stdin Чтение из стандартного ввода stdout Запись в стандартный вывод stderr Запись на стандартное устройство для вывода ошибок Эти встроенные альтернативы не должны встречаться в описании file. Открывать и закрывать их не требуется. Обратите внимание, что в стратегии VPI (см. гл. 26) может быть использована только альтернатива screen. Открытие и закрытие файлов Следующие разделы описывают стандартные предикаты для открытия и закрытия файлов. Замечание Во время открытия файла необходимо помнить, что обратный слэш (\), используе- мый для указания подкаталога диска, в DOS-ориентированных версиях Visual Prolog является ESC-символом (управляющим). Поэтому при указании пути доступа файла в программе нужно всегда указывать два обратных слэша (\\). Например, строка: "с:\\prolog\\include\\iodecl.con” представляет путь доступа к файлу: с:\prolog\include\iodecl.con Предикат openread/2 Предикат openread открывает файл OSFileName для чтения, используя формат: openread(SymbolicFileName, OSFileName) % (i, i)
Глава 19. Запись, чтение и файлы 487 Visual Prolog обращается к открытому файлу по символическому имени SymbolicFileName, объявленному в домене file. Если файл не может быть открыт, Visual Prolog выдаст сообщение об ошибке. Предикат openwrite/2 Предикат openwrite открывает файл OSFileName для записи, используя формат: openwrite(SymbolicFileName, OSFileName) % (i,i) Если файл уже существует, то он уничтожается. В противном случае Visual Prolog создает новый файл и помещает его в соответствующем каталоге. Если файл не мо- жет быть создан, Visual Prolog выдаст сообщение об ошибке. Предикат openappend/2 Предикат openappend открывает файл OSFileName для записи в конец файл£. При этом используется формат: openappend(SymbolicFileName, OSFileName) % (i, i) Если файл не может быть открыт на запись, Visual Prolog сообщит об ошибке. Предикат openmodify/2 Предикат openmodify открывает файл OSFileName и для записи, и для чтения; если файл уже существует, он не будет перезаписан, openmodify имеет формат: openmodify(SymbolicFileName, OSFileName) % (i, i) Если система не может открыть OSFileName, Visual Prolog сообщит об ошибке. Для заполнения файла с произвольным доступом предикат openmodify может использо- ваться вместе со стандартным предикатом filepos. Предикат filemode/2 При открытии файла в текстовом режиме предикат filemode устанавливает указан- ный файл в текстовый или двоичный режим, используя формат: filemode(SymbolicFileName, FileMode) % (i, i) Если FileMode = 0, файл SymbolicFileName устанавливается в двоичный режим; если FileMode = 1, то он устанавливается в текстовый режим. В текстовом режиме при записи к новым строкам добавляются символы "возврат каретки'/'перевод строки", а при чтении эта пара символов интерпретируется как новая строка. Carriage return (возврат каретки) = ASCII 13 Line feed (перевод строки) - ASCII 10 В двоичном режиме никаких преобразований не производится. Для чтения двоично- го файла вы можете использовать только предикат readchar или предикаты для дос- тупа к двоичным файлам, которые обсуждаются в гл. 17.
488 Часть IV. Программирование на Visual Prolog Предикат closefile/1 Предикат closefile закрывает указанный файл; он использует формат: closefile(SymbolicFileName) % (i) Этот предикат всегда завершается успешно, даже если файл не был открытым. Предикат readdevice/1 Предикат readdevice переопределяет current_read__device (текущее устройство чтения) или возвращает его имя. Предикат имеет формат: readdevice(SymbolicFileName) % (i), (о) Предикат readdevice переопределяет текущее устройство чтения, если переменная SymbolicFileName определена, и файл открыт для чтения. Если SymbolicFileName является свободной переменной, то readdevice присвоит ей имя текущего активного устройства чтения. Предикат writedevice/1 Предикат writedevice либо назначает, либо позволяет получить имя current- write-device (текущего устройства записи). Он имеет формат: writedevice(SymbolicFileName) % (i), (о) Предикат writedevice переопределит устройство записи, если указанный файл от- крыт для записи или добавления. Если переменная SymbolicFileName свободна, writedevice присвоит ей имя текущего активного устройства записи. Примеры открытия файла, записи в файл и закрытия файла: 1. Следующая последовательность открывает файл MYDATA.FIL для записи, затем направляет весь вывод, порождаемый операторами между двумя предикатами writedevice, в этот файл. MYDATA.FIL соответствует символическому имени destination, появляющемуся в описании домена file. domains file = destination goal openwrite (destination, ’’MYDATA. FIL”), writedevice(OldOut), % writedevice(destination), % Получаем текущее устройство вывода Перенаправляем вывод в файл writedevice(OldOut) Восстанавливаем устройство вывода 2. Программа chl2e09.pro (листинг 19.9) помещает символы, набранные на клавиа- туре, в файл TRYFILE.ONE на текущем диске; использует стандартные предика- ты read и write. Набираемые символы не выводятся на экран дисплея. Для вас будет хорошим упражнением написать программу, которая выводила бы эти сим- волы и на экран. Файл закрывается при нажатии клавиши <#>.
Глава 19. Запись, чтение и файлы 489 Листинг 19.9. Программа ch12e09.pro «• - ; . i." J '» W »»»»»> <> domains file = myfile predicates readloop clauses readloop:- readchar(X)f X<>'#' , ! , write(X)f readloop, readloop. goal write("This program reads your input and writes it to"),nl, write("tryfile.one. For stop press #"),nl, openwrite(myfile,"tryfile.one") , writedevice(myfile) , readloop, closefile(myfile) , writedevice(screen) , write("Your input has been transferred to the file tryfile.one"), nl. Переопределение стандартного ввода/вывода Домен file имеет три дополнительные опции: stdin, stdout, stderr. Преимущество этих файловых потоков в том, что вы можете переназначить стандартный ввод/вы- вод в командной строке (табл. 19.4). Таблица 19.4. Файловые потоки Файловый поток Описание stdin Стандартный ввод является файлом, доступным только для чтения. По умолчанию это клавиатура, readdevice (stdin) назначает устройством ввода stdin stdout Стандартный вывод является файлом, доступным только для запи- си. По умолчанию это экран терминала, writedevice (stdout) назначает устройством ввода stdout stderr Стандартный вывод ошибок является файлом, который доступен только для записи. По умолчанию это экран терминала. Writedevice (stderr) назначает устройством для вывода сооб- щений об ошибках stderr
490 Часть IV. Программирование на Visual Prolog Работа с файлами В этом разделе мы опишем несколько других предикатов для работы с файлами. Это предикаты filepos, eof, flush, existfile, deletefile, renamefile, disk и copyfile. Предикат filepos/3 Предикат filepos может управлять позицией, где производится чтение или запись. Он имеет формат: filepos(SymbolicFileName,FilePosition,Mode) % (i,i,i), (i,o,i) Если Fileposition — связанная переменная, предикат Может изменить позицию чтения и записи для файла с именем SymbolicFileName. Если при вызове FilePosition является свободной переменной, то SymbolicFileName возвращает те- кущую позицию в файле. Значение SymbolicFileName принадлежит типу long. Mode является целой величиной и указывает, как должно интерпретироваться (со- гласно табл. 19.5) значение FilePosition. Таблица 19.5. Интерпретация значения FilePosition Mode FilePosition О Относительно начала файла 1 Относительно текущей позиции 2 Относительно конца файла (конец файла соответствует позиции 0) Когда возвращается FilePosition, то filepos возвращает позицию относительно начала файла независимо от значения Mode. ( Замечание J В 003-ориентированнь1х версиях Visual Prolog filepos не различает текстовый и двоичный режимы, таким образом, "новая строка", согласно соглашению DOS, счи- тается как два символа. Приведем несколько примеров: 1. Следующая последовательность запишет значение Text в файл somefile.pro (к которому Пролог обращается как к myfile), начиная с позиции 100 относи- тельно начала файла. Text = "A text to be written in the file”, openmodify(myfile, "somefile.pro”), writedevice(myfile) , filepos(myfile, 100, 0), write(Text) , closefile(myfile).
Глава 19. Запись, чтение и файлы 491 2. Используя filepos, можно проверить содержимое файла байт за байтом, как это сделано в программе сЫ2е10.рго (листинг 19.10). Эта программа запрашивает имя файла, затем показывает содержимое позиций файла, причем номера пози- ций вводятся с клавиатуры. *•;***, *'****£** ?**» • *• ** » *' *:* '< •-•*?**»•*** -**• *•• ?• ** •*»••• Н*’ jЛистинг19.10.ПрограммасЫ2е10.рго *i*+»»,^,**»**Un***r»..»*».**b*olf**»A<»»«4*.b.*.i ьЛ . т.,.... domains file = input predicates inspect-positions(file) clauses inspect ^positions(Userinput) readdevice(Userinput)f nl, write (’’Position No? "), readin(X), term__str (ulong, Posn,X), readdevice(input), filepos(input,Posn,0), readchar(Y), nl, write("Char is: ", Y), inspect_positions(Userinput). goal write("Which file do you want to work with ?"),nl, readln(FileName), openread(input, FileName), readdevice(Userinput), inspect_positions(Userinput). Предикат eof/1 Предикат eof проверяет, является ли позиция, полученная в процессе чтения, кон- цом файла. В этом случае eof успешен. В противном случае он терпит неуспех. Пре- дикат eof имеет вид: eof(SymbolicFileName) % (i) eof выдает ошибку во время выполнения, если файл был открыт с правами только на запись. Обратите внимание, что предикат не придает особого значения символу конца файла DOS (комбинация клавиш <Ctrl>+<Z>). Пример, как предикат eof можно использовать для определения предиката repfile, который полезен при работе с файлами, repfile генерирует точку возврата до тех пор, пока не будет достигнут конец файла. predicates repfile(FILE)
492 Часть IV. Программирование на Visual Prolog clauses repfile repfile(F):- not(eof(F) ), repfile(F). Следующая программа chl2ell.pro (листинг 19.11) преобразует один файл в другой, все буквы которого являются заглавными. [ Листинг 19.11. Программа ch12e11.pro domains file - input; output predicates convert_file nondeterm repfile(FILE) clauses convert_file :- repfile(input), readln(Ln), upper__ lower(LnlnUpper,Ln), write(LnlnUpper),nl, fail. convert_file. repfile (_J . repfile(F):- not(eof(F)), repfile(F). goal write("Which file do you want readln(InputFileName),nl, write ("What is the name of the output file ?’’), readln(OutputFileName) , nl, openread(input, InputFileName), readdevice(input), openwrite(output, OutputFileName), writedevice(output), convert_file, closefile(input), closefile(output). % Перевод букв строки в заглавные convert ?"), Предикат flush/1 Предикат flush записывает содержимое внутреннего буфера в именованный файл. Он имеет формат: flush(SymbolicFileName) % (i) Он же запрашивает систему "сбросить все буферы".
Гпава 19. Запись, чтение и файлы 493 Предикат existfile/1 Предикат exist file выполняется успешно, если файл OSFileName будет найден. Формат его следующий: existfile(OSFileName) % (i) Предикат OSFileName может содержать каталог, а само имя может содержать знаки подстановки, как c:\psys\*.cfg. Предикат existfile завершается неуспешно, если имя файла не найдено в обозначенном пути каталога. Однако, заметьте, несмотря на то, что existfile находит все файлы, включая файлы с установленными атрибутами "system" (Системный) и "hidden" (Скрытый), он не находит каталоги. Это может быть выполнено использованием предикатов поиска каталогов, которые описаны ниже. Для проверки того, что файл присутствует на диске (прежде чем открывать его), вы можете воспользоваться: open(File, Name) existfile(Name), !, openread(File, Name). open(_, Name) :- write (’’Error: the file ’’, Name,” is not found”). Предикат searchfile/3 Предикат searchfile используется для нахождения файла в списке путей. Он при- нимает три аргумента: searchfile (PathList,Name, FoundName) % (i, i, о) где PathList — это строка, содержащая один или более путей, разделенных точкой с запятой, Name — имя искомого файла. Если файл найден, FoundName будет связана с полным его именем, иначе searchfile завершится неуспешно. Например: SearchFile (” .; . . ;С: \\”, "autoexec.bat", FoundName) , если autoexec.bat расположен в корне диска С:, FoundName установится равным C:\AUTOEXEC.BAT. Имя файла может содержать символы подстановки. В этом случае FoundName будет связана с полным именем файла, содержащим символы подстановки, и далее может быть использована в качестве аргумента для предикатов поиска каталогов, описан- ных ниже. Например, если имя файла в предыдущем примере задано как * .bat вме- сто autoexec.bat, FoundName окажется связанной с С:\*.ВАТ. Предикат deletefile/1 Предикат deletefile удаляет файл, заданный его аргументом, и имеет формат: deletefile(OSFileName) % (i) Предикат deletefile даст ошибку, если не сможет удалить файл. OSFileName не мо- жет содержать подстановочные символы.
494 Часть IV. Программирование на Visual Prolog Предикат renamefile/1 Предикат renamefile переименовывает файл с именем 01 dOSFileName в NewOS FileName. Он имеет формат: renamefile(OldOSFileName, NewOSFileName) % (i, i) Предикат renamefile будет успешен, если файл с именем NewOSFileName не сущест- вует, и оба имени являются допустимыми файловыми именами. В противном случае будет выдана ошибка. Предикат disk/1 Предикат disk используется для изменения текущего диска и/или каталога/под- каталога и имеет формат: disk(Path) % (i) (о) При вызове со свободной переменной в качестве параметра, disk возвратит текущий каталог. В DOS-ориентированных версиях для переключения на другой диск без изменения существующего текущего каталога на этом диске используйте D:. Здесь D — буква, обозначающая устройство. Предикат copyfile/2 Предикат copyfile используется для копирования файла. Он принимает два пара- метра: copyfile(SourceName,DestinationName) % (i,i) Имена файлов могут быть заданы полностью или частично, включая диски и каталоги пути к файлу. Подстановочные символы запрещены. Скопированный файл будет иметь те же атрибуты (и права доступа), как и исходный файл. Атрибуты файлов Хотя стандартные предикаты открытия файлов, описанные ранее, охватывают все основные случаи, может понадобиться открыть или создать файлы со специальными атрибутами или режимами использования. Для этого в Visual Prolog имеются специ- альные предикаты, но, прежде чем их рассматривать, нам необходимо ознакомиться с файловыми атрибутами и режимами разделения. Атрибуты и режимы доступа в Visual Prolog используют те же значения, что и ОС. з& исключением атрибута по умолчанию. Константы для файловых атрибутов и режимов разделения находятся в файле IODECL.CON. Открытие и создание файлов При открытии или создании файла ОС нужно знать его атрибуты (например, "сис- темный'’ или "скрытый"), тип доступа (например, "на чтение"), а также то, как со- крытый файл могут разделять другие программы (например, "запись запрещена” — это три различных вида информации, лишь частично зависимые.
Глава 19. Запись, чтение и файлы 495 Атрибуты Файловые атрибуты — это постоянные атрибуты, относящиеся к физическому файлу на диске. В DOS есть всего несколько атрибутов, таких как ’’read only” и ’’hidden". Эти атрибуты сообщают ОС, как она может управлять файлом. Сетевые и много- пользовательские ОС, как правило, имеют много больше атрибутов. Режимы доступа Режимы доступа (access modes) указывают, как файл будет использован. ОС комби- нирует эти данные с физическими атрибутами файла для определения, разрешен ли запрошенный доступ. Например, если при установленном физическом атрибуте "только на чтение" будет запрошен режим доступа fin_access_wo "только для записи" или fm_access_rw "для чтения и записи", то доступ к файлу будет запрещен. Режимы разделения Режимы разделения (sharing modes) указывают, как этот процесс рассматривает раз- деление данных с другими процессами. Если файл уже используется, то для реше- ния, будет ли успешен новый запрос на открытие, ОС комбинирует режимы разде- ления и доступа процесса, открывшего файл, с аналогичными режимами открываю- щего процесса (для определения, будет ли попытка открытия успешной). Если файл успешно открыт, то использованные режимы ограничат будущие попытки его от- крытия. Заметьте, что оба режима направлены на формирование комбинированного множе- ства ограничений на файл: они определяют, что процесс хочет от файла, и что будет позволено другим, открывающим файл, процессам. Например, если файл был от- крыт с атрибутами "запрещена запись" и "только чтение", то попытка открыть его с атрибутами "запрещений нет" и "только запись" будет неуспешна, т. к. первый про- цесс определил "запрещена запись". И это, теперь уже существующее ограничение на файл, отвергает попытку открытия с противоречащим атрибутом. С другой стороны, попытка открытия файла с атрибутом "запрещено чтение" будет неуспешна, т. к. файл уже открыт с правом на чтение. Это текущее требование отвергнет такую по- пытку открытия. Замечание J Режим fm_sh_denyrw запрещает все режимы доступа для других процессов. Все описанные выше стандартные предикаты определяют режим разделения "запрет на запись". Специальный предикат openfile/5 Специальный предикат openfile обеспечивает открытие и создание файлов нестан- дартными способами, openfile выглядит так: openfile (SymbolicName, OSName, OpenMode, Attributes, Creation) % (i,i,i,i,i) SymbolicName и OSName те же, что и в ранее описанных предикатах.
496 Часть IV. Программирование на Visual Prolog Остальные аргументы следующие: □ openMode — это режимы доступа и разделения для файла. Они формируются объ- единением одного из значений fm_access_xx, одного из fm_sh_xxxxxx и (необяза- тельно) fm г eturner г и fm_writethru. Если режим доступа не задан, он будет ус- тановлен в атрибут '‘только чтение". Если режим разделения не задан, он будет установлен в атрибут "запись запрещена". □ Attributes — это физические атрибуты файла. Доступные атрибуты для DOS — это fa_rdonly, funhidden, fa_system, fa_arch и f abnormal. Если ничего не за- дано (0), атрибуты будут установлены по умолчанию в fa normal. Атрибуты "сис- темный" и "скрытый" аналогичны: они скрывают файл при выполнении команды "dir". Заметьте, что DOS автоматически устанавливает архивный атрибут при за- крытии измененного файла. □ Creation — определяет, что делать в случае отсутствия или наличия файла с ука- занным именем. Он формируется добавлением не более одного параметра из группы с г_ех_хх и не более одного — из группы or_поех _хх. Обратите внимание на значение Creation по умолчанию, если ничего не задано (0). Оно эквивалент- но заданию cr_ex_fail и cr_noex_fail, т. е. неуспех и при существовании, и при отсутствии файла. Помните, что реальное действие Creation по умолчанию будет установлено в соответствии с режимом доступа: fm_access__ro -> cr_ex_open + cr_noex_fail fm_access_wo -> cr_ex_replace + cr_noex_c reate fm_access_rw -> cr_ex_open + cr_noex_create Оптимальный Creation по умолчанию для доступа на запись-чтение достаточно сложен: если запись-чтение определены в связи с открытием файла на модифи- кацию, то существующий файл с таким именем должен быть открыт, а не заме- нен. Это действие по умолчанию. Однако если запись-чтение определены в связи с тем, что кто-то хочет реализовать двунаправленный доступ к файлу, то сущест- вующий файл с таким именем должен быть удален. Это возможно при таком вы- зове openfile: FMode = fm_access_rw + fm_sh_denywr + fm_returnerr, FCrea = cr_ex_replace + cr_noex_create, openfile(dbfile,"salient.dba",FMode,fabnormal,FCrea), Имена файлов и путей Рассмотрим набор стандартных предикатов, упрощающих управление именами фай- лов и осуществляющих поиск файлов на диске. Предикат filenamepath/3 Предикат filenamepath используется для составления полного имени файла и раз- ложения его на путь и имя файла.
Глава 19. Запись, чтение и файлы 497 Он имеет три аргумента: filenamepath(QuaIName,Path,Name) % (i,o,o) (o,i,i) Аргумент filenamepath преобразовывает QualName в Path и Name, и наоборот, как в листинге 19.12. j1 -W-W Я" Г " 1 Ч ••••. ,*.J м •!!*!!•« . Ч •••* •"/ * * *» »Л * '.'Л?.* .4 Я* •• ? Ч.'.М Ч" ЯЧЛ1.П* •«•.о * Я* ЯЧ.Ч.М Ч •;$« • * .ЗДу.ЧЧ .Чуу..0..'УяУЯ' • *4 я *Л " | Д Истй M'2v Пр6грШйа:;>с^ goal QualName=”c: \\vip\\bin\\prolog.err", fileNamePath (QualName, Path, Name), write("\nQualName=",QualName), write("\nPath=",Path), write("\nName=",Name), fileNamePath(NewName,Path,Name), write("\nConverted back: ",NewName),nl. Аргумент fileNamePa th установит Path в C:\V1P\BIN, a Name в PRO LOG. ERR; NewName установится в C:\VIP\BIN\PROLOG.ERR. Заметьте, что под DOS всякое управление Visual Prolog именами файлов превращает буквы имен в заглавные. Используя в этом предикате (о, i, i) поток, учитывайте особые случаи, описанные в разделе filenamepath интерактивной справки VDE. Предикат filenameext/3 Предикат filenameext используется для составления и разбора (полного) имени от- носительно его расширения, определяемого точкой. Он имеет три аргумента: filenameext(Name,BaseName,Ext) % (i,o,o) (o,i,i) Листинг 19.13 — пример для DOS. ГМ"* ** •• 1 Я1, я**.* .ч. *• У •.у,, у ’ »_• М т ’ • у • * у • • ?.W т М ’’’ мЯГМ • М* • • У £ ?•*•••• * "J * Г УМ Т • > ’ " * М ’ «Л«у.М ЯУЯ *• * М У V * ’ ’.Г Уя •• УЯ • * "ЛЯУЛ ЛУ.Ч.’Я У." * У • 1 ” * - *Л1 ЧМУ «ЛМ *««.«я ; Листинг 19ЛЗ. Программа ch12e14.pro goal Name="c: \\vip\\bin\\winW16Wvip.exe", FileNameExt(Name,BaseName,Ext), write("\nName=",Name), write("\nBaseName-",BaseName) , write(M\nExt=",Ext) , FileNameExt(NewName,BaseName,Ext), write("\nConverted back: ",NewName), % Override the old extension FileNameExt(NewName1,"VIP.EXE”,".HLP"), write(”\nNewNamel=",NewNamel), nl.
498 Часть IV. Программирование на Visual Prolog Эта программа установит BaseName в C:\VIP\BIN\WIN\16\VIP, a Ext в EXE; затем NewName установится в C:\V1P\B1N\W1N\16\VIP.EXE, и, наконец, NewNamel проде- монстрирует прямое переопределение расширения — вам не понадобится явно уда- лять старое расширение. Заметьте, что точка считается частью расширения, и, как и в filenamepath, в DOS-ориентированной версии все буквы преобразованы в за- главные. Поиск в каталогах Visual Prolog имеет предикаты поиска в каталогах, осуществляющие сопоставление имен файлов, содержащих подстановочные символы. Кроме того, эти предикаты возвращают всю информацию о найденных каталогах. Поиск в каталогах очень зависим от файловой системы, вам следует настроить себя на будущие изменения и использовать предикаты только там, где они работают. Для нахождения файлов каталог должен быть открыт. Это делает предикат di г open, задавая маску имени файла и атрибуты для поиска. Затем, последовательно вызывая предикат dirmatch, можно находить подходящие по маске и атрибутам файлы один за другим. Наконец, каталог закрывается вызовом предиката dirclose. Обычно предикаты ведут себя независимо от платформы: имя файла, содержащее, возможно, подстановочные символы, используется для определения имен при со- поставлении, а набор атрибутов поиска уточняет сопоставление (список атрибутов см. разд. "Атрибуты файлов" данной главы). Visual Prolog считает все атрибуты строго информативными, и все они могут быть использованы для выбора файлов. При ис- пользовании предикатов поиска в каталогах следует задать интересующие вас атри- буты. Если вы, например, хотите найти все файлы с установленным архивным би- том, задайте fa_arch; если'с установленным системным битом, — fa_system; если вы хотите найти все '‘обычные'* файлы, задайте fabnormal, и т. д. Предикат diropen/3 Предикат diropen используется для открытия доступа к каталогу, он имеет формат: diropen(Wild,Attrib,Block) % (i,i,o) Здесь Wild — имя файла, возможно, содержащее подстановочные символы, Attrib — требуемые атрибуты поиска, Block — информационный блок, используемый в по- следовательных вызовах dirmatch. Для компилятора этот блок выглядит как строка, но, на самом деле, он более информативен, чем кажется на первый взгляд. Он не может быть внесен в базу фактов, а затем получен оттуда. Пока каталог открыт, он должен храниться в локальной переменной или в аргументе, что то же самое. Пре- дикат diropen завершится неуспешно, если нет файлов, соответствующих заданной спецификации; а также, если имя файла включает несуществующий каталог. Несколько предикатов diropen могут быть активны одновременно, т. е. они могут быть вложенными и использоваться рекурсивно.
Глава 19. Запись, чтение и файлы 499 Предикат dirmatch/10 После того как diropen возвратит информационный блок, предикат dirmatch будет возвращать имя и другую информацию для каждого подходящего файла (одного за вызов). Он имеет формат: dirmatch(Block,Name,Attг,Hour,Min,TwoSec,Year,Month,Day,Size) % (i,o,o,o,o,o,o,0,0,0) Здесь Block — это информационный блок, возвращаемый diropen, Name — соответ- ствующее имя, a Attr — атрибуты найденного элемента. Остальные аргументы должны быть самоочевидны — все они беззнаковые целые, кроме size, который есть беззнаковое длинное целое. DOS использует только 5 бит для кодирования вто- рой части временной метки, создавая максимум 32 различных значения — отсюда имя TwoSec. При каждом вызове dirmatch возвращает следующий элемент, удовлетворяющий запросу. Когда соответствий больше нет, предикат завершается неуспешна; когда это случается, dirmatch автоматически закрывает каталог. При поиске подкаталогов с заданием имени типа »»*.*", dirmatch всегда возвращает точки входа и если их возвращает операционная система. Следовательно, dirmatch найдет заданные каталоги во всех каталогах, кроме, возможно, корневого. Предикат dirclose/1 Предикат dirclose закрывает ранее открытый каталог. Его формат: dirclose(Block) Здесь Block — информационный блок, возвращаемый diropen. Если вызывать dirmatch до тех пор, пока он не закончится неуспешно (из-за отсутствия следующе- го подходящего файла), dirclose вызывать не надо, ибо dirmatch уже закрыл ка- талог. Следующий пример chl2el6.pro (листинг 19.14) демонстрирует использование пре- дикатов, работающих с каталогами, для того, чтобы предикат existdir дополнял стандартный предикат existfile, описанный выше. include "iodecl.con" predicates existdir(string) exdl(string) exd2(string, string) clauses existdir(Wild) diropen(Wild,fa_subdir,Block), exdl(Block), dirclose(Block).
500 Часть /И Программирование на Visual Prolog exdl(Block) : - dirmatch (Block, Name, _, , exd2(Block,Name). exd2 (_, Name) : - not(frontchar(Name,’.',_)),!. exd2(Block,_):- exdl(Block). Если задана цель existdir("C:\\*.*") в DOS, то, если ваш диск не обладает не- обычной организацией, ответом будет "yes”. Однако программа найдет лишь подка- талоги в существующих путях. Если вы, например, запросите existdir (С: WJNKW* не имея в корневом каталоге диска С каталога с именем "JNK", завершение будет с ошибкой. Вы также должны знать, что в DOS сам корневой каталог не сопостав- ляется (каталога с именем \ не существует), и existdir("с:\\") завершится неус- пешно. Обратите внимание, как в примере отфильтровываются точки входа текущего и ро- дительских каталогов ("." и ".. ”). Предикат dirfiles/11 Мы показали сложный путь поиска файлов, а вот простой. Предикат dirfiles — это недетерминированный стандартный предикат, который, путем поиска с возвратом, возвращает один за другим все файлы, соответствующие заданному критерию. Он выглядит так: dirfiles(Wild,Attrib,Fnam,RetAttr,Hour,Min,Sec, Year,Month,Day,Size) % (i, i, o, o, o, o, o, o, o, o, o) Использование dirfiles избавляет от необходимости открытия и закрытия каталога, т. к. это делается автоматически, но с одним условием: для корректного использова- ния он должен действовать через поиск с возвратом, пока тот не завершится неус- пешно. Именно последний вызов, завершающийся неуспехом, автоматически за- крывает каталог. Полезно помнить, что ни компилятор, ни код, поддерживающий выполняемую программу, не могут определить, случится ли это: здесь ответствен- ность целиком ложится на программиста. Конечно, ничего страшного не произой- дет, если оставить пару каталогов открытыми, но, в конечном счете, система может истощить свои ресурсы. Аналогично diropen, вызовы dirfiles могут быть вложенными и использоваться рекурсивно. Рассмотрим пример для DOS. Программа chl2el7.pro (листинг 19.15) проходит все каталоги на диске С, разыскивая точки входа, имеющие установленные атрибуты "системный” или "скрытый”. ОС обычно содержит пару скрытых файлов в корневом каталоге. Однако если где-то на диске находятся скрытые файлы, будьте осторожны! Это, возможно, системные файлы или файлы защиты от копирования инсталлиро- ванного вами коммерческого ПО.
Глава 19. Запись, чтение и файлы 501 у <У ' constants fa_hidden = $02 % Скрытый файл fa_system = $04 % Системный файл fa_subdir = $10 % Подкаталог fa_hidsys = $06 % скрытый + системный predicates findhidden(string,string) wrattr(integer) clauses wrattr(A):- bitand (A, fa_hidden,AA), AA<>0, write(’H’), fail. wrattr(A) :- bitand (A, f a_system, AA) , AAO0, write(’S’), wrattr (A) :- bitand (A, f a_subdir, AA) , AA<>0, write ( ’ D’ ) , fail. wrattr(_). findhidden(CurrPath,Wild) write (CurrPath, " : \n,f) , filenamepath(FileSpec,CurrPath,Wild), dirfiles (FileSpec, fa_hidsys, FileName, RetAttr,_, , wrattr(RetAttr), write(1\t’,FileName,’\n'), fail. findhidden(CurrPath,Wild) filenamepath(DirSpec,CurrPath,"*.*"), dirfiles(DirSpec,fa_subdir,Name,, not(frontchar(Name,'.',_)), filenamepath(DirName,CurrPath,Name), findhidden(DirName,Wild), fail. findhidden (_,__) . goal findhidden("C:\\”,*").
502 Часть IV. Программирование на Visual Prolog Пример также демонстрирует декодирование возвращенного атрибута (в предикате wrattr) путем побитовой проверки. Манипулирование файловыми атрибутами Стандартный предикат позволяет получать и устанавливать (информационные) ат- рибуты файлов. Несмотря на то, что в документации на DOS и MS Windows часто говорится об "атрибуте каталога", файл нельзя превратить в каталог путем простого очищения этого атрибута. Предикат fileattrib/2 В зависимости от потока данных предикат fileattrib получает или устанавливает файловые атрибуты: fileattrib(Name,Attrib) % (i,o) (i, i) Здесь Name должно задавать существующий файл, иначе fileattrib завершается с ошибкой. Заметьте, что определение возможности получения или установки атри- бутов целиком определяется ОС; в частности, вы не можете установить файловые атрибуты каталогу. Атрибуты для файла появляются в Attrib как беззнаковое корот- кое целое. Оно может быть проанализировано или изменено побитовыми операция- ми. Например, следующий код очищает системный атрибут для файла JNK: constants fa_system = $04 % Системный файл fa_notsys = $FFFB % -системный файл goal fileattrib("jnk", FA) , bitand(FA, fa_notsys,Plain), fileattrib(”jnk",Plain). Константа fa_notsys — это побитовое отрицание fa system. Оно выполняется при помощи стандартного предиката bitxor: constants fa_system = $04 % Системный файл goal bitxor(f a_system,$ FFFF,NotSys), fileattrib("jnk",FA), bitand(FA,NotSys, Plain), fileattrib("jnk",Plain).
Гпава 19. Запись, чтение и файлы 503 Управление термами в текстовых файлах Предикат readterm позволяет получить доступ к фактам в файле. Он может считать любые объекты, записанные предикатом write, и имеет вид: readterm (<name>, Те rmPa г ат) . где <пате> — имя домена. Следующая программа показывает, как readterm может быть использован: domains name,addr ~ string one_data_record = p(name, addr) file = file_of_data_records predicates person(name, addr) moredata(file) clauses person(Name,Addr) :- openread(file_of_data_records, "DD.DAT"), readdevice (file_of_data__records), moredata(file_of_data_records), readterm(one_data_record, p(Name, Addr)). moredata(_). moredata(File) not(eof(File)), moredata(File). Если файл DD.DAT содержит факты, принадлежащие домену one_data_record, такие как: p("Peter","28th Street") р("Curt","Wall Street") то вернуть информацию из этого файла можно очень просто. Запустите Test Goal, добавив следующую цель: goal person("Peter",Address). Test Goal ответит: Address="28th Street" 1 Solution А теперь запустите Test Goal с целью: goal person("Peter","Not an address"). Test Goal ответит: no
504 Часть IV. Программирование на Visual Prolog Работа с фактами как с термами С фактами, которые описываются предикатами базы фактов, можно работать так же, как с термами. Это возможно благодаря тому, что Visual Prolog автоматически объявляет домены, соответствующие именам разделов facts и домену dbasedom, ко- торый автоматически объявляется для неименованных разделов facts. Visual Prolog создает одну доменную альтернативу для каждого предиката из раздела facts. Пролог описывает каждый предикат базы фактов с помощью функтора и доменов аргументов этого предиката. Например, для такого объявления раздела facts: facts person(name, telno) city(eno, cname) Visual Prolog сгенерирует следующий домен dbasedom: domains dbasedom = person(name, telno); city(cno, cname) Именованный раздел facts подобным же образом генерирует домен, соответствую- щий имени раздела facts. Пример: facts — test person(name, telno) city(eno, cname) Visual Prolog генерирует домен domains test = person(name, telno); cityicno, cname) который может быть использован как любой другой домен. Следующий пример показывает, как вы можете построить предикат my consult, ана- логичный стандартному предикату consult. % Предикат my_consult, определенный с помощью readterm domains file = dbase facts — dbal % Объявление предикатов базы данных, которые будут считываться из файла predicates my_consult(string) repfile(file) clauses my_consult(FileName) :- openread(dbase, FileName), readdevice(dbase), repfile(dbase),
Глава 19. Запись, чтение и файлы 505 readterm(dbal, Term), assertz(Term), my_consult(_):- eof(dbase). repfile(_). repfile(F) not(eof(F)), repfile(F). Если, например, раздел facts содержит описание: р(string, string) и файл с именем DD.DAT существует, вы можете попробовать следующие цели, которые берут информацию из этого файла. Добавьте следующий раздел goal в код программы и запустите Test Goal: goal my_consult ("dd. dat ’’) . Test Goal ответит: yes Теперь запустите Test Goal, добавив следующую цель: goal my_consult("dd.dat"), p(X,Y) . Test Goal ответит: X=Peter, Y=28th Street X=Curt, Y=Wall Street 2 Solutions Резюме Отметим наиболее важные моменты, рассмотренные в этой главе: □ Visual Prolog обеспечивает три стандартных предиката для записи: • Write — для простого вывода; • writef — для вывода, форматированного в соответствии с форматными спе- цификаторами; • nl — для генерации новой строки. □ Базовые стандартные предикаты Visual Prolog для чтения: • readln — для чтения целиком строки символов; • readint, readreal, и readchar — для чтения целых, вещественных и символь- ных значений соответственно;
506 Часть IV. Программирование на Visual Prolog • readterm — для чтения составных объектов; • file_str — для чтения целиком текстового файла в строку. □ Двоичные блоки могут обрабатываться, используя: • readblock — для чтения двоичного блока из файла; • writeblock — для записи двоичного блока в файл; • file bin — для чтения/записи целых файлов в двоичные блоки. □ Visual Prolog использует устройство cur renter ead_de vice (обычно это клавиа- тура) для ввода и current_write_device (обычно это экран) для вывода. Вы можете указать другие устройства и можете переопределить текущие устройства ввод a/вы вода во время выполнения. (Такое переопределение называется измене- нием направления ввода/вывода.) □ Основные предикаты доступа к файлам Visual Prolog: • openread — открыть файл для чтения; • openwrite — открыть файл для записи; • openappend — открыть файл для добавления; • openmodify — открыть файл для модификации; • openfile — открыть файл для общих целей; • filemode — установить файл в текстовый или двоичный режим; • closefile — закрыть файл; • readdevice — переназначить текущее устройство чтения и получить его имя; • writedevice — переназначить текущее устройство записи и получить его имя. □ Для доступа к файлам используется домен file, который имеет пять предопреде- ленных альтернатив: • keyboard — для чтения с клавиатуры; • screen ~ для записи на экран; • stdin " для чтения со стандартного ввода; • stdout — для записи на стандартный вывод; • stderr — для записи на стандартное устройство вывода ошибок. □ Для работы с файлами используются следующие стандартные предикаты: • filepos — контролирует позицию, в которой происходит чтение или запись; • eof — проверяет, достигнут ли конец файла; • flush — записывает содержимое внутреннего буфера в файл; • existfile — проверяет, существует ли файл; • searchfile — осуществляет поиск файла в нескольких каталогах; • deletefile — стирает файл; • renamefile — переименовывает файл;
Гпава 19. Запись, чтение и файлы 507 • copyfile — копирует файл; • fileattrib — получает или устанавливает атрибуты файла; • disk — изменяет текущий диск и каталог/подкаталог. □ Для поиска в каталогах доступны следующие предикаты: • diropen — открывает каталог для поиска; • dirmatch — ищет соответствующие файлы в каталоге; • dirclose — закрывает каталог; • dir files — недетерминированно возвращает имена файлов из каталога, соот- ветствующие заданному образцу. □ Для работы с именами файлов используются предикаты: • filenamepath — объединяет или разделяет полные имена файлов на путь и собственно имя файла; • filenameext — объединяет или разделяет имена файлов и расширения. □ Стандартный предикат readterm позволяет программе получить доступ к фактам в файле во время исполнения и может считывать любой объект, записанный write, а также факты и предикаты базы данных.
Обработка строк в Visual Prolog Visual Prolog поддерживает набор стандартных предикатов для эффективной обра- ботки строк. В этой главе мы разделили их на две группы: семейство базовых преди- катов управления строками и ряд предикатов, используемых для преобразования строк в другие типы (и наоборот). Строки также можно сравнивать друг с другом, об этом рассказывается в гл. 16. Основные предикаты управления строкой Строки и их обработка подчиняются определенным правилам. Так, в строках Visual Prolog обратный слэш (\) является управляющим символом, позволяющим вставлять в строки символы, которых нет на клавиатуре (см. гл. 12). Предикаты, описываемые в этом разделе, являются основой обработки строк в Visual Prolog; они служат для нескольких целей: □ разделение строки на подстроки и лексемы; □ построение строки из определенных подстрок и лексем; □ проверка, состоит ли строка из определенных подстрок или лексем; □ возврат лексемы или списка лексем из данной строки; □ проверка или определение длины строки; □ создание пустой строки определенной длины; □ проверка, является ли строка термином, определенным в Visual Prolog; □ форматирование переменного числа аргументов в строковую переменную. Предикат frontchar/3 Предикат frontchar действует согласно равенству: Stringl = объединение Char и String?
Глава 20. Обработка строк в Visual Prolog 509 и имеет следующий формат: frontchar(Stringl,Char,String2) % (i,o,o) (i,i,o) (i,o,i) (i,i,i) (o,i,i) Предикат front char имеет три аргумента: первый из них — строка, второй — символ (первый символ первой строки), третий — остаток первой строки. Предикат front char можно использовать для расщепления строки в последователь- ность символов или для создания строки из последовательности символов, а также для проверки символов в строке. Если аргумент stringl связан со строкой нулевой длины, то предикат завершается неуспешно. В программе сЫЗеОЕрго (листинг 20.1) frontchar используется для определения предиката, преобразующего строку в список символов. Задайте цель: string_chlist("ABC", Z) После обработки этого целевого утверждения z будет связано со списком ['А', ’В' , 'С' ] J Дйстййг ПрограммасМЗеОЕproГ35ft domains charlist = char* predicates string_chlist(string, charlist) clauses string_chlist("", []):-!. string_chlist(S, [H|T]):- frontchar(S,H,SI), string_chlist(S1,T) . Предикат fronttoken/3 Предикат fronttoken выполняет три взаимосвязанные функции, в зависимости от типа потока аргументов, который используется для обращения к нему. fronttoken(Stringl, Token, Rest) % (i,o,о) (i,i,о) (i,o,i) (i, i,i) (o,i,i) В случае потока (i,o,o) fronttoken находит первую лексему в stringl, связывает ее с Token, а остаток stringl связывает с Rest. Варианты потока (i,i,o), (i,o,i), а также (i, i, i) служат для проверки: если связанные аргументы соответствуют час- тям stringl (первой лексеме, всему, что находится после первой лексемы, или же тому и другому), то fronttoken завершается успешно, в противном случае — не- успешно. В случае если использован поток (o,i,i), предикат создает объединение Token и Rest, связывая stringl с результатом.
510 Часть IV. Программирование на Visual Prolog Последовательность знаков является лексемой, если она представляет собой: □ имя в соответствии с синтаксисом Visual Prolog; □ число (предшествующий ему знак является отдельной лексемой); □ отличный от пробела знак. Предикат fronttoken отлично приспособлен для разбиения строки на лексические символы. Пример в программе сЫЗе02.рго (листинг 20.2) иллюстрирует способ использования предиката fronttoken для разбития предложения на список имен. Если задать целе- вое утверждение: string_namelist("bill fred tom dick harry", X). то после выполнения x будет связано co списком: [bill, fred, tom, dick, harry] 5 ЬЛ истинг20.2.ПрЬграмМасЬ13е02^|>го G' ’ * « «Vi жй1*/** a a M • ** t a a*a « a a« a 4,i i** i •*’ «t a a>a a a * a « <’• *"<a «1га « a r*a afra if* <a*a a itUiiia aViiva a1* tiir*' * <•? > rt • Н.итпн! ФЪ • irrt«»t tt • ««'ai • «t « $ domains namelist = name* s name = symbol predicates string__namelist (string, namelist) clauses string_namelist(S,[HIT]):- fronttoken(S,H,SI),!, string_namelist(S1,T). string__namelist (_,[])• Предикат frontstr/4 Предикат frontstr расщепляет stringl на две части. Синтаксис предиката: frontstr(NumberOfChars, Stringl, StartStr, EndStr) % (i,i,o,o) где StartStr содержит NumberOfChars первых символов из Stringl, a EndStr содер- жит остаток. При обращении к frontstr первые два параметра должны быть связан- ными, а последние два — свободными. Предикат concat/3 Предикат concat устанавливает, что строка strings является результатом сцепления stringl и string?. Он имеет форму: concat(Stringl, String?, String3) % (i,i,o), (i,o,i), (o,i,i), (i,i,i)
Гпава 20. Обработка строк в Visual Prolog 511 По крайней мере два параметра должны быть связаны перед тем, как вы вызываете conceit; это означает, что этот предикат всегда дает только одно решение (другими словами, он — детерминированный). Например, мы вызываем concat("croco”, "dile", In_a_whilе) связывая ln_a_while с crocodile. В другом случае, если See_ya_later связано, то обращение к предикату concat ("alii" f "gator", See__ya_later) завершается успешно, только если See__ya_later является связанным с alligator. Предикат str_len/2 Предикат str_len решает следующие задачи: определяет или проверяет длину стро- ки или возвращает строку пробелов заданной длины. Он имеет формат: str_len(StringArg, Length) % (i/O), (i,i), (o,i) Предикат str_len связывает переменную Length с длиной строки StringArg или проверяет, имеет ли StringArg данную длину Length. Length — это беззнаковое це- лое. В версии предиката с третьим потоком str_len возвращает строку пробелов данной длины, что может быть использовано для распределения буферов и других операций. Хотя в этих целях предпочтительнее использовать предикат makebinary, в особенности для распределения бинарных данных. Предикат isname/1 Предикат is name проверяет, является ли аргумент допустимым именем согласно синтаксису Visual Prolog, и имеет формат: isname(String) % (i) Имя начинается с буквы алфавита или символа подчеркивания, за которым следует любое число букв, цифр и символов подчеркивания. Предыдущие и последующие пробелы игнорируются. Предикат format/* Предикат format выполняет преобразования, аналогичные writef (см. гл. 19), но format выдает результат в виде строковой переменной. format(Outputstring,Formatstring,Argl, Arg2, Arg3,...,ArgN) % (о, i, i, i, . . ., i) Предикат subchar/3 Предикат subchar возвращает символ на данной позиции строки: subchar(String,Position,Char) % (i,i,о)
512 Часть IV. Программирование на Visual Prolog Первый символ строки имеет позицию 1. Например: subchar("АВС", 2,Char) свяжет char с в. Если позиция определяет символ за концом строки (несуществую- щий символ), subchar завершится с ошибкой. Предикат substring/4 Предикат substring возвращает часть строки; его формат: substring(Strain,Pos,Len,Str_out) % (i,i,i,o) Предикат Str out будет связан с копией части строки str in, начиная с символа на позиции Pos и длиной Len. Например: substring("GOLORP",2,3,SubStr) связывает SubStr с olo. Если Pos и Len задают строку частично или полностью вне границ str_in, то substring завершается с ошибкой. Однако запрос 0 байт на самом конце строки не является ошибкой: substring("АВС",4,0,SubStr) свяжет SubStr с пустой строкой, тогда как substring("АВС",4,1,SubStr) % WRONG является ошибкой, так же, как и substring("АВС",5,-1,SubStr) % WRONG Предикат searchchar/3 Предикат searchchar возвращает позицию первого появления заданного символа в строке; его формат: searchchar(String,Char,Position) % (i,i,о) Например: searchchar("ABEKAT",'A',Pos) свяжет Pos c 1. Если символ не найден в строке, то searchchar завершится неус- пешно. Заметьте, что searchchar удовлетворяется лишь однократно (т. е. если задан- ный символ встречается в строке более одного раза, поиск с возвратом для их нахо- ждения не будет применен), но вы легко можете использовать для этого свой собст- венный поиск. Рассмотрите пример в листинге 20.3. a ir« fift a * a a a«a a a a « a aaa a » i a a ri'ri'ra r> r> rB »a »a a ii'^ar.'* * a a a r> <«»• a a* *>aria a a a a a* a•• *a *a a a a *• га г***» • *a a *a a aa« a * tn at. >a<< ♦* tmrHUm, «a a *«a ч . H ...... r. r. predicates nondeterm nd__searchchar (st ring, char, integer) nondeterm nd_searchcharl(string,char,integer,integer)
Глава 20. Обработка строке Visual Prolog 513 nondeterm nd_sc(string,char,integer,integer,integer) run clauses nd_searchchar(Str,Ch,Pos):- nd_searchcharl(Str,Ch,Pos,0). nd_searchcharl(Str,Ch,Pos,Old) searchchar(Str,Ch,Posl), nd_sc(Str,Ch,Pos,Posl,Old). nd_sc (_,_, Pos, Posl, Old) .* - Pos = Posl+Old. nd_sc(Str,Ch,Pos,Posl,Old):- frontstr(Posl,Str,_,Rest), Oldl == Old + Posl, nd_searchcharl(Rest,Ch,Pos,Oldl). goal nd__searchchar ("abbalblablabbala", ' a' , P), write(P,’\n’), Это реализация недетерминированного предиката nd_searchchar, который пол- ностью совместим с searchchar. Если вы не возражаете против вывода на печать дополнительного аргумента (Old), то можно сэкономить на одном уровне вызовов. Предикат searchstring/3 Предикат searchstring возвращает позицию первого появления строки в другой строке; его формат: searchstring(SourceStr,SearchStr,Pos) % (i,i,o) Например: searchstring("ABEKAT","BE”,Pos) свяжет Pos c 2. Если искомая строка не найдена или длиннее исходной строки, searchstring завершается неуспехом. Как и searchchar, searchstring удовлетворя- ется лишь однократно, но вы можете придумать собственный аналог searchstring. Все, что для этого необходимо, — это взять программу сЫЗеОЗ.рго и глобально за- менить ’’char" на "string", а в целевом утверждении вместо "а" подставить подхо- дящую строку для поиска, например, "ab": goal nd_searchstring("abbalblablabbala","ab",P) , write(P,1\n’),
514 Часть IV. Программирование на Visual Prolog Преобразования типов В этом разделе мы рассмотрим стандартные предикаты, предназначенные для преобразования типов. Это предикаты char_int, str_char, str_int, str_real, upper_lower и, наконец, tennjtr, который совершает преобразования между любы- ми термами и строками. Предикат char_int/2 Предикат char_int преобразует символ в целое число или целое в символ и имеет формат: char_int(Char, Integer) % (i,o), (o,i), (i,i) Если оба аргумента связаны, то char_int проверяет, соответствуют ли значения аргументов. Если один аргумент связан, а другой свободен, char_int выполняет пре- образование и связывает выходной параметр с преобразованной формой входного параметра. Замечание J Этот предикат уже не является необходимым в новых версиях Visual Prolog. В них введено автоматическое преобразование между символом и целым числом. Преди- кат char_int оставлен для совместимости со старыми версиями. Предикат str_char/2 Предикат str char преобразует строку, содержащую один и только один символ, в символ или символ в строку из одного символа; предикат имеет формат: str_char(String, Char) % (i,о), (о,i), (i,i) В случае если поток параметров — (i,i), то предикат str_char завершается успеш- но, если при этом string связан со строкой из одного символа, который соответст- вует Char. Если длина строки — не единица, то str_char завершается неуспешно. Предикат str_int/2 Предикат str_int преобразует строку, содержащую целое число, в его текстовое представление и имеет формат: str_int(String, Integer) % (i,o), (o,i), (i,i) В случае если поток параметров— (i,i), то str_int завершается успешно, при условии, что integer связан с целым эквивалентом числа, представленного с по- мощью String.
Гпава 20. Обработка строк в Visual Prolog 515 Предикат str_real/2 Предикат str_real преобразует строку в вещественное число или вещественное чис- ло в строку и имеет формат: str_real(String, Real) % (i,o), (o,i), (i,i) В случае если поток параметров — (i,i), то str real завершается успешно, если Real связан с вещественным числом, равным числу, представленному в string. Предикат upper_lower/2 Предикат upper_lower преобразует строку, все символы (или часть символов) кото- рой являются символами верхнего регистра, в строку соответствующих символов нижнего регистра, и наоборот. Формат предиката: upper_lower(Upper, Lower) % (i,o), (o,i), (i,i) Если оба параметра связаны, то upper_lower завершается успешно, если upper и Lower связаны со строками, которые являются конвертируемыми друг в друга. На- пример, целевое утверждение: goal Strl-samPLEstrING, Str2=SAMpleSTRing, upper_lower(Strl, Str2). успешно. Предикат term_str/3 Предикат term_str является универсальным предикатом для преобразований, он совершает преобразование терма заданного домена в его строковое представление. Формат предиката: termjstr (Domain, Term, String) % (i,i,o),(i,_,i) Здесь Domain задает, какому домену принадлежит терм. Предикат term str может заменить все представленные выше предикаты str *, например, str_real может быть реализован как str_real(S,R)term str(real,R,S). Однако term_str имеет более сложную реализацию. Domain не обязан быть одним из стандартных доменов; он может быть также любым из доменов, определенных пользователем (листинг 20.4). гЛ истин г 20.4. Программа ch 13е04. pro domains intlist = integer* goal write("Input list (example [66,73,76,83]): "),
516 Часть IV. Программирование на Visual Prolog readln(L),nl, str_len(L,Len), write("The stringlength of ”,L), write(" is ",Len,'\n'). Рассмотрим несколько примеров: 1. Пример, приведенный в программе chl3e05.pro (листинг 20.5), определяет преди- кат scanner, который преобразует строку в список лексем. Лексемы классифици- руются с помощью связывания функтора с каждой лексемой. В этом примере используются предикаты isname, str int, str char для определения природы лексемы, полученной с помощью fronttoken. domains tok = numb(integer); name(string) ; char(char) toklist = tok* predicates nondeterm scanner(string, toklist) nondeterm maketok(string, tok) clauses scanner("",[]). scanner(Str,[Tok|Rest]):- fronttoken(Str, Sym, Strl), maketok(Sym, Tok), scanner(Strl, Rest). maketok(S,name(S)):-isname(S). maketok(S,numb(N) ) : -str__int (S, N) . maketok (S, char (C) ) :-str__char (S, C) . goal write("Enter some text:"),nl, readln(Text),nl, scanner(Text,T_List), write (T_List) . 2. Преобразования между типами доменов symbol и string, а также между char, integer и real производятся автоматически при использовании стандартных предикатов или в процессе оценки математического выражения. Вещественные числа округляются в процессе автоматического преобразования. Visual Prolog производит это автоматическое преобразование по мере необходимости при вы- зове предикатов: predicates р(Х)write("The integer value is ",X,'\n').
Гпава 20. Обработка строк в Visual Prolog 517 Следующие цели дадут одинаковый эффект в этом примере: Х=97.234, р(Х). Х=97, р(Х). Х='а', р(Х). 3. Программа сЫЗеОб.рго (листинг 20.6) грамматического разбора английского язы- ка представляет собой практический пример строкового грамматического разбо- ра. Этот пример производит непосредственный разбор строки. Если эта програм- ма будет расширяться, то строка может разделяться на лексемы с помощью тако- го же предиката, как и в программе chl3e05.pro. Если вы заинтересовались грамматическим разбором английского языка, мы ре- комендуем вам просмотреть Анализатор предложений и Геобазу (Sentence Analyzer и Geobase) на прилагаемом CD-диске в каталоге <CDROM>:\RUN\VPI \PROGRAMS. | Листинг 20.6. Программа ch 13е06.pro Ф I domains sentence - s(noun_phrase,verb_phrase) noun_phrase = noun (noun); noun_johrase (detrm, noun) noun = string verb_phrase = verb(verb); verb_phrase(verb,noun_phrase) verb = string detrm = string predicates nondeterm s_sentence(string,sentence) nondeterm s_noun_phrase(string,string,noun_phrase) nondeterm s_verb_phrase(string,verb_phrase) d(string) n(string) v(string) clauses s_sentence(Str,s(N_Phrase, V_Phrase)):- s_noun_phrase(Str, Rest, N_Phrase), s_verb_phrase(Rest, V_Phrase). s_noun_phrase(Str,Rest,noun_phrase(Detr,Noun)) fronttoken(Str,Detr,Restl), d(Detr), fronttoken(Restl,Noun,Rest), n(Noun) . s_noun_phrase(Str,Rest,noun(Noun));- fronttoken(STR,Noun,Rest), (Noun). s_verb_phrase(Str, verb_phrase(Verb,N_Phrase)) fronttoken(Str,Verb,Restl),
518 Часть IV. Программирование на Visual Prolog v (Verb) , s_noun_phrase(Restl,"”,N_Phrase). s_verb_phrase(Str, verb(Verb)) fronttoken(STR,Verb,""), v(Verb). % determiner d("the") . d("a") . % nouns n("bill"). n("dog"). n ("cat"). % verbs v("is"). goal s_sentence("bill is a cat", Result). Загрузите и запустите эту программу. Пролог вернет: Result - s(noun("bill”),verbjphrase("is", noun_phrase("a","cat”))) 1 Solution Резюме Отметим наиболее важные моменты, рассмотренные в этой главе: □ Стандартные предикаты обработки строк делятся на две группы: базовое управ- ление строками и преобразование строковых типов. □ Предикаты, предназначенные для базового управления строками: • froritchar, fronttoken и concat — для деления строки на компоненты, для построения строк из указанных компонентов и для проверки того, состоит ли строка из указанных компонентов; компоненты могут быть символами, лек- семами или строками; • subchar и substring — для возвращения отдельного символа из строки или ее части; • searchchar и searchstring — для нахождения позиции первого появления в строке символа или другой строки; • str_ien — для проверки или получения длины строки или создания пустой строки заданной длины; • front st г — для разделения строки на две различные подстроки; • isname — для проверки, что строка удовлетворяет всем ограничениям, нало- женным на имена Visual Prolog; • format — для форматирования переменного числа аргументов в строковую переменную.
Гпава 20. Обработка строк в Visual Prolog 519 Некоторые из предикатов, предназначенных для основных действий со строками, имеют несколько вариантов потоков параметров. В случае только входных пара- метров выполняется проверка, которая будет успешна, если обрабатываемая строка состоит из определенных компонентов (или имеет определенную длину). □ Предикаты, предназначенные для преобразования типа: • char_int — для преобразования символа в целое число или наоборот; • str_char — для преобразования отдельных символов в строку из одного сим- вола или наоборот; • str int — для преобразования целого в его текстовое представление или на- оборот; • str_real — для преобразования вещественного числа в строку и наоборот; • upper_lower — для преобразования строки во все символы верхнего или все символы нижнего регистра, а также для проверки регистро-независимого ра- венства строк; • term_str — для преобразования между произвольными доменами и строками. Каждый предикат преобразования типов имеет три варианта потока параметров; в случаях (i,o) и (o,i) выполняются соответствующие преобразования, а в слу- чае (i,i) осуществляется проверка, которая завершается успешно, только если два аргумента являются различными представлениями одного и того же объекта.
ГЛАВА 21 Внешние базы данных в Visual Prolog Система внутренних баз данных Visual Prolog, использующая предикаты asserta, assertz, retract и retractall, является простой и удобной. Однако она уступает в скорости работы с большими базами данных, отчасти из-за этих соображений была создана система внешних баз данных, с помощью которой можно, например, создать: О систему управления запасами с большим количеством записей; □ экспертную систему со многими отношениями и небольшим количеством запи- сей сложной структуры; □ учетную систему для запоминания больших текстов в базе данных; □ пользовательскую базу, в которой данные связаны не реляционным путем. Система внешних баз данных Visual Prolog поддерживает различные типы приложе- ний, и при этом удовлетворяет следующему требованию: недопустима потеря дан- ных при операции обновления (даже в случае прекращения подачи электропитания). Предикаты внешних баз данных Visual Prolog обеспечивают следующие возмож- ности: □ эффективная обработка больших объемов данных на диске; □ возможность поместить базу данных как в файл, так и в память; □ многопользовательский доступ; □ большая гибкость в работе с данными, чем та, что обеспечивается механизмом поиска с возвратом; □ способность сохранить и загрузить внешние базы данных в двоичном виде. Внешняя база данных Visual Prolog состоит из двух компонентов: единиц данных, т. е. термов Visual Prolog, сгруппированных в цепочки, и соответствующих В+ деревьев. которые используются для быстрого доступа к данным. Внешние базы данных запоминают данные в цепочках (chain), а не по отдельности, т. е. взаимосвязанные данные запоминаются совместно. Например, одна цепочка мо- жет содержать номера частей списков запасов, в то время как другая содержит име- на покупателей. Простые операции с базами данных, такие как добавления новых
Гпава 21. Внешние базы данных в Visual Prolog 521 величин и замена или обновление данных, не требуют применения В+ деревьев. Они становятся важными при сортировке базы и поиске конкретного элемента. Подробное описание В+ деревьев см. ниже в данной главе. Структура внешней базы данных изображена на рис. 21.1. Chain 1: Chain 2: Chain Л/: В+ дерево Л/ Рис. 21.1. Структура внешней базы данных Visual Prolog Соглашения об именовании Имена всех стандартных предикатов управления базами данных подчиняются сле- дующим соглашениям: □ первая часть имени (db_, chain , term_ и т. д.) идентифицирует то, что вы должны определить как входные данные; П вторая часть имени (flush, btrees, delete и т. д.) указывает на производимое действие или выходное данное.
522 Часть IV. Программирование на Visual Prolog Например, с помощью db_delete удаляется база данных, посредством chain_delete — цепочка, a term_delete удаляет терм. Селекторы внешних баз данных Есть возможность поддерживать несколько баз данных открытыми одновременно в памяти и на диске. Используя такую гибкость, можно помещать внешние базы дан- ных там, где достигается наилучший компромисс между быстродействием и затрата- ми памяти. Для указания одной из нескольких открытых баз данных при каждом вызове стан- дартных предикатов используются селекторы, которые определяются доменом db_selector. Он работает аналогично домену file в системе файлов. Например, сле- дующее объявление декларирует customers и parts селекторами внешних баз данных: domains db__selector = customers; parts Цепочки Внешняя база данных является набором термов Visual Prolog. Примерами термов могут быть integer, real, string, symbol, величины и составные объекты, как-то: 32, -194, 3.1417, "Wally", wages И book (dickens, "Wally goes to the zoo"). Внутри базы данные запоминаются в виде цепочек. Цепочка (chain) может содержать любое количество термов, а база данных — любое количество цепочек. Каждая це- почка идентифицируется именем, представляющим собой просто строку. Рис. 21.2 иллюстрирует структуру цепочки. Рис. 21.2. Структура цепочки Отношения и таблицы базы данных моделируются с помощью цепочек термов. На- пример, пусть мы имеем поставщика, покупателя и базу данных о товарах, и хотим поместить эти данные в одной базе с тремя отношениями: одно для покупателей, одно для поставщиков и одно для товаров. Это можно сделать, поместив всех поку- пателей в цепочку, названную "покупатели”, всех поставщиков в цепочку, названную "поставщики", и все товары — в цепочку "товары". Для внесения терма во внешнюю базу данных его необходимо включить в поимено- ванную цепочку. С другой стороны, можно производить поиск фактов без явного упоминания содержащей их цепочки. В обоих случаях вы должны указать домен, которому принадлежит терм. На практике лучше, если все термы в цепочке имеют один и тот же домен, но нет ограничений на то, как данные располагаются в базе. Для нас важно обеспечить, чтобы терм, который мы ищем, принадлежал к тому же домену, что и терм, который добавили в базу.
address) Гпава 21. Внешние базы данных в Visual Prolog 523 В следующем простом примере (программа chl4e01.pro, листинг 21.1) создаются две цепочечные базы данных: dbal и dba2. Все данные о покупателях хранятся в dbal, а все данные о товарах — в dba2. Теперь посмотрим, что здесь происходит. Листинг 21.1. Программа сМ4е01.рго «hattfakrVa*'f*»**>»*«htfaaaaaakaatata*^aaiatavapaIBJ ‘MtWn,l*IVIW*a|afci ««Vlhfcr Vlafca|aaaIWIaahlWaWaaaV«aaaaa domains db_selector = dbal; dba2 customers = customer(customer_name, parts - part(part_name, id, customer_name) cus tome rename, part_name = symbol id - integer address = string predicates access clauses access:- chain_terms(dbal,chainl,customers,customer(Name, Addr),_), chain_terms(dba2,chain2,parts,part(Part, Id, Name),_), writef’send ’’,Part," part num ",Id, " to ",Addr), nl, fail. access. goal % create the databases dbal and dba2 db_create(dbal, "ddl", in_memory) , db_create(dba2, "ddl.bin”, in_file), % insert customer facts into chainl in dbal chain_insertz(dbal, chainl, customers, customer(”Joe Fraser","123 West Side"), _), chain_insertz(dbal, chainl, customers, customer("John Smith","31 East Side"), _), chain_insertz(dbal, chainl, customers, customer("Diver Dan","l Water Way"), _), chain_insertz(dbal, chainl, customers, customer("Dave Devine","123 Heaven Street"), _), % insert parts facts into chain2 in dba2 chain_insertz(dba2, chain2, parts, part("wrench”, 231, "John Smith"), _) , chain_insertz(dba2, chain2, parts, part("knife", 331, "Diver Dan"), _), access, db_close(dbal), db_close(dba2), db_delete("ddl", in_memory), db delete("ddl.bin", in file).
524 Часть IV. Программирование на Visual Prolog Эта программа, прежде всего, создает базы данных dbal (в памяти) и dba2 (в файле на диске). Затем в базы включаются факты в две цепочки: chainl и chain2. После этого программа осуществляет поиск покупателя и товара, заказанного покупателем. Найдя их, возвращает адрес, по которому необходимо доставить товар. И, наконец, базы данных закрываются, и память очищается. Домены внешних баз данных Базы данных используют шесть стандартных доменов, приведенных в табл. 21.1. Таблица 21.1. Стандартные домены внешних баз данных Домен Для чего используется db_selector Домен для объявления селекторов баз данных bt_selector Домен для объявления селекторов В+ деревьев place Домен для объявления местоположения базы данных (в памяти или на диске) accessmode Домен, решающий, как использовать файл denymode Домен, определяющий, как остальные пользователи будут открывать файл Ссылка на расположение терма в цепочке Числа-указатели в базе данных Всякий раз при введении нового терма в базу данных Visual Prolog присваивает ему число-указатель. Вы можете использовать число-указатель терма (далее мы будем говорить просто ’’указатель") для выборки, перемещения или замены терма, а также для доступа к предыдущему или последующему терму. Вы можете также внести ука- затель в В+ дерево (см. ниже в этой главе), а затем использовать В+ дерево для сор- тировки или быстрого поиска терма. Указатель не зависит от местоположения базы данных или от возможных операций упаковки. Если указатель был связан с термом, вы можете использовать его для дос- тупа к терму, независимо от того, какие операции по управлению базой данных производятся, вплоть до момента удаления этого терма. Домен ref Указатель — это особый тип данных: вы можете включать его во внутреннюю базу данных, выводить его операторами write и writef, но не можете ввести его с кла- виатуры. Можно объявить аргументы предикатов, предназначенных для работы с указателями, как принадлежащие домену ref.
Гпава 21. Внешние базы данных в Visual Prolog 525 Если вы удалите терм предикатом term delete, система сможет вновь использовать указатель удаленного терма при внесении нового терма в базу данных. Это происхо- дит автоматически, однако если указатели в силу каких-то причин были сохранены во внутренней базе данных или в В+ дереве, то нельзя гарантировать, что данный указатель связан именно с тем термом, который вы имеете в виду. Здесь поможет опция проверки ошибок, включаемая стандартным предикатом db_reuserefs. Предикат db_reuserefs/2 Предикат db_reuserefs имеет следующую форму: db_reuserefs(DBase,ReUse) % (i,i) где DBase — это db_selector, a ReUse — беззнаковое целое число. Оно должно быть установлено в 0 для разрешения проверки на использование освобожденных термов или в 1 для ее запрещения. Расход памяти на проверку очень мал (4 байтална терм), но эти 4 байта никогда не будут освобождены. Если вы постоянно создаете и .удаляе- те термы, ваша база данных будет постоянно разрастаться. Основная цель db_reuserefs — помочь в отслеживании ошибок при разработке программы. Обработка баз данных Когда вы создаете новую внешнюю базу данных или открываете существующую, можно поместить ее в файл или в оперативную память, в зависимости от значения аргумента place при обращении к db create или db_open. По окончании работы с внешней базой данных она закрывается с помощью вызова db_close. Если база данных размещена в памяти, то закрытие базы с помощью db_close не будет приводить к удалению ее из памяти. Вы должны сделать это явно обращением к предикату db_delete для высвобождения памяти, занятой базой данных. Если вы закрываете базу (но не уничтожаете), то можете позднее открыть ее снова предика- том db_open. В этом разделе мы обсудим следующие предикаты: db create, db open, db_copy, db_loademsdb__delete, db_openinvalid, db_flush, db_btrees, db_chains и db statistics. Предикат db_create/3 Предикат db create создает новую базу данных. db_create(Dbase, Name, Place) % (i,i,i) Если база данных создается на диске, то Name указывает имя файла; если она созда- ется в оперативной памяти, то Name используется в качестве параметра db_close и db_open. Dbase и Name обозначают соответственно внутреннее и внешнее наименова- ние базы данных. Место расположения базы данных указывается переменной Place, которая может принимать одно из трех значений, описанных в табл. 21.2.
526 Часть IV. Программирование на Visual Prolog Таблица 21.2. Значения переменной Place Значение переменной Place Описание in__f ile in_memory interns Для экономии оперативной памяти база данных помещается на диске Для достижения высокой скорости доступа база данных помеща- ется в оперативной памяти Если установлена карта расширения памяти, то база данных помещается в EMS расширенной памяти, interns годится только для DOS. На других платформах она обладает эффектом in-memory Значения in__file, injnemory, in_ems являются элементами предопределенного домена place, который соответствует следующему объявлению: domains place = in_file; injnemory; interns Приведем два примера обращения к db_create: db_create (db_sell, "MYFILE. DBA", in_file) % Создает файл MYFILE.DBA на диске db_create (db_se!2, "SymName2" t injnemory) % Создает в памяти базу данных SymName2 Предикат db_open/3 Предикат db_open открывает предварительно созданную базу данных, идентифици- рованную параметрами Name и Place. db_open(Dbase, Name, Place) Если Place принимает значение injnemory, то Name соответствует символическому имени файла, а если Place принимает значение inpfile, то Name соответствует име- ни файла, принятому в DOS. Предикат db_copy/3 Независимо от того, где помещена база данных, ее можно будет переместить в дру- гое место, используя предикат db copy. dtjcopy(Dbase, Name, Place) % (i,i,i) Например, при вызове предиката db_copy*. db copy (my__base, "new_MemBase", injnemory) Visual Prolog копирует базу данных, связанную с селектором my_base, в новый файл базы данных newjlemBase, который размещается в памяти.
Гпава 21. Внешние базы данных в Visual Prolog 527 При копировании сохраняется исходная база данных, и вы будете иметь две иден- тичные базы данных до тех пор, пока явным способом не удалите одну из них. После перемещения базы данных ее обработка может проводиться, как если бы ни- чего не произошло, т. к. все указатели во внешней базе данных остаются правиль- ными. Таким образом, если вы работаете с базой данных в оперативной памяти, и освобождение памяти не занимает большого времени, вы можете скопировать базу данных в файл и продолжать обработку там. Указатель, установленный для базы данных в оперативной памяти, остается верным после копирования базы данных в файл. Предикат db_copy используется в нескольких случаях, например: □ для загрузки базы данных в оперативную память и для сохранения ее в файле после обработки (вместо использования предикатов save и consult); □ для копирования баз данных среднего объема с диска в память (для ускорения доступа); □ для сжатия базы данных, содержащей слишком много свободных мест, т. к. при копировании базы данных пустые места устраняются. Предикат db_openinvalid/3 Предикат db^openinvalid позволяет открыть базу данных, которая ранее была объ- явлена "неисправной”. db__openinvalid(Dbase, Name, Place) % Если в процессе обновления базы данных было отключено электропитание компью- тера, содержимое базы данных может быть утеряно, т. к. часть буфера могла быть не переписана на диск. После этого система объявляет базу данных "неисправной". База данных объявляется "неисправной" после обработки любого предиката, изме- няющего содержимое базы данных. Эти предикаты: chain_inserta, chain_insertz, chain_insertafter, term_replace, term_delete, chain_delete, bt_create, key_ insert и key_delete. База данных объявляется "неисправной" и после того, как она закрывается предикатом db_close, или после того, как происходит обращение к db_f lush для освобождения буферов. Предикат db_openinvalid позволяет продолжить обработку базы данных, если она была объявлена неисправной. При этом возможно восстановление части данных, если все дублирующие файлы были удалены. Однако необходимо проявлять долж- ную осторожность, попытка использования неисправной базы данных после откры- тия ее с помощью db_openinvalid может привести к нежелательным результатам. Предикат db_flush/1 Предикат db_flush высвобождает буферы и записывает их содержимое в места их назначения в базе данных: db flush(Dbase) % (i)
528 Часть /И Программирование на Visual Prolog После обновления базы данных она объявляется неисправной и остается такой до записи с помощью db_flush или до закрытия. Уровень надежности, с которым вы будете работать, зависит, безусловно, от значи- мости содержимого базы данных. Наиболее надежный путь — сохранять копии фай- лов на диске. Промежуточный уровень — обращение к db_flush после обновления базы данных. Однако освобождение буферов весьма медленная операция, и если она используется часто, ваша система баз данных будет слишком неповоротлива. Нако- нец, если содержимое вашей базы данных особенно ценно, вы можете записывать все изменения в особом файле — "журнале" или поддерживать две идентичные базы данных, может быть, на различных дисках. Предикат db_close/1 Предикат db_close закрывает открытую базу данных. db_close(Dbase) % (i) Если база данных Dbase помещается на диске, то соответствующий файл будет за- крыт. При этом база данных не будет удалена, даже если она помещена в оператив- ной памяти, т. е. вы можете открыть ее вновь путем обращения к db_open. Для уда- ления закрытой базы данных используется предикат db_delete. Предикат db_delete/1 Если база данных расположена в оперативной памяти, db_delete высвобождает за- нятую базой память. db_delete (Name, Place) % (i, i) Если база данных расположена в файле, то db delete удаляет файл. Если при обра- ботке db_delete база данных Name не находится в месте, указанном Place, возникает ошибка. Предикат db_btrees/2 В процессе поиска с возвратом предикат db_btrees последовательно связывает BtreeName с именем каждого В+ дерева в базе данных Dbase. nondeterm db btrees(Dbase, BtreeName) % (i,o) Имена B+ деревьев присваиваются в порядке сортировки. Предикат db_chains/2 В процессе поиска с возвратом пре^ди^^ат сП? chams пооче^ре/^но связывает перемен- ную ChainsName с именем каждой цепочки базы данных Dbase. nondeterm db_chains(Dbase, ChainName) % (i,o) Имена цепочек присваиваются в порядке сортировки.
Гпава 21. Внешние базы данных в Visual Prolog 529 Предикат db_statistics/5 Выходные данные предиката db_statistics — статистические сведения о базе дан- ных Dbase. db_statistics(Dbase, NoOfTerms, MemSize, DbaSize, FreeSize) % (i,o,0,0,0) Аргументы предиката db_statistics описаны в табл. 21.3. Таблица 21.3. Аргументы предиката db_statistics Аргумент Описание NoOfTerms Связывается с общим числом термов в базе данных MemSize Связывается с размером (в байтах) внутренней таблицы для фазы дан- ных, хранящейся в памяти DbaSize Связывается с общим числом байт, которые заняты термами и определе- ниями базы данных Dbase. Если Dbase записана на диске и DbaSize принимает значение намного меньшее, чем размер файла, то файл мо- жет быть сжат использованием db_copy FreeSize Связывается с величиной свободной памяти, зависящей от того, где Dbase находится в настоящее время. Если в оперативной памяти, то FreeSize связывается с количеством байт неиспользуемой памяти; если Dbase помещена в файле, FreeSize связывается с количеством сво- бодных байт на диске, содержащем файл Обработка цепочек Для включения термов в цепочку внешней базы данных следует использовать пре- дикаты chain_inserta, chain_insertz или chain_insertafter. Вы можете последова- тельно связывать термы в цепочке и их указатели с аргументами chain_terms. Пре- дикат chain delete удаляет цепочку термов из базы данных. Четыре стандартных предиката возвращают указатели базы данных. Это chain_first, chain_last, chain_next, chain_prev. Предикаты chain_inserta/5 и chain_Jnsertz/5 Предикаты chain_inserta и chain_insertz соответствуют assert а и assert z соответ- ственно. Вид их таков: chain_inserta(Dbase, Chain, Domain, Term, Ref) % (i,i,i,i,o) chain_insertz(Dbase, Chain, Domain, Term, Ref) % (i,i,i,i,o) При этом chain_inserta включает Term в начало цепочки Chain, a chain insertz помещает Term в конец цепочки. Dbase — это db selector базы данных, Domain —
530 Часть IV. Программирование на Visual Prolog домен переменной Term, a Ref — указатель на соответствующий тепл. Например, если объявлено, что my_dba имеет вид db_selector домена: domains db__selector = my_dba; .... то в следующем обращении: chain_inserta(my_dba, customer, person, p(john, "1 The Avenue", 32), NewRef) customer является именем цепочки, и все "покупатели" хранятся в одной цепочке. Очень хорошо хранить список поставщиков в качестве термов домена person, но в другой цепочке, возможно названной "поставщики" (supplier). Person — это имя домена, который содержит факт pfjohn, "1 The Avenue", 32), как показано в объяв- лении: domains person = pfname, address, age) Если цепочка Chain еще не создана, то она автоматически создается. Предикат chain_insertafter/6 Предикат chain_insertafter помещает терм после указанного терма, возвращая ука- затель нового терма. Он имеет вид: chain_insertafter(Dbase, ChainName, Domain, Ref, Term, NewRef) % (i, i, i, i, i, o) Предикат chain_insertafter вставляет терм Term после элемента цепочки, опреде- ленного указателем Ref, причем NewRef связывается с указателем, соответствующим терму Term после его вставки. П ре ди кат chain_ terms/5 В процессе поиска с возвратом предикат chain_terms поочередно связывает Term и Ref с каждым термом и соответствующим ему указателем в данной цепочке Chain Он имеет вид: chain_terms(Dbase, Chain, Domain, Term, Ref) % (i,i,i,o,o) Предикат chain_delete/2 Предикат chain_delete удаляет определенную цепочку из данной базы данных, of имеет вид: chain delete(Dbase, Chain) % (i,i)
Глава 21. Внешние базы данных в Visual Prolog 531 Предикаты chain_first/3 и chain_last/3 Эти предикаты возвращают указатели первого и последнего элемента цепочки: chain_first(Dbase, Chain, FirstRef) chain_last(Dbase, Chain, LastRef) % (i,i,o) % (i,i,o) Предикаты chain_next/3 и chain_prev/3 Предикат chain_next возвращает указатель терма, следующего за данным в цепочке, a chainjorev — указатель терма, предшествующего данному. chain__next (Dbase, Ref, NextRef) chainjorev(Dbase, Ref, PrevRef) % (i,i,o) % (i,i,o) Обработка термов Имеется три предиката работы с базами данных, обрабатывающих термы: term_replace, term_delete и ref_term. При обращении к любому из этих предика- тов необходимо объявить домен терма в качестве аргумента. Удобно объявить все термы данной базы как альтернативы одного домена, например: domains tеrms_fоr_my_stoek_control_database - customer(Customer, Name, Zipcode, Address); supplier(SupplierNo, Name, Address); parts(PartNo, Description, Price, SupplierNo) Здесь нет ограничений на смешивание доменов во внешней базе данных. Одна це- почка может содержать текстовые строки, другая — целые числа, а третья составные объекты и т. д. Однако элементы внешней базы данных не запоминаются вместе с описанием домена, например, целые не обязательно занимают именно два байта. Это ваше дело выбирать из базы данных терм в тот домен, из которого он был запи- сан в базу. Если вы перепутали домены, обычно происходит ошибка при исполнении. Предикат term_replace/4 Предикат term_replace заменяет терм (с указателем базы данных Ref) на новый терм с именем Term. term replace(Dbase, Domain, Ref, Term) % (i,i,i,i) Предикат term_delete/3 Предикат term_delete удаляет терм с указателем Ref. term delete(Dbase, Chain, Ref) % (i,i,i)
532 Часть IV. Программирование на Visual Prolog Память, занятая термом, будет высвобождена, и в дальнейшем нельзя допускать ссылки на Ref. Предикат ref_term/4 Предикат ref_term связывает Тепл с термом, который помещен под данным указате- лем Ref: ref_term(Dbase, Domain, Ref, Term) % (i,i,i,o) Приведенная ниже программа chl4e02.pro (листинг 21.2) использует почти все пре- дикаты работы с базами данных, определенные выше. Работая с оперативной па- мятью, программа производит следующие операции: 1. Записывает 100 термов в базу данных. 2. Считывает их. 3. Заменяет каждый второй терм. 4. Удваивает количество термов. 5. Удаляет каждый второй терм. 6. Просматривает термы с помощью предиката ref term. 7. Вычисляет размер базы данных. Затем программа копирует базу данных в файл на диске и .выполняет ту же последо- вательность действий уже с базой данных на диске. В завершение она подсчитывает (в сотых долях секунды) общее время, требуемое для выполнения этих действий. Заметим, что программа для иллюстрации генерирует большое количество выходных данных, что существенно замедляет ее исполнение. Запустите программу chl4e02.pro (листинг 21.2) и посмотрите, что произойдет, затем попробуйте изменить число термов и изучите поведение системы. м Листинг 21.2. Программа ch 14e02.pro domains my_dom = f(string) db_selector = my_dba predicates write_dba(integer) read_dba rd(Ref) count_dba(integer) count(Ref, integer, integer) replace_dba replace(Ref) double_dba double(Ref)
Гпава 21. Внешние базы данных в Visual Prolog 533 half_dba half(Ref) mixture clauses write_dba(0):-!. write_dba(N):- chain_inserta (my_dba,my__chain,my_dom, f ("Prolog system") ,_) , chain_insertz(my_dba, my_chain, my__dom, f("Prolog Compiler"), _) , N1=N-1, write_dba(Nl). read_dba:- db_chains(my_dba, Chain), chain_terms (my_dba, Chain, my__dom, Term, Ref), nl, write("Ref=", Ref), write(", Term=", Term), fail. read_dba:- db_chains (my__dba, Chain) , chain_first (my_dba, Chain, Ref) , rd(Ref), fail. read__dba. rd(Ref) ref_term(my_dba, my_dom, Ref, Term), nl, write(Term), fail. rd(Ref):- chain_next (my_dba,Ref,Next) , !, rd (Next) . rd(_) . replace_dba:- chain_first (my_dba, my_chain, Ref), replace(Ref). replace(Ref) :- termer epl ace (my_dba, my_dom, Ref, f( "Prolog Toolbox")), chain_next(my_dba, Ref, NN), chain_next (my__dba, NN, Next) , !, replace(Next). replace (__) . half_dba: - chain_last (my_dba, my_chain, Ref), half(Ref). half(Ref) chain_prev (my__dba, Ref, PP) , chainjorev (my_dba, PP, Prev) , *,
534 Часть IV. Программирование на Visual Prolog term_delete (myjdba, myjzhain, Ref), half(Prev). half(_). double_dba:- chain_first(my_dba, my_chain, Ref), double(Ref). double(Ref) chain_next(my_dba, Ref, Next),!, chain_insertafter(my_dba, my_chain, myjdom, Ref, f("Programmers Guide"), _), double(Next). double(_). count jdba(N) chain_first (my__dba, my_chain, Ref), count(Ref, 1, N). count(Ref, N, N2) chain_next(myjdba, Ref, Next),!, N1=N+1, count(Next, Nl, N2). count(_, N, N). mixture :-nl, write("Replace every second term:"), replace_dba,nl, write("Double the number of terms:"), double_dba,nl, write("Erase every second term:"), half_dba,nl, write("Use ref_term for all terms:"), read__dba, count_dba(N),nl, write("There are now ", N, " terms in the database"), db__statisties(my_dba, NoOfTerms, MemSize, DbaSize, FreSize),nl, writef("NoOfTerms=%, MemSize=%, DbaSize=%, FreeSize=%", NoOfTerms, MemSize,DbaSize,FreSize). goal nl,nl,nl, write("\tTest of database system\n\t***********************\n\n"), time(Hl, Ml, SI, DI), db_create(my_dba, "dd.dat”, injnemory),nl,nl, write("Write some terms in the database:"), write_dba(50), read_dba, mixture,nl,nl,
Гпава 21. Внешние базы данных в Visual Prolog 535 write("Copy to file"), db_copy(my_dba, "dd.dat", in_file), db_close(my_dba), db_delete("dd.dat", in_memory), db_open(my_dba, "dd.dat", in_file) , mixture, db_close(my_dba),nl,nl,nl, write("Open the database on file"), db_open(my_dba, "dd.dat", in_file), mixture, db_close (my__dba) , time(H2, М2, S2, D2) , Time = (D2-D1)+100.0*((S2-S1)+60.0*((M2-M1)+ 60.0*(H2-H1))),nl,nl, write("Time ~ ", Time, "/100 Sec"), nl. B+ деревья B+ дерево является структурой данных, которую можно применять для очень эффек- тивного метода сортировки большого количества данных; В+ деревья дают возмож- ность использовать эффективный алгоритм поиска и аналогичны указателям базы данных (В+ деревья иногда сравнивают с указателями). В Visual Prolog В+ деревья находятся во внешней базе данных. Каждый вход в В+ дерево — это пара величин: ключевая строка и связанный с ней указатель базы данных. При создании базы данных вы сначала заводите в ней запись и определяете ключ для этой записи. Затем Visual Prolog включает этот ключ и указатель, соответ- ствующий этой записи, в В+ дерево. При поиске записи в базе данных все, что необходимо сделать, — применить ключ для этой записи, и В+ дерево выдаст вам соответствующий указатель. Используя этот указатель, вы можете выбрать запись из базы данных. По мере построения В+ дерева его элементы содержатся в порядке, определяемом ключами. Таким обра- зом, вы можете легко получить отсортированный список записей. В+ дерево подобно бинарным деревьям, за исключением того, что в В+ дереве более чем одна ключевая строка запоминается в каждом узле. В+ деревья сбалансированы; это значит, что пути поиска каждого ключа в "листьях" дерева имеют одну и ту же длину. Вследствие этого, поиск каждого ключа (среди миллиона ключей) требует только нескольких операций доступа к диску, в зависимости от того, как много ключей запоминается в каждом узле. Хотя В+ деревья помещаются во внешних базах данных, они не нуждаются в указа- телях на термы в той же самой базе данных. Есть возможность иметь базу данных, содержащую ряд цепочек и другую базу данных с В+ деревом, указывающим на тер- мы в этих цепочках. Страницы, порядок и длина ключа В В+ дереве ключи сгруппированы в страницы, причем каждая страница имеет оди- наковый размер, и все страницы могут содержать одно и то же число ключей. Это значит, что все ключи В+ дерева должны иметь одинаковый размер. Размер ключей
536 Часть IV. Программирование на Visual Prolog определяется аргументом KeyLen, который вы должны определить в момент создания В+ дерева. При попытке внесения в В+ дерево строки длиннее, чем KeyLen, Visual Prolog обрежет ее. В общем, вы должны выбрать минимально возможную величину для KeyLen в целях экономии памяти и увеличения быстродействия. В момент создания В+ дерева необходимо задать величину аргумента Order. Этот аргумент определяет, сколько ключей должно запоминаться в каждом узле дерева. Наилучшая величина аргумента выбирается методом проб и ошибок. Хорошее пер- вое приближение для order — это 4, что соответствует запоминанию от 4 до 8 клю- чей в каждом узле. Вы можете выбрать величину order экспериментально, т. к. скорость поиска в В+ дереве зависит от величин KeyLen и order, числа ключей в В+ дереве и конфигурации вашей вычислительной системы. Двойные ключи Создавая В+ дерево, вы должны предусмотреть возможность использования повто- ряющихся ключей. Например, если вы создаете В+ дерево для базы данных покупа- телей, в которой ключом является фамилия покупателя, вам необходимо учесть всех покупателей с фамилией Смит. Здесь помогут двойные ключи в В+ дереве. Когда вы удаляете терм из базы данных, необходимо удалить соответствующий вход в В+ дерево с двойными ключами путем задания как ключа, так и указателя базы данных. Множественный просмотр Для того чтобы иметь более одного внутреннего указателя на одно и то же В+ дерево, вы можете открыть это дерево несколько раз. Заметьте, однако, что В+ дерево, на которое ссылается несколько указателей, не может быть обновлено, пока не удалены дополнительные указатели: для этого требуется закрытие В+ дерева соответствующее число раз. Стандартные предикаты для В+ деревьев Visual Prolog имеет несколько стандартных предикатов для обработки В+ деревьев, эти предикаты работают подобно одноименным предикатам db_... обработки баз данных. Предикаты bt_create/5 и bt_create/6 Новое В+ дерево создается с помощью предиката bt create: bt_create(Dbase, BtreeName, Btree_Sel, KeyLen, Order) % (i,i,o,i,i) bt_create (Dbase, BtreeName, Btree__Sel, KeyLen, Order, Duplicates) % (i, i,o,i,i,i) Аргумент BtreeName определяет имя нового дерева. Это имя используется в даль- нейшем в качестве аргумента для bt_open. Аргументы KeyLen и Order задаются в мо-
Гпава 21. Внешние базы данных в Visual Prolog 537 мент создания В+ дерева. После этого их изменять нельзя. Если вы вызываете bt__create/5 иди bt_create/6 с аргументом Duplicates, установленным в 1, то в В+ дереве можно использовать повторяющиеся ключи. Если вы . вызовите bt_create/6 с аргументом Duplicates, установленным в 0, то нельзя включать дуб- лирующиеся ключи в В+ дерево. Предикат bt_open/3 Этот предикат открывает уже созданное В+ дерево, идентифицированное именем, данным в bt_create: bt_open(Dbase, BtreeName, Btree_Sel) % (i,i,o) В момент открытия или создания В+ дерева возвращается значение селектора В+ дерева Btree_Sel. Селектор В+ дерева принадлежит к предопределенному домену bt selector и указывает на В+ дерево, когда система выполняет операции поиска или позиционирования. Связь между именем В+ дерева и его селектором такая же, как и связь между физическим именем файла и соответствующим ему символиче- ским именем. Вы можете открыть данное В+ дерево более чем один раз для выполнения несколь- ких одновременных просмотров. Всякий раз, когда В+ дерево открыто, устанавлива- ется описатель, и каждый описатель соответствует своему внутреннему указателю В+ дерева. Предикаты bt_close/2n bt_delete/2 Предикат bt close используется для закрытия В+ дерева, a bt delete — для его удаления: bt_close(Dbase, Btree_Sel) % (i,i) bt__delete (Dbase, BtreeName) % (i,i) Обращение к bt__close высвобождает внутренние буферы с именем BtreeName. Предикат bt_copyselector Предикат bt_copyselector выдает новый указатель на уже открытый селектор В+ дерева: bt_copyselector(Dbase,OldBtree^sel,NewBtree_sel) % (i,i,o) Новый селектор указывает на то же место В+ дерева, что и уже существующий. По- сле создания оба селектора В+ дерева могут свободно перемещаться, не влияя друг на друга. Предикат bt_statistics/8 Предикат bt_statistics возвращает статистическую информацию для В+ дерева, на которое указывает Btree_Sel*. bt_statistics (Dbase, Btree_Sel, NumKeys,NumPages, Depth, KeyLen, Order,PgSize) % (i,i,o,o,o,o,o,o)
538 Часть IV. Программирование на Visual Prolog Аргументы предиката bt statistics описаны в табл. 21.4. Таблица 21.4. Аргументы предиката bt__statistics Аргумент Описание Dbase Btree_Sel NumKeys NumPages Depth KeyLen Order PgSize Селектор, определяющий базу данных Селектор В+ дерева Общее число ключей в В+ дереве Btree_Sel Общее число страниц в В+ дереве Глубина В+ дерева Длина ключа Порядок В+ дерева Размер страницы (в байтах) Предикаты key_insert/4 и key_delete/4 Стандартные предикаты key_insert и key__delete используются для обновления В+ дерева: key_insert(Dbase, Btree_Sel, Key, Ref) % (i,i,i,i) key_delete(Dbase, Btree_Sel, Key, Ref) % (i,i,i,i) Задавая предикату key_delete оба аргумента: Key И Ref — можно удалить в В+ дереве определенный элемент с двойным ключом. Предикаты key_first/3, key_Jast/3 и key_search/4 Каждое В 4- дерево поддерживает внутренние указатели на свои узлы. Предикаты key_first и key_last позволяют установить указатель на первый или последний узел в В+ дереве соответственно. Предикат key_search устанавливает указатель на дан- ный ключ: key_first(Dbase, Btree_Sel, Ref) key_last(Dbase, Btree_Sel, Ref) key_search(Dbase, Btree_Sel, Key, Ref) % (i,i,o) % (i,i,o) % (i,i,i,o) (i,i,i,i) Предикат key_search завершается успешно, если ключ найден, в противном слу- чае — неудачно, а внутренний указатель В+ дерева устанавливается на ключ, сле- дующий после того, где должен был бы быть расположен ключ Key. Можно исполь- зовать key_current, чтобы указать ключ и указатель базы данных на этот ключ. Если вы хотите указать на точную позицию в В+ дереве с двойными ключами, то можете предоставить Ref в качестве входного параметра.
Гпава 21. Внешние базы данных в Visual Prolog 539 Предикаты key_next/3и key_prev/3 Предикаты key_next и key__prev используются для перемещения указателя В+ дерева вперед или назад: key_next(Dbase, Btree_Sel, NextRef) key__prev (Dbase, Btree_Sel, PrevRef) % (i,i,o) % (i,i,o) Если указатель находится на одном из концов В+ дерева, то попытка передвинуть указатель далее завершится неудачно, а указатель В 4- дерева будет работать так, как если бы он был помещен на одну позицию вне дерева. Предикат key_current/4 Предикат key_current возвращает ключ с указателем базы данных, соответствующий текущему положению указателя В+ дерева: key_current(Dbase, Btree_Sel, Key, Ref) % (i,i,o,o) Предикат key_current завершается неудачно после обращения к предикатам bt_open, bt__create, key__insert или key_delete, а также, если указатель находится перед первым (используется key_prev) или после последнего ключа (key_next). Рассмотрим пример доступа к базе данных через В+ деревья. Приведенная ниже программа сЫ4е04.рго (листинг 21.3) обрабатывает несколько текстовых файлов в одной базе данных. Вы можете выбрать и отредактировать тексты, как если бы они находились в разных файлах. Соответствующее В+ дерево предназначено для быст- рого доступа к файлам и сортировки их названий. йстинг 21 .3. ПрограммасМ4е04.рго f«k<t««tk k '<•« |'«:«ГМс • к »«1 **• « «'»« к к « « к , »»к th к ‘«'«'к 1к к к « I i « г » «к41|« м ht I kii,», Г| tikkk -j: Sr1: domains db_selector = dba predicates % List all keys in an index list_keys(db_selector,bt_selector) clauses list_keys(dba,Bt_selector):- key^current (dba, Bt_selector, Key,__), write(Key,’ 1), fail. list__keys (dba, Bt_selector) key__next (dba, Bt_selector,_), 1, list_keys (dba, Bt__selector) . ist_keys (_,_) . predicates open_dbase(bt_selector) main(db selector,bt selector)
540 Часть IV. Программирование на Visual Prolog ed (db_selector,bt_selectorrstring) edl(db_selector,bt_selector,string) clauses % Выполнение цикла до нажатия любой клавиши main(dba,Bt_select);~ write ("File Name: ’’) , readln(Name), ed(dba,Bt_select,Name), main(dbafBt_select). main(_,_)- % Предикат ed убеждает, что редактирование не имеет неудач, ed(dba,Bt_select,Name) edl(dba, Bt_sel$ct, Name),!. ed (_,_,_) * * Существуют три возможности: * а) Если имя есть пустая строка — просмотреть все имена * Ь) Имя уже существует — изменить содержимое файла * с) Новое .имя — создать новый файл edl(dba,Bt_select,""), key__first(dba,Bt_select,_), list_keys(dba,Bt_select), nl. edl(dba,Bt_select,Name) key__search (dba,Bt_select,Name,Ref) , !, ref_term(dba,string,Ref, Str) , edit(Str,Strl,"Edit old",NAME,"",0,"PROLOG.HLP",RET), clearwindow, StrXStrl, RET=0, term_replace(dba, string, Ref, Strl). edl (dba,Bt_jselect,Name) edit("",STR1,"Create New",NAME, "",0, ”PROLOG.HLP",RET) , clearwindow, ""xstrl, RET=0, chain_insertz(dba,file_chain,string,Strl,Ref), key_insert(dba,Bt_select,Name, Ref). open_dbase(INDEX):- existfile("ddl.dat"), !, db_open(dba,"ddl.dat",inpfile) , bt_open(dba,"ndx",INDEX).
Глава 21. Внешние базы данных в Visual Prolog 541 open_dbase(INDEX) db_cr eate (dba,"ddl.dat",in_file), bt_create(dba,"ndx",INDEX,20,4). goal open_dbase (INDEX), main (dba, INDEX) , bt_close(dba,INDEX), db_close (dba) . Программирование внешних баз данных Рассмотрим некоторые общие принципы и методы работы с системой внешних баз данных Visual Prolog. П Просмотр базы данных — показывает способ последовательного просмотра цепо- чек или В+ деревьев во внешней базе данных. П Вывод содержимого базы данных — определяет предикат, который вы можете ис- пользовать для вывода содержимого внешней базы данных. П Создание защищенной базы данных — показывает, как защищать базу данных от неожиданного отключения питания или других возможных аварий. П Обновление базы данных — содержит пример, который делает простым дополне- ние, изменение и защиту базы данных. П Использование указателей В+ деревьев — описывает несколько предикатов, пред- назначенных для установки указателя внутри открытого В+ дерева. П Изменение структуры базы данных — предлагает альтернативу старому методу изменения структуры базы данных. Просмотр базы данных При использовании систем баз данных важно представлять механизм работы с па- мятью Visual Prolog. Каждый раз, извлекая терм из внешней базы данных с по- мощью предиката ref_term, Visual Prolog помещает его в общий стек. Память, заня- тая этим термом, не освобождается до тех пор, пока программа не выдаст неудачу и не вернется к точке, находящейся перед вызовом предиката ref_term. Это значит, что для последовательного просмотра базы данных необходимо использовать алго- ритм, аналогичный следующему: % Структура для последовательного просмотра цепочки scan(db_selector, Chain, . .. .) chain_first(db_selector, Chain, Ref), scanloop(db_selector, Ref). scanloop(db_selector, Ref) ref_term(db__selector, mydom, Ref, Term), % ... ваши предикаты... fail.
542 Часть IV. Программирование HaVisual Prolog scanloop(db_selector, _) :- chain_next(db_selector, Ref, NextRef), scanldop(db_selector, NextRef). Для последовательного просмотра каталога индексов следует использовать структуру типа: % Структура для последовательного просмотра каталога scan(db_selector, Bt_selector) :- key_first(db_selector, Bt_selector, FirstRef), scanloop(db_selector, Bt_selector, FirstRef). scanloop(db_selector, Bt_selector, Ref) ref_term(db_selector, mydom, Ref, Term), % ... ваши предикаты... fail. scanloop(db_selector, Bt—selector, key_next(db—selector, Bt_selector, NextRef), scanloop(db_selector, Bt_selector, NextRef). Вы также можете осуществить последовательный просмотр цепочки в базе данных с использованием предиката chain terms: % Другой путь последовательного просмотра цепочки scan(db_selector, Chain) chain_terms (db_selector, Chain, mydom, Term, Ref), % ... ваши предикаты... fail. scan(_, . Для просмотра B+ дерева вы должны определить и использовать предикат bt_keys. В процессе поиска с возвратом предикат возвращает каждый ключ в В+дереве и связанный с ним указатель базы данных. % Этот фрагмент выполняется с программой chl4e05.pro predicates bt_keys(db_selector, bt_selector, string, ref) bt_keysloop(db_selector, bt—selector, string, ref) clauses bt_keys(Db_selector, Bt_selector, Key, Ref):- key_first(Db_selector, Bt_selector, _), bt—keysloop(Db_selector, Bt_selector, Key, Ref). bt_keysloop(Db_selector, Bt—Selector, Key, Ref) key_current(Db_selector, Bt_selector, Key, Ref). bt_keysloop(Db_selector, Bteselector, Key, Ref):- key_next (Db_selector, Bt—selector, _), bt kevslooofDb selector, Bt selector. Key, Ref).
Глава 21. Внешние базы данных в Visual Prolog 543 Вывод содержания базы данных Вы можете использовать предикат listdba, определенный ниже, для вывода текуще- го состояния базы данных. Он имеет один аргумент — селектор просматриваемой уже открытой базы данных. Все термы в базе данных должны быть одного домена. В примере (программа chl4e05.pro, листинг 21.4) определен домен mydom; при его использовании необходимо заменить mydom на имя домена из вашей программы. constants filename = "\\vip\\vpi\\programs\\register\\exe\\register .bin" domains db_selector = mydba mydom = city(zipcode, cityname); person(firstname, lastname, street, zipcode, code) zipcode, cityname, firstname, lastname, street, code = string predicates listdba(db_selector) nondeterm bt_keys (db_selectоr,bt__selector, string, ref) nondeterm bt_keysloop(db^selector,bt_selector,string, ref) clauses listdba(Db_selector) nl, ite(M********************************************nj *nlj write(" Database listing"),nl, wtite ("*********************************************’) z b_statisties(Db_selector,NoOfTerms,MemSize,DbaSize,FreeSize),nl,nl, write("Total number of records in the database: ",NoOfTerms),nl, write("Number of bytes used in main memory: ”,MemSize),nl, write ("Number of bytes used by the database: DbaSize) ,nl, write("Number of bytes free on disk: ",FreeSize),nl, fail. listdba (Db__selector) :- db_chains(Db_selector,Chain),nl,nl,nl,nl, write(”******* Chain LISTING *************"),nl,nl, write("Name=",Chain),nl,nl, write("CONTENT OF: ",Chain),nl, write("-------------------------------\n”), chain_terms (Db__selector, Chain, mydom, Term, Ref), write ("\n", Ref, ": *’,Term), fail. listdba(Db_selector) db btreesfDb selector,Btree), % Возврат имени каждого B+ дерева
544 Часть IV Протра/мд^вание на Visual Prolog bt_open(Db_selector,Btree,Bt_selector), bt_statistics(Db_selector,Bt_s elector,NoOfKeys, NoOfPages,Dept,KeyLen,Order,Pagesize) ,nl,nl,xix, writef'******** INDEX LISTING * + ******★**** + -),nl,nl, write("Name= ", Btree),nl, write("NoOfKeys= ", NoOfKeys),nl, write ("NoOfPages=", NoOfPages) ,.nl, write("Dept= ", Dept),nl, write("Order= ", Order),nl, write("KeyLen- ", KeyLen),nl, write("PageSize= ", PageSize), nl, write("CONTENT OF: ", Btree),nl, write ("----------------------------\n"), bt__keys (Db_selector, Bt_selector, Key, Ref), write("\n",Key, " — ",Ref), fail. listdba(_). bt_keys(Db_selector,Bt_selector, Key, Ref) key_first(Db_selector,Bt_selector,_), bt_keysloop(Db_selector,Bt_selector,Key,Ref). bt_keysloop(Db_selector,Bt_selector,Key,Ref)/ key__current (Db__selector, Bt_selector, Key, Ref). bt_keysloop(Db_selector,Bt_selector,Key,Ref) key_next(Db_selector,Bt_selector,_), bt_keysloop (Db_selector, Bt__selector, Key, Ref) . goal db_open (mydba, filename, in_file), listdba (mydba) . Создание защищенной базы данных Если вы вводите много новой информации в базу данных, то важно удостовериться, что она не будет утеряна при возможной аварии в системе. Покажем, как это сде- лать, путем ведения журнала изменений в другом файле. Внесение изменений включает обновление базы данных и заполнение ее. Если эта операция завершится успешно, система сама записывает изменения в журнал изме- нений. Это значит, что в каждый момент времени лишь один файл является недопи- санным на диске. Если файл с базой данных становится непригодным (например, из-за аварии в системе перед его заполнением), вы можете реставрировать его, ис- пользуя журнал изменений и дубликат исходной базы данных. Если же файл изме- нений становится непригодным, то придется создать новый дубликат базы данных и новый файл изменений. Если вы записываете дату и время в файле изменений вместе со старыми величи- нами данных, подвергшимися изменению или удалению, то привести базу данных
Гпава 21. Внешние базы данных в Visual Prolog 545 к тому состоянию, в котором она была в определенный момент времени, достаточно просто. % Этот фрагмент работает с программой chl4e05.pro г J domains logdom = insert(relation,dbdom,ref); replace (relation, dbdom, ref, dbdom); erase(relation,ref,dbdom) predicates logdbchange(logdom) clauses logdbchange(Logterm) chain_inserts(logdba,logchain,logdom,Logterm,_), db_flush(logdba). Обновление базы данных Основной принцип обновления базы данных таков: рекомендуется сосредоточить обновление базы в нескольких определенных вами предикатах. Это облегчает изме- нение базы данных и/или добавление новых В+ деревьев, а также способствует соз- данию корректных систем баз данных, т. к. ваша программа будет содержать лишь маленький участок, во время исполнения которого база данных незащищена. Следующий пример показывает обновление двух различных отношений, объекты которых являются строками: person(firstname, lastname, street, zipcode, code) city(zipcode, cityname) Обновление происходит с помощью следующих ключей, соответствующих отноше- ниям person И city: Person's Name.......... . .Last Name plus First Name Person's Address.........Street Name City Number..............Zip Code В этом примере мы подразумеваем, что В+ деревья уже открыты, и их bt_selector были установлены в предикате indices. Перед тем как программа начинает обновление, она исключает возможность ситуа- ции break с помощью предиката break. По окончании обновления программа запол- няет базу данных с помощью db_flush. Хотя db flush делает обновление довольно медленно, но обеспечивает сохранность файла. Чтобы сделать систему максимально безопасной, программа сЫ4е06.рго (лис- тинг 21.5) записывает изменения в специальном файле с помощью предиката logdbchange.
546 Часть IV Программирование на Visual Prolog ?>.': \: <<;/ ; < . ^. ? r ?. <d:'' L.; < :' _. хЛ^*^<д/^ Листинг 21.5; ПрограммасЬ14е06.ргов .•!•***•*•••***''• moiinm*i***itii#iii*i»» Чн**а«»м г • • tit jvt W #ЛШ»4|^6У^Й % Ведение журнала изменений базы данных domains logdom - insert(relation,dbdom,ref); replace(relation,dbdom,ref,dbdom); erase(relation,ref,dbdom) predicates logdbchange(logdom) clauses logdbchange(Logterm):- chain_insertz(logdba,logchain,logdom,Logterm,_), db_flush(logdba). domains dbdom = city(zipcode, cityname); person(firstname, lastname, street, zipcode, code) zipcode, cityname, firstname, lastname - string street, code = string indexName = person_name; person_adr; cityjao relation - city; person db_selector = dba; logdba facts % Содержит ключ, который является именем личности или адресом % или номером города; также содержит селектор В+ дерева indices(IndexName, bt_selector) predicates % Этот предикат создает имя индексации из последнего имени % (20 символов) и первого имени (10 символов) xname(FirstName,LastName,string) clauses xname(F,L,S) strjen (L, LEN) ,LEN>20, !, frontstr(20, L, Ll,_), format(S,"%-20%",Ll,F). xname(F,L,s):- format(S,"%-20%",L,F). predicates ba_insert(relation, dbdom) dba_replace(relation, dbdom, Ref) dba_erase(relation, Ref) clauses dba_insert(person,Term)
Гпава 21. Внешние базы данных в Visual Prolog 547 t • f break(OldBreak)f break(off), indices (person_nanie, II) f indices(person_adr,12), । - f Term = person (Fname, Lname, Adr,_,_J , xname(Fname,Lname,Xname), chain_insertz(dba,person,dbdom,Term,Ref), key_insert(dba,II,Xname,Ref), key_insert (dba, 12,Mr,Ref) , db_flush (dba) , logdbchange(insert(person,Term,Ref)) , break(OldBreak). dba_insert(city, Term):- break(OldBreak) , break(off) , indices(city_no, I) , ! Term = city(ZipCode,_), chain_insertz(dba,city,dbdom,Term,Ref), key_insert(dba,I,ZipCode, Ref) , db_flush(dba), logdbchange(insert(city,Term,Ref)), break(OldBreak). dba__replace (person, NewTerm, Ref) i ’ f break(OldBreak), break(off), indices(person_name, Il) , indices(person_adr,12), i J ref_term(dba, dbdom, Ref,OldTerm) , OldTerm-person (OldFname, OldLname, 01dAdr,_,_) , xname (OldFname, OldLname, OldXname) , key_delete (dba, II, OldXname, Ref) , key_delete(dba,12, Oldadr,Ref), NewTerm=person(NewFname,NewLname,NewAdr,_,_), xname(NewFname,NewLname,NewXname), term_replace(dba,dbdom,Ref,NewTe rm), key_insert(dba,I1,NewXname,Ref), key_insert(dba,12,NewAdr, Ref) , db_flush (dba) , logdbchange(replace(person,NewTerm,Ref,OldTerm)), break(OldBreak).
548 Часть IV Программирование на Visual Prolog dba_replace(city,NewTerm,Ref) i t break(OldBreak), break(off), indices(city_no,I), i • 9 ref__term (dba, dbdom, Ref, OldTerm) , 01dTerm=city(OldZipCode,_), key_delete(dba,I,OldZipCode,Ref), NewTerm=city(Zipcode,_), term_replace(dba,dbdom,Ref,NewTerm), key_insert(dba,I,ZipCode,Ref), db flush(dba), logdbchange(replace(city,NewTerm,Ref,OldTerm)), break(OldBreak). dba_erase(person,Ref) i - 9 break(OldBreak), break(off), indices(person_name,Il), indices(person_adr,12)t i 1 9 ref term(dba, dbdom, Ref, OldTerm), OldTerm^person (OldFname, OldLname, 01dAdr,_,_), xname(OldFname,OldLname,OldXname), key_delete(dba,II,OldXname,Ref), key_delete(dba,12,OldAdr,Ref), term_delete(dba,person,Ref), db_flush(dba) , logdbchange(erase(person, Ref, OldTerm)), break(OldBreak). dba_erase(city,Ref) break(OldBreak), break(off), indices (city__no, I) , 11 ref_term (dba, dbdom, Ref, OldTerm) , 01dTerm=city(OldZipCode, _) , key_delete(dba,I,OldZipCode,Ref), term_delete(dba,city,Ref), db_flush (dba) , logdbchange(erase(city,Ref,OldTerm)), break(OldBreak).
Гпава 21. Внешние базы данных в Visual Prolog 549 Использование внутреннего указателя В+ дерева Каждое открытое В+ дерево имеет указатель на свои узлы. В момент открытия или обновления В+ дерева указатель помещается перед началом дерева. Если вы обра- щаетесь к предикату key next, имея указатель на последнем ключе, он будет поме- щен за пределами дерева. Всегда, когда указатель перемещается за пределы дерева, key current завершается неуспешно. Если же это ограничение неприемлемо, то можно использовать предикаты mykey_ next, mykey_prev и mykey_search с гарантией, что указатель В+ дерева всегда нахо- дится внутри В+ дерева (если там есть хоть какие-то ключи). predicates mykey_next(db_selector, bt_selector, ref) mykey__prev (db_s elect or, bt_selector, ref) mykey__search (db_s elec tor, bt_selector, string, ref) clauses mykey_prev(Dba, Bt_selector, Ref) :- key_prev(Dba, Bt_selector, Ref), i « mykeyjprev(Dba, Bt_selector, Ref) :- key_next(Dba, Bt_selector, Ref), fail. mykey_next(Dba, Bt_selector, Ref) key_next(Dba, Bt_selector, Ref), i « « mykey_next(Dba, Bt_selector, Ref) key_prev(Dba, Bt__selector, Ref), fail. mykey_search(Dba, Bt_selector, Key, Ref) key_search(Dba, Bt_selector, Key, Ref), ! mykey_search(Dba, Bt_selector, Ref) :- key__current (Dba, Bt_selector, Ref), i mykey_search(Dba, Bt_selector, Ref) :- key_last(Dba, Bt_selector, Ref). Вы можете использовать предикаты samekey next и samekey_prev, определенные в следующем примере, для того, чтобы передвинуть указатель В+ дерева к следую- щему идентичному ключу, если дерево имеет дублируемые ключи, а predicates samekey_next(db_selector, bt_selector, ref) try_next(db_selector, btjselector, ref, string)
550 Часть IV. Программирование на Visual Prolog * samekeyjprev(db_selector, bt_selector, ref) try_jprev(db_selector, bt_s elector, ref, string) clauses samekey^next(Dba, Bt_selector, Ref) key_current(Dba, Bt^selector, OldKey, _), try_next(Dba, Bt_selector, Ref, OldKey). try_next(Dba, Bt_selector, Ref, OldKey) key__next (Dba, Bt_selector, Ref), key_current(Dba, Bt_selector, NewKey, __), NewKey ~ OldKey, i try_next (Dba, Bt_selector, _, __) key_prev(Dba, Bt_selector, __), fail. samekeyjprev(Dba, Bt__selector, Ref) key_current (Dba, Bt_selector, OldKey, _J , tryjprev (Dba, Bt_selector, Ref, OldKey). try_prev(Dba, Bt_selector, Ref, OldKey) key_jprev(Dba, Bt_selector, Ref)/ key^current (Dba, Bt_selector, NewKey, —), NewKey - OldKey, t • tryjprev(Dba, Bt_selector, _, _) key_next(Dba, Bt_selector, _), fail. Изменение структуры базы данных Одним из способов изменения структуры базы данных является копирование старой базы данных в новую с внесением изменений. Другой путь заключается в том, чтобы выгрузить базу данных в текстовый файл, внести изменения с помощью редактора текста, а затем считать (загрузить) обратно в обновленном виде. Предикат dumpDba, описанный ниже, предназначен для выгрузки базы данных в тек- стовый файл, если база данных удовлетворяет следующим условиям: П каждая цепочка в базе данных моделирует отношение; □ все элементы базы данных принадлежат одному домену. Эта методика не требует выгрузки имея в виду первое условие, что В+ деревьев в текстовый файл, мы предполагаем, В+ дерево может быть порождено из отношений. В этом примере все термы принадлежат родовому домену mydom, в момент примене- ния можно заменить mydom на требуемое имя домена и соответствующее его объяв- ление. Программа сЫ4е07.рго (листинг 21.6) записывает содержание базы данных в тексто- вый файл, открытый предикатом outfile. Каждая строка текстового файла содержит
Гпава 21. Внешние базы данных в Visual Prolog 551 терм и имя содержащей его цепочки. Имена цепочки и терма записываются в слож- ный объект домена chainterm. ............. «*НМ<М.|1ЦММ », 4«'а 4'««4' 4 ' »«•*»*•«<«4, HI 4 4 4 4 ».), Нм..4 1 ,Н,.И 44«44* 44И Я 1<1<44< О< < I |Йистинг21;б|;:Лрограмма-::€Ь14е07;рга-.?^;:;^^ ............. t't'»,», hHH.H.«4«НмПн.ои.н|;|м,.,.,,игМ1.н<>>1Н|кпП4||1н<.нн>|4|<.о>и.н<<ч«1<;<1иr„„.4t constants filename = "\\vip\\vpi\\programs\\register\\exe\\register.bin" domains Db_selector = myDba chainterm ~ chain(string, mydom) file - outfile mydom = city(zipcode, cityname); person(firstname, lastname, street, zipcode, code) zipcode, cityname, firstname, lastname = string street, code = string predicates wr(chainterm) dumpDba(string,string) clauses wr(X):- write(X),nl. dumpDba(Db_selector,OutFile) db_open(myDba,Db_selector,in_file) , openwrite(outfile,OutFile), writedevice(outfile) , db_chains(myDba,Chain) , chain_terms (myDba, Chain,mydom, Term,_), wr(chain(Chain,Term) ), fail. dumpDba (_,_) : - closefile(outfile), db_close (myDba) . goal dumpDba(filename,"register.txt"). Используя приведенную выше программу, можно сгенерировать текстовый файл, вызвав dumpDba. И вы можете перегрузить базу данных, используя предикат readterm для домена chainterm. Предикат dba^insert, определенный в разд. "Обновление базы данных" настоящей главы^ производит это обновление, domains chainterm - chain(string, dbdom) predicates nondeterm repfile(file) cop у Dba loadDba(string)
552 Часть IV. Программмование на Visual Prolog clauses repfile(_) . repfile(File) not(eof(File)), repfile(File). loadDba(OutFile) openread(Prn_file, OutFile)f readdevice(Prn_file), repfile(Prn_file)t readterm (Chainterm, chain(Chain, Term)), write(Term), nl, Dba_insert(Chain, Term), fail. loadDba(_) :- closefile (Prn__file) . copyDba :- createDba, db_open(Dba, "register.bin", inpfile), open_indices, loadDba ("register.txt1'), db_close(Dba). Разделение файлов и внешние базы данных Visual Prolog поддерживает разделение файлов внешних баз данных. Это означает, что файл может быть открыт несколькими пользователями или процессами одно- временно, что будет полезно, если вы используете внешнюю базу в сетевом прило- жении или на одной из многозадачных платформ. Пролог предоставляет следующие средства разделения файлов: □ два различных режима доступа и три различных режима разделения для оптими- зации скорости при открытии существующей базы данных; □ группировка доступов к базе данных в транзакции для обеспечения целостности; □ предикаты, позволяющие проверять, обновляли ли другие пользователи базу данных. Домены разделения файлов Два особых домена, которые используются для разделения файлов, описаны в табл. 21.5. Таблица 21.5. Домены, используемые для разделения файлов Домен Функторы AccessMode Чтение; чтение и запись DenyMode Все разрешено; запрещена запись; все запрещено
Гпава 21. Внешние базы данных в Visual Prolog 553 Открытие баз данных в режиме разделения Для доступа к внешней базе данных в режиме разделения вы должны открыть уже существующую базу данных версией предиката db open с четырьмя аргументами, задав значения доменов AccessMode и DenyMode. Если AccessMode равен read, файл откроется только на чтение (readonly), и любая попытка обновить файл приведет к ошибке во время исполнения; если AccessMode — readwrite, файл откроется на запись и на чтение. AccessMode исполь- зуется также с предикатом db_begintransaction. Если DenyMode равен denynone, все пользователи могут как модифицировать, так и читать файл; если DenyMode — denywrite, остальные пользователи не могут откры- вать файл с AccessMode = readwrite, но вы можете модифицировать файл, открыв его с AccessMode = readwrite. Если db_open вызван с DenyMode = denyall, никто из остальных пользователей вообще не сможет получить доступ к файлу. Первый пользователь, который открывает файл, определяет, произойдет ли ошибка при исполнении для всех последующих попыток открытия файла, а также при от- крытии файла в недопустимом режиме. На рис. 21,3 перечислены результаты откры- тия файла и последующих попыток открытия вновь того же самого файла для всех комбинаций DenyMode и AccessMode. 2-е, 3-е открытие ф ф DenyAII DenyWrite DenyNone R RW R RW R RW DenyAII R N N N N N N RW N N N N N N Deny Write R N N Y N Y N RW N N N N Y N DenyNone R N N Y Y Y Y RW N N N N Y Y Рис. 21.3. Результаты повторных открытий файла для всех комбинаций DenyMode и AccessMode Здесь: R: AccessMode - read RW: AccessMode = readwrite Y: 2-е, 3-е открытие... дозволено N: 2-е, 3-е открытие... не дозволено
554 Часть /К Программирование на Visual Prolog Транзакции и разделение файлов Если файл базы данных открыт в режиме разделения, все предикаты базы данных, осуществляющие доступ к файлу, должны быть сгруппированы в транзакцию; для этого вызовы предикатов надо окружить db-begintransaction и db_endtransaction.. В зависимости от комбинации выбранных значений параметров AccessMode и DenyMode, разделяемый файл может быть заблокирован на протяжении транзакции. В зависимости от серьезности блокировки, пока выполняется ваша транзакция, можно запретить остальным пользователям читать или обновлять файл. Это, безус- ловно, необходимо для избежания конфликтов чтения-записи, но, с другой стороны, чтобы разделение файлов было оправдано, чрезмерных блокировок быть не должно. Для этого нужно делать транзакции (насколько возможно) короткими и включать в них только те предикаты, которые осуществляют доступ к базе данных. В отношении разделения файлов концепция транзакций очень важна. Два основных конфликтующих требования — целостность баз данных и минимальное блокирова- ние файлов — должны быть выполнены одновременно. Предикат db_begintransaction гарантирует, что целостность баз данных поддер- живается, и что производится соответствующее блокирование файла. Несколько пользователей могут иметь одновременный доступ к файлу, но в каждый момент времени только одному процессу разрешено обновлять файл. Предикат cflb_setretry вызывают, чтобы установить, как долго db_begintransaction будет ожидать до- ступа к файлу прежде, чем возвратит ошибку во время исполнения. Вызов db_begintransaction с AccessMode, установленным в readwrite, При файле, откры- том с AccessMode и установленным в read, также возвратит ошибку во время испол- нения. Если был вызван db_begintransaction, то db_endtransaction должен быть вызван прежде нового вызова db_begintransaction для той же базы данных, иначе произойдет ошибка во время исполнения. Все действия, совершаемые предикатом db_begintransaction при различных комби- нациях параметров AccessMode и DenyMode, резюмируются, как показано на рис. 21.4. AccessMode DenyMode Read ReadWrite DenyNone WLock\Reload RWLock\Reload Deny Write None RWLock DenyAII None None Рис. 21.4. Действия предиката db_begintransaction при различных комбинациях DenyMode и AccessMode Здесь: WLock : Запись запрещена. Чтение разрешено. RWLock: Запись и чтение запрещены. Reload : Перезагрузка дескрипторов файлов.
Глава 21. Внешние базы данных в Visual Prolog 555 Так как перезагрузка и блокирование требуют времени, значения AccessMode и DenyMode следует выбирать аккуратно. Если никто из пользователей не собирается обновлять базу данных, установите AccessMode в read и DenyMode в denywrite для ускорения работы. Предикаты разделения файлов В этом разделе мы обсудим предикаты разделения файлов dbopen, db_begintransaction, db_endtransaction, db_updated, bt_updated и db_setretry. Предикат db_open/4 Версия db_open с четырьмя аргументами открывает существующую базу данных в режиме разделения. db_open(Dbase, Name, AccessMode, DenyMode) % После создания внешней базы данных в файле (in_file) предикатом db_create, она может быть открыта в режиме разделения, где Dbase — это селектор базы дан- ных, Name — имя файла, AccessMode — read или readwrite, a DenyMode — denynone, denywrite ИЛИ denyall. Предикат db_begintransaction/2 db_begintransaction(Dbase, AccessMode) % (i,i) Этот предикат обозначает начало транзакции и должен быть вызван до какого-либо доступа к базе данных, открытой в режиме разделения, даже если она открыта с denyall. Кроме селектора базы данных, в качестве аргумента db begintransaction должен быть передан AccessMode, связанный либо с read, либо с readwrite. Предикат db_endtransaction/1 db_endtransaction(Dbase) % (i) Этот предикат обозначает конец транзакции и выполняет разблокировку базы дан- ных. Вызов db endtransaction без предварительного вызова db_begintransaction для селектора базы данных Dbase возвратит ошибку на этапе исполнения. Предикат db_updated/1 db_updated(Dbase) Если другие пользователи могут обновлять базу данных, вызов db begintransaction будет гарантировать, что поддерживается целостность базы данных. Изменения мо- гут быть отслежены предикатом db_updated, который выполняется успешно, если он вызван внутри транзакции, в которой изменения, совершенные другими пользовате- лями, произошли после последнего вызова db_begintransaction. Если изменений не произошло, db_updated завершится неуспехом. Если предикат вызван вне транзак- ции, произойдет ошибка на этапе исполнения.
556 Часть IV. Программирование на Visual Prolog Предикат bt_updated/2 bt_updated(Dbase,Btree_Sel) % (izi) Этот предикат аналогичен db_updated/l, но успешен лишь в случае, если именован- ное В+ дерево было изменено. Предикат db_setretry/3 db_setretry(Dbase,SleepPeriod,RetryCount) % (i,i,i) Если доступ к файлу запрещен из-за того, что другой процесс заблокировал файл, вы можете заставить свой процесс ждать некоторое время и затем попробовать за- пустить его снова. Предикат db_setretry изменяет два значения по умолчанию: SleepPeriod — интервал в сотых долях секунды между попытками, и RetryCoun ~ максимальное число попыток доступа. Значение RetryCount по умолчанию — 100, a SleepPeriod — 10. Программирование с разделением файлов Предикаты разделения файлов надо применять крайне аккуратно. Даже при пра- вильном использовании они обеспечивают низкий уровень целостности разделяемой базы данных. Обеспечение целостности приложения на высоком уровне целиком лежит на разработчике приложения. Когда несколько процессов разделяют файл, особое внимание надо уделять вовле- ченным в дело доменам. Их идентичность и использование одинаковых выравнива- ний (align) — критичны. Во избежание ненужных блокировок файлов баз данных, транзакции следует делать небольшими, с целью обеспечения блокировки файла на максимально (по возмож- ности) короткое время. В то же время, важно, чтобы все предикаты, используемые для нахождения и доступа к некоторому элементу базы данных, были сгруппирова- ны внутри одной транзакции: db_begintransaction(dba,readwrite), key_current(dba,firstindex, Key,Ref)f ref__term(dba, string, Ref, Term), db_endtransaction(dba), write (Term) , В этом примере предикаты key_current и ref_term не должны быть расположены в разных транзакциях, т. к. терм, хранимый в Ref, может быть удален пользователем между транзакциями. Если В+ дерево обновлено другим пользователем, и файловые буферы переполнены, В+ дерево будет спозиционировано перед первым элементом дерева. Вызвав преди- кат bt_updated, вы можете отследить момент перепозиционирования В+ дерева. Пе- речислять все индексы (ключи) и, одновременно, делать маленькие транзакции воз- можно путем временного хранения текущего ключа во внутренней базе данных, как
Гпава 21. Внешние базы данных в Visual Prolog 557 показано в следующем фрагменте программы. Она работает с предположением, что дублированных ключей не существует. DOMAINS db__se lector = dba facts determ currentkey(string) PREDICATES list_keys(bt_selector) list_index(bt_selector) check—update (bt_selector, string) CLAUSES check_update(Index,Key): - not(bt—updated(dba,Index)), i / key_next (dba, Index,_) . check—update(Index, Key) key_search(dba,Index,Key,_), !. % Завершится неуспешно, если текущий check—update(_,_). % был удален другим пользователем list—keys(Index) currentkey(Key) , write(Key) ,nl, db_begintransaction(dba,read), check—update(Index, Key) , key_current(dba,Index, NextKey,_), db—endtransaction(dba), i - r retract(currentkey(_)), assert(currentkey(NextKey)), list—keys(Index). list—keys(_):- db_endtransaction(dba). list index(Index):- db—begintransaction(dba,read), key_first(dba,Index,_), key_current(dba,Index, Key,_) , db_endtransaction(dba), retractall(currentkey(_)), assert(currentkey(Key)), list—keys(Index). list—index (_) . Предикат key search используется для перепозиционирования B+ дерева на ключ, который был перечислен последним. Предикат my_search гарантирует, что В+ де-
558 Часть IV. Программирование на Visual Prolog рево будет правильно спозиционировано, даже если currentkey был удален другим пользователем. Приведенный выше пример также иллюстрирует другой важный аспект: db endtransaction должен быть использован после каждого и перед следующим вы- зовом db begintransaction. Выше в предикате list_keys перечисление прекращает- ся, когда key next завершается неуспехом, указывая, что все ключи были перечис- лены. Так как db begintransaction должен был быть вызван перед доступом к базе данных, db endtransaction должен быть вызван после завершения доступа. Второе list_keys предложение обеспечивает, что db_endtransaction будет вызван, если key_next завершится неуспехом. Реализация высокоуровневого блокирования Рассмотренные выше примеры проиллюстрировали некоторые проблемы разделения файлов и то, как их можно разрешать. Вы можете совершать над разделяемым файлом базы данных все те операции, какие вам были бы доступны, будь вы единственным пользователем файла. Группирование доступов к файлу внутри db_begintransaction И db_endtransaction позволяет сис- теме Visual Prolog обеспечивать целостность своих таблиц дескрипторов. Но на вы- соком уровне вы должны самостоятельно проверять, чтобы различные логические ограничения, накладываемые на ваше приложение, учитывались в сети с многочис- ленными пользователями. Назовем это блокировкой высокого уровня или блокировкой на уровне приложения. Ис- пользуя примитивы db-begintransaction И db jsndtransaction, МОЖНО разрабатывать собственные эффективные реализации высокоуровневого блокирования. Примером необходимости такого высокоуровневого блокирования является база данных, в которой пользователь хочет изменить запись. Для этого он должен дать команду приложению заблокировать эту запись до тех пор, пока пользователь не завершит ее изменение. После чего новая запись сможет быть записана обратно на диск и разблокирована. Несколько предложений по реализации блокировки на уровне приложения: □ выделите специальное поле записи для хранения информации о том, блокирова- на ли запись; □ создайте специальное В+ дерево или цепочку, где вы сможете хранить все ссыл- ки на все записи, блокированные пользователями; □ храните связанный с ref список ссылок на все блокированные записи. Замечание J Если вы хотите удалить В+дерево в файле базы данных, открытом в режиме раз- деления, то вы должны гарантировать (применением высокоуровневого блокирова- ния), что никакие другие пользователи не открывали этого В+ дерева. В системе Visual Prolog нет проверки на то, действителен ли указатель на В+ дерево, и что В+ дерево не было удалено другим пользователем.
Гпава 21. Внешние базы данных в Visual Prolog 559 Полный пример разделения файлов В следующем большом примере будет показано, как можно наиболее просто обеспе- чить разделение файлов, используя собственную систему блокировки. Этот при- мер — версия примера сЫ4е04.рго (см. листинг 21.3) с разделением файлов. Про- грамма ch!4e08.pro (листинг 21.7) позволяет нескольким пользователям создавать, редактировать, просматривать и удалять тексты из разделяемого файла. Когда текст создается или изменяется, он блокируется, пока операция не будет закончена. Дру- гие пользователи не могут изменять или удалять текст, пока он заблокирован, но они могут просматривать текст. Запустите программу и поэкспериментируйте с раз- личными установками для предикатов db_open и db_setretry. facts — indexes determ lockindex(bt—Selector) determ index(bt—selector) determ mark(real) DOMAINS my_dom = f (string) db_selector = dba PREDICATES nondeterm repeat wr_err(integer) % List texts and their status list_texts(bt_selector,bt—selector) show_textname(string,bt—selector) CLAUSES show_textname(Key,Lockindex) key_search(dba,Lockindex,Key,_),!, write("\n*",Key). show—textname (Key,_) write("\n ",Key) . list—texts(Index,Lockindex) key_current(dba,Index,Key,—), show_textname(Key,Lockindex), key_next(dba,Index,_),!, list—texts(Index,Lockindex). list—texts (__,_) . list:-nl, write(”***************** TEXTS (*=Locked) *******************"),nl, index(Index), lockindex(Lockindex),
560 Часть IV. Программирование на Visual Prolog key_first (dba, Index,_), list texts(Index, Lockindex),nl repeat. repeat:-repeat. wr_err(E):- errormsg("PROLOG.ERR",E,Errormsg,_) , write(Errormsg), readchar(_). PREDICATES %Logical locking of files lock(string,bt_selectorf bt_selector) CLAUSES lock(Name,Index,Lockindex) not(key_search(dba,Lockindex,Name,_)), i * r key_search(dba,Index,Name,Ref), key_insert(dba, Lockindex, Name, Ref). lock (Name,_,_) db_endtransaction(dba), write(Name," is being updated by another user.Xn Access denied"), fail. PREDICATES ed (db_selector, bt__selector, bt_selector, string) edl(db_selector, bt_selector, bt_selector, string) CLAUSES % The ed predicates ensure that the edition will never fail, ed(dba,Index,Lockindex,Name) edl(dba,Index,LockIndex,Name), /* ****************************** * Есть две альтернативы: * * 1) Имя уже существует — модифицировать содержимое файла * * 2) Имя новое — создать новый файл * * ***************************** */ edl(dba, Index,Lockindex, Name) db__begintransaction (dba, readwrite) , key_search(dba, Index, Name, Ref), t • f ref term(dba, string, Ref, Str),
Гпава 21. Внешние базы данных в Visual Prolog 561 lock(Name,Index,Lockindex), list, db_endtransaction(dba), nl, ) , nl, write (’’* EDIT ",Name," *"),nl, g И*******************************************************’ J Г11 write(Str),nl, write("< Press ’r’ to replace this string; else any key >"),nl, readchar(X),X=’r’, nl, write("Enter string and press <ENTER>"),nl, readln(Strl),nl, db_begintransaction(dba,readwrite) , term_replace(dba, string, Ref, Strl), ke\—delete(dba, Lockindex, Name, Ref), %unlock list, db_endtransaction(dba). %New file edl(dba, Index,Lockindex, Name):- chain_insertz (dba, file_chain, string, Ref), key_insert(dba, Index, Name, Ref), list r db_endtransaction(dba), edl(dba,Index,Lockindex, Name). PREDICATES main(db_selector, bt_selector, bt_selector) interpret(char, bt_selector, bt_selector) check_update_yiew update_view get—command(char) CLAUSES % Loop until ’Q’ is pressed main(dba,Index,Lockindex) che ck_updat e_ vie w, get—Command (Command), trap(interpret(Command,Index,Lockindex),E,wr_err(E)) , i ‘ r main(dba,Index,Lockindex). main (_,_,_) . check_update_view:- mark(T),timeout(T), i * r db_begintransaction(dba,read), update^view, db endtransaction(dba),
562 Часть1У/Программирование на Visual Prolog r d marktime(100,Mark), retractall(mark(_)), assert(mark(Mark)). check__update_view. update__view: -nl, writef******* COMMANDS E:Edit V:View D:DeleteQ:Quit ♦*♦♦*♦♦") ,nl, write("COMMAND>"), db_updated(dba), i - / list. update view. get_command (Command) readchar(C), 1, upper_lower(Command,C), write(Command),nl. get_command(’ '). %interpret commandline input interpret(’ ’, : - i , interpret (*Q’,_,_) : - fail. interpret(' E ’,Index,Lockindex):- !, write ("\nFile Name: ’’), readin(Name),nl, ed(dba,Index,Lockindex,Name). interpret('V',Index,_) : - write("\nFile Name: "), readln(Name),nl, db_begintransaction(dba,read), key_search(dba,Index,Name,Ref), i f refuterm(dba,string,Ref,Str), db_endtransaction(dba), J’1************************* *****t*it**iltif*ii* l**iii**^*»T j ? write(”* VIEW ",Name," ”),nl, write(Str),nl. interpret(’V',_):- i - 9 db_endtransaction(dba).
Гпава 21. Внешние базы данных в Visual Prolog 563 interpret('D',Index,_) write("\nDelete file: ”), readln(Name),nl, db_begintransaction(dba,readwrite), key_search(dba,Index,Name,Ref), i * / % not(key_search(dba,Lockindex,Name,_)), key_delete(dba,Index,Name,Ref), term_delete (dba, f ile__chain, Ref), list f db endtransaction(dba). interpret (' D', - ! * r db_endtransaction(dba). interpret (_,_,„) beep. PREDICATES open—dbase (bt_selector,bt_selector) CLAUSES open_dbase( INDEX, LOCKINDEX) existfile("share.dba"), db—openfdba, "share.dba",readwrite,denynone), db—begintransaction(dba,readwrite), bt_openfdba, "locks", LOCKINDEX), bt_open(dba, "ndx", INDEX), db_endtransaction(dba). open_dbase(INDEX,LOCKINDEX) db_create(dba,"share.dba", in_file), bt—create(dba, "locks",TEMPLOCKINDEX,20, 4), bt—create(dba, "ndx",TEMPINDEX, 20, 4), bt—close(dba, TEMPINDEX), bt_Close(dba, TEMPLOCKINDEX), db_close(dba), open—dbase(INDEX,LOCKINDEX). GOAL open_dbase (INDEX, LOCKINDEX) , assert(index(INDEX)), assert(lockindex(LOCKINDEX)), marktime(10,Mark), assert(mark(Mark)), db_setretry(dba,5,20), db—begintransaction(dba,read),
564 Часть /И Программирование на Visual Prolog list,nl, db_endtransaction(dba)t main(dba, INDEX,LOCKINDEX), db_begintransaction(dba,read), bt_close(dba, INDEX), bt close(dba, LOCKINDEX), ***** db_endtransaction(dba), db_close (dba) . Аспекты реализации разделения файлов в Visual Prolog Разделение файлов в Visual Prolog действенно и быстро, т. к. загружаются после из- менения другим пользователем только необходимые части дескрипторов файлов баз данных. Как было показано ранее, лишь в определенных обстоятельствах нужно де- лать перезагрузку буферов и блокирование файлов; сложное внутреннее управление файлами баз данных обеспечивает, что после внесения изменений будет необходим минимум действий с диском. База данных имеет серийный номер, 6-байтовое целое число, которое реализуется и записывается на диск при каждом обновлении. Предикат db_begintransaction сравни- вает локальную копию серийного номера с копией на диске, и, если они разные, дескрипторы перегружаются. Блокировка делается в массиве на 256 читателей. Когда читатель хочет получить доступ к файлу, незаблокированное пространство помеща- ется в массив блокировки и блокируется на время транзакции. Это позволяет не- скольким читателям получить доступ к файлу одновременно. Если предикат db_begintransaction вызван с AccessMode = readwrite, он будет ждать, пока все присутствующие пользователи не разблокируют пространство, а затем заблокирует весь массив целиком. Резюме Напомним основные вопросы, затронутые в этой главе. □ Термы внешней базы данных запоминаются в цепочках, к которым вы можете получить прямой доступ с помощью указателей базы данных:, эти указатели при- надлежат к предопределенному домену ref. □ Индивидуальность баз данных идентифицируется селекторами базы данных, принадлежащими к стандартному домену db_selector. □ Вы можете хранить базу данных в двух местах, в зависимости от аргумента, при- надлежащего к предопределенному домену place: • in file помещает базу данных в файл на диске; • in memory помещает ее в оперативную память. □ Если вы хотите сортировать термы в базе данных, то следует использовать В+ деревья. Как и базы данных, В+ деревья обозначаются селекторами, принад- лежащими к стандартному домену bt_selector.
Глава 21. Внешние базы данных в Visual Prolog 565 □ Каждый вход в узел В+ дерева содержит строку key (также называемую индек- сом), которая идентифицирует запись, и указатель базы данных, который связан с этой записью. □ Ключи В+ дерева сгруппированы в страницы, и число ключей, содержащихся в одном узле, определяется порядком (order) дерева. □ Разделение файлов совершается путем группирования в транзакции предикатов, которые получают доступ к базе данных.
ГЛАВА 22 Программирование на системном уровне Visual Prolog имеет несколько предикатов для непосредственного доступа к операци- онной системе. Для начала мы рассмотрим предикаты, которые позволяют обра- щаться к операционной системе, а затем уделим внимание предикатам побитовой обработки чисел. После этого мы рассмотрим группу предикатов поддержки низко- уровневого режима работы с памятью. Доступ к операционной системе Пользуясь предикатами, можно включать возможность доступа к ОС непосредствен- но из прикладной программы на Visual Prolog. Обращение к ОС можно выполнять вызовом предиката system, обращение к утилитам формирования даты и времени — предикатов date и time, с помощью предиката envsymbol возможно получение пере- менных среды ОС, а с помощью comline — чтение аргументов командной строки, с которыми была вызвана программа. Более того, вы можете получить стартовый каталог и имя программы вызовом syspath, а предикаты marktime, timeout и sleep предоставляют возможность устанавливать и ожидать наступления установленного момента времени. Также существуют предикаты sound и beep, и, наконец, osversion, возвращающий версию ОС, diskspace, возвращающий объем свободного места на диске, и три версии storage, позволяющие определять объем используемой памяти. Настоящий раздел описывает подробно каждый из этих предикатов. В нем приведе- ны несколько практических примеров их использования. Предикат system/1 Программа на Visual Prolog получает доступ к ОС через предикат system, имею- щий вид: system("command”) % (i) Если аргумент является пустой строкой, то новый командный интерпретатор будет запущен в интерактивном режиме — появляется возможность обращения к ОС, на- пример, с клавиатуры.
Гпава 22. Программирование на системном уровне 567 Приведем два примера: 1. Для копирования файла B:\ORIGINAL.FIL в файл A:\NEWCOPY.FIL из систе- мы Visual Prolog вызовите: system (””) После этого скопируйте файл, используя обычную команду ОС: сору B:\ORIGINAL.FIL A:\NEWCOPY.FIL Возврат к системе Visual Prolog выполняется набором команды exit после которой снова происходит возврат в программу. 2. Для переименования файла (без работы непосредственно в ОС) необходимо по- дать команду наподобие: system("ren newcopy.fi! newcopy.txt") Предикат system/3 Эта расширенная версия предиката system обладает двумя дополнительными воз- можностями: с помощью нее можно узнать уровень ошибки ОС, а также переопре- делить режим вывода на экран системы во время работы с ней. Последняя возмож- ность при работе с Windows эффекта не дает. Вид предиката system/3: system(Commandstring, Resetvideo, ErrorLevel) % (i,i,o) Здесь переменная ErrorLevel связывается co значением уровня ошибки ОС. Это программный код возврата, известный ОС в момент, когда программе возвращается управление, т. е. в момент завершения вызова system. В текстовом режиме DOS переменная Resetvideo сообщает о необходимости воз- вращения видеоаппаратуры в состояние, в котором она находилась перед обработ- кой предиката system/З. Значение Resetvideo = 1 возвращает ее в первоначальное положение, Resetvideo = 0 — не возвращает. Если Resetvideo = 0, то выполнение программы будет поддерживаться в экранном режиме, заданном вами. (Инфор- мацию об установке режима экрана см. в справочном руководстве по видеоаппара- туре.) Предикат envsymbol/2 Предикат envsymbol выполняет поиск переменных среды в таблице ОС. Эти пере- менные могут быть установлены командой операционной системы SET и другими средствами ОС. Предикат envsymbol имеет вид: envsymbol(EnvSymb, Value) % (i,o)
568 Часть IV. Программирование на Visual Prolog Например, команда SET SYSDIR=C:\FOOL присваивает переменной sysdir строку C:\FOOL,a цель о. О envsymbol("SYSDIR", SysDir), выполняет поиск переменной среды sysdir в таблице переменных среды и связыва- ет SysDir с C:\FOOL. Предикат envsymbol завершается неуспешно, если заданная переменная среды не определена. Предикаты time/4 и date/3/4 Visual Prolog имеет два часто используемых стандартных предиката: date и time. Каждый из них может быть использован двумя способами, в зависимости от того, свободны или связаны переменные при вызове предиката. Если все переменные связаны, то предикат time переопределяет системные часы на новое значение времени. Если переменные свободны, то система связывает их со значениями, полученными от системных часов. time(Hours, Minutes, Seconds, Hundredths) % (i,i,i,i), (o,o,o,o) Предикат date/3 также связан с системными часами, обрабатывается аналогично time и имеет вид: date(Year, Month, Day) % (i,i,i), (о, о, о) Предикат date/4 имеет только версию, у которой все аргументы выходные. Четвер- тый аргумент — номер дня недели, но используемая схема нумерования зависит от ОС. Обычно, 0 — это воскресенье, 1 — понедельник и т. д. date(Year, Month, Day, WeekDay) % (о, о, о, о) В качестве примера рассмотрим программу сЫ5е02.рго (листинг 22.1), которая ис- пользует time для сообщения времени, затраченного на листание каталога, заданно- го по умолчанию. .•****•$ я* **** Г- Mri Листинг 22.1. Программа ch15e02.pro л goal time (Hl, Ml, Sl,_), nl, write("Start time is: ”, Hl,":",Ml,":SI),nl, % This is the activity that is being timed system("dir /s/Ь c:\\*.*”), time (H2,М2, S2,_), Time = S2-S1 + 60*(M2-M1 + 6O*(H2-H1)),
Гпава 22. Программирование на системном уровне 569 write("Elapsed time: ",Time," seconds"),nl, time(H3,M3,S3,_), write("The time now is: ",H3,":",М3,":",S3). Предикат comline/1 Предикат comline считывает параметры командной строки, использовавшейся при вызове программы. Формат предиката: comline(CommandLine) % (о) Здесь CommandLine — строка. Предикат syspath/2 Предикат syspath возвращает стартовый каталог и имя вызывающей программы. Он имеет вид: syspath(HomeDir,ExeName) % (о, о) Предикат syspath используется для предоставления программам возможности за- грузки файлов из их домашнего каталога. Сервисы времени Visual Prolog предоставляет два различных сервиса времени: приостановку выполне- ния и проверку использованного времени. Предикат sleep/1 Предикат sleep приостанавливает выполнение программы на заданное время. Он имеет вид: sleep(CentiSecs) % (i) Здесь CentiSecs — это время, измеряемое в сотых долях секунды, на которое про- грамма будет приостановлена. Точное время, в течение которого программа будет ждать, зависит от активности процессора и ОС. Не следует ожидать точности боль- шей, чем 20—50 миллисекунд. Предикат marktime/2 Предикат marktime возвращает отметку времени, которая далее может быть провере- на на истечение, используя предикат timeout. Предикат marktime имеет вид: marktime(CentiSecs,Ticket) % (i, о) Здесь CentiSecs — требуемое время, в течение которого Ticket должен продолжать- ся. Отметим, что Ticket — не просто время. Эта структура хранит некоторую специ- альную информацию в недокументированном формате. Ticket предназначена только для последующего использования предикатами timeout и difftime.
570 Часть IV Программирование на Visual Prolog Предикат timeout/1 Предикат timeout проверяет на истечение отметку времени, возвращенную marktime. Если время истекло, timeout успешен, иначе — неуспешен. Предикат timeout имеет вид: timeout(Ticket) % (i) Как и с предикатом sleep, большой точности вам ожидать не следует. Предикат difftime Visual Prolog имеет стандартный предикат difftime: difftime(real,real,real) % (i,i,o) который возвращает разницу между первым и вторым временными маркерами в формате вещественного числа. Первый временной маркер должен быть моложе, чем второй, т. е. возможно следующее использование: marktime (0, Ml), lengthy_processf marktime (О, М2) , difftime(М2,Ml,Diff). В качестве примера рассмотрим программу chl5e04.pro (листинг 22.2), демонстри- рующую работу marktime и timeout. Jw* г* • • * • * • * * ** * * • • • 1 * , ♦ *• ’ь * О'* * * * ' * »'• ** • h * * * • • 1 * 10 * * »I* ь г» • nv« Wn t *%'•»♦*•»*» if**» r> ri predicates ttimeout(real) clauses ttimeout(TM):-timeout(TM), ! . ttimeout(TM):- write("No timeout, sleep 0.5 secs"),nl, sleep(50), ttimeout(TM). goal marktime (400, TM) , ttimeout (TM), write(”\nBINGO!\n"). % 4 secs Предикат sound/2 Предикат sound генерирует звук аппаратного спикера: sound(Duration,Frequency) % (i,i) Здесь Duration — это время, выраженное в сотых долях секунды.
Гпава 22. Программирование на системном уровне 571 Предикат Ьеер/О Предикат beep эквивалентен sound(50,1000) и.имеет вид: beep % (no arguments) Предикат osversion/1 Предикат osversion возвращает версию текущей ОС и имеет вид: osversion(VerString) % (о) Формат VerString зависит от ОС. Предикат diskspace/2 Предикат diskspace возвращает в беззнаковом длинном целом доступное простран- ство на диске и имеет формат: diskspace(Where,Space) % (i,o) Space выражен в байтах. В DOS-ориентированных версиях Visual Prolog where — это символ, обозначающий имя диска. Предикаты storage/Зм storage/11 Стандартный предикат storage возвращает информацию о трех областях памяти, используемых системой для исполняемых программ (стек, куча (heap) и след (trail) соответственно) в виде беззнаковых длинных целых: storage(StackSize,HeapSize,Trailsize) % (о,о,о) Все значения аргументов измеряются в байтах. Во всех версиях Visual Prolog Trailsize содержит количество памяти, используемой для области следа. stacksize указывает, сколько осталось свободного стекового пространства. Heapsize показывает, сколько памяти доступно процессу. storage/11 является расширенной версией предиката storage/3. storage/11 возвра- щает более подробную информацию об используемых приложениями областях па- мяти для исполняемых программ. Описание этого предиката вы можете найти в ин- терактивной системе помощи Visual Prolog. Предикат storage/0 Версия storage без аргументов предназначена для отладки. Этот предикат печатает в специальном окне, сколько памяти используют различные части системы управле- ния памятью Visual Prolog и число точек возврата.
572 Часть /К Программирование на Visual Prolog Операции на уровне бит Visual Prolog включает шесть предикатов для битовых операций: bitand, bitnot, bitor, bitxor, bitleft и bitright. Эти предикаты имеют по одному варианту потока параметров, работают с целыми числами и должны использоваться в префиксной нотации. Предикат bitnot/2 Предикат bitnot выполняет функции побитового логического "НЕ": bitnot(X, Z) % (i,o) Если х связана с целой величиной, z будет связана с результатом побитового логиче- ского отрицания х, как показано в табл. 22.1. Таблица 22.1. Предикат Ы tnot Оператор х z Bitnot 1 О ° 1 Предикат bitand/3 Этот предикат выполняет побитно логическую операцию "И". Он имеет вид: bitand(X, Y, Z) % (1,1,0) Если х и y связаны с целыми числами, z будет связана с результатом побитового применения логической операции "И" к соответствующим битам двоичного пред- ставления х и Y (табл. 22,2). Таблица22.2. Предикатbitand Оператор X Y z Bitand 1 1 1 1 0 0 0 1 0 0 0 0 Предикат bitor/3 Предикат bitor выполняет побитовое логическое "ИЛИ”: bitor(X, Y, Z) % (i,i,o)
Глава 22. Программирование на системном уровне 573 Если х и Y связаны с целыми числами, z будет связана с результатом побитового применения логической операции "ИЛИ” к соответствующим битам двоичного представления х и y (табл. 22.3). Таблица 22.3. Предикат Ы to г Оператор X Y Z bi tor 1 1 1 1 0 1 0 1 1 0 0 0 Предикат bitxor/3 Предикат bitxor выполняет побитовое логическое "исключающее ИЛИ": bitxor(X, Y, Z) % (i,i,o) Если х и Y связаны с целыми числами, то z будет связана с результатом побитового применения логической операции "исключающее ИЛИ" к соответствующим битам двоичного представления х и Y (табл. 22.4). Таблица 22.4. Предикатbitxor Оператор z bitxor 1 1 О 1 0 1 О 1 1 ООО Предикат bitleft/3 Предикат bitleft выполняет побитовый сдвиг влево: bitleft(X, N, Y) % (i,i,o) Y будет связана с результатом, полученным при сдвиге х влево на ы разрядов (бит в двоичном представлении х). Оставшиеся справа разряды заполняются нулями. Предикат bitright/3 Предикат bitright выполняет сдвиг вправо: bitright (X, N, Y) % (i,i,o)
574 Часть IV. Программирование на Visual Prolog Если х и N связаны с целыми величинами, то Y связывается со сдвинутым вправо на N разрядов двоичным представлением числа х. Оставшиеся слева разряды заполня- ются нулями. Упражнение Напишите программу и покажите, что: myxor(A, В, Result) bitnot(В, NotB), bitand(A, NotB, AandNotB), bitnot(A, NotA)t bitand(NotA, B, NotAandB)r bitor(AandNotB, NotAandB, Result) действует аналогично: bitxor(A, В, Result) Прямой доступ к памяти Visual Prolog имеет четыре встроенных предиката для прямого (низкоуровневого) доступа к памяти. Это предикаты ptr__dword, memDword, memWord И memBite. Предикат ptr_dword Предикат ptr dword сообщает абсолютный адрес StringVar или создает строку StringVar на основе полученного адреса. ptr_dword(StringVar, Seg, Off) % (o,i,i), (i,o,o) Если stringVar связана, то предикат ptr_dword возвращает номер сегмента (Seg) и смещение (off) строки StringVar. Если связаны Seg и Off, предикат ptr_dword связывает stringVar со строкой, которая содержится в этом месте. На 32-битных платформах сегмент игнорируется. Предикат ptr_dword в значительной степени был вытеснен функцией cast. Строкой в Visual Prolog является последовательность символов ASCII, заканчиваю- щаяся пустым символом. Вы можете использовать процедуры низкого уровня для обработки нестандартных строк (тех, что содержат несколько нулевых байт). Однако нельзя осуществлять вывод нестандартных строк или включать их в базу данных. Предикаты те т Byte, memWord и memDword Visual Prolog содержит три предиката для получения и изменения определенных элементов памяти: memByte, memWord и memDword. Они используются для доступа к byte, word и dword соответственно. Все предикаты имеют два формата: memByte(Segment, Offset, Byte) memWord(Segment, Offset, Word) memDword(Segment, Offset, DWord) % (i,i,i), (i,i,o) % (i,i,o) % (i,i,i),
Гпава 22. Программирование на системном уровне 575 и memByte(StringVar, Byte) % (i,i), (i,o) memWord(StringVar, Word) % (i,i), (i,o) memDword(StringVar, DWord) % (i,i), (i,o) Segment имеет тип ushort, Offset — тип unsigned, a Byte, Word и Dword — тип byte, word и dword соответственно. В реальном режиме DOS месторасположение в памяти вычисляется по формуле ((Segment * 16) + Offset). Предикаты mem* в значительной степени были вытеснены предикатами get*entry и set*entry для бинарных доменов. Резюме Visual Prolog включает несколько предикатов, рассмотренных в этой главе, которые осуществляют доступ к ОС, выполняют побитовые логические операции и операции сдвига, обеспечивают поддержку низкого уровня для манипулирования памятью: □ Предикаты, осуществляющие доступ к ОС: • system — возможность выполнения внешней программы; • time — установка или чтение показаний встроенных часов; • date — доступ к встроенному календарю; • envsimbol — доступ к таблице переменных среды ОС; • comline — чтение аргументов командной строки; • syspath — возврат стартового каталога и имени исполняемого файла; • osversion — возврат номера версии ОС; • diskspace — возврат объема доступного пространства на диске. □ Предикаты, выполняющие побитовые операции: • bitor — логическое "ИЛИ”; • bitand — логическое "И”; • bitand — логическое ”НЕ"; • bitxor — логическое "исключающее ИЛИ”; • bitleft — сдвиг влево; • bitright — сдвиг вправо. □ Предикаты, обеспечивающие поддержку прямого доступа к памяти: • ptr_dword — возвращает адрес аргумента или помещает аргумент в заданное место памяти; • membyte ~ выбирает или помещает однобайтную величину; • memword — выбирает или помещает двухбайтную величину; • memword — выбирает или помещает четырехбайтную величину.
ГЛАВА 23 Систематический обзор языка Visual Prolog В этой главе мы рассмотрим элементы языка Visual Prolog и его компилятора. Обсу- дим некоторые основные элементы языка: имена, разделы программы, директивы компилятора и управление памятью. После этого дадим введение в управление модулями в Visual Prolog и покажем, как разбить программу на несколько модулей, которые можно компилировать раздельно, а затем связывать компоновщиком. Эта глава предназначена для программистов, которые уже немного поработали с Visual Prolog и освоили материал первых глав части IV. Имена В Прологе, напомним, имена используются для обозначения символических кон- стант, доменов, предикатов и переменных. Имя состоит из буквы (или знака под- черкивания), за которой следует любая комбинация нуля или более букв, цифр или знаков подчеркивания. На имя накладываются два важных ограничения: □ имена символических констант должны начинаться со строчной буквы; □ имена переменных должны начинаться с прописной буквы или знака подчерки- вания. За исключением этих ограничений, вы можете использовать прописные и строчные буквы в программе как угодно. Например, можно сделать имя более читабельным, смешивая большие и маленькие буквы, как в переменной MyLongestVariableNameSoFar или используя знаки подчеркивания: pair_who_might_make_a_happy_couple (henry_viii, ann_boleyn) Компилятор Visual Prolog, кроме как для первой буквы, не делает отличий между прописными и строчными буквами.
Глава 23. Систематический обзор языка Visual Prolog 577 Это означает, что две переменные SourceCode и SOURCECODE одинаковы. Ключевые слова Следующие слова являются зарезервированными и их нельзя использовать как име- на, определенные пользователем: abstract align as and class clauses constants database determ elsedef endclass enddef erroneous facts failure global goal if ifndef implement include language multi nocopy nondeterm object or procedure protected predicates reference single static struct this domains Специально определенные предикаты Следующие предикаты обрабатываются компилятором специальным образом; не переопределяйте эти имена в вашей программе: assert asserta assertz bound chain_inserta chain_insertafter chain insertz chain terms consult db_btrees db_chains fail findall format free msgrecv msgsend not readterm ref_term retract retractall term_replace term_str trap write writef save term_bin Разделы программы Программа на языке Visual Prolog состоит из модулей, каждый из которых содержит несколько разделов. Раздел программы идентифицируется ключевым словом, как показано в табл. 23.1.
578 Часть IV. Программирование на Visual Prolog Таблица 23.1. Содержание разделов программы Раздел Опции компилятора constants domains facts predicates goal clauses class implement abstract class Содержание Опции компилятора, заданные в начале модуля Нуль или более символических констант Нуль или более объявлений доменов Нуль или более объявлений предикатов базы данных Нуль или более объявлений предикатов Цель программы Нуль или более предложений Нуль или более объявлений открытых (и защищенных) преди- катов, фактов и доменов Нуль или более объявлений закрытых предикатов, фактов и доменов. Нуль или более предложений, реализующих откры- тые и закрытые предикаты (и инициализирующих факты) Нуль или более объявлений открытых предикатов и доменов. Не должны иметь реализацию Чтобы создать программу, вы должны в ней указать цель. Программе необходимы, по меньшей мере, разделы predicates и clauses. Большинству программ нужен раздел domains для объявления списков, сложных структур и ваших собственных доменов. При модульном программировании вы можете ставить перед ключевыми словами domains, predicates и facts ключевое слово global, указывая, что последующие объявления имеют глобальную область видимости и объявленные имена действи- тельны во всех программных модулях, которые включают объявления этих глобаль- ных разделов (см. разд. "Модульное программирование” гл. 17). Программа может содержать несколько разделов domains, predicates, facts и clauses, а также несколько объявлений и реализаций классов, при следующих огра- ничениях: □ опции компилятора должны предшествовать всем остальным разделам; □ константы, домены (включая неявно определенные домены, заданные именами классов и разделов фактов) и предикаты должны быть определены до того, как вы их используете (внутри раздела domains можно ссылаться на домены, которые объявляются позже); □ все предикаты с одинаковым именем, но с разными доменами или арностями должны быть объявлены в одном разделе предикатов; □ не разрешается давать одинаковое имя предикатам, объявленным в разделах predicates и facts; □ все предложения, которые описывают предикаты с одинаковым именем и ар- ностью (но с разными доменами), должны идти одно за другим;
Гпава 23. Систематический обзор языка Visual Prolog 579 □ раздел facts может иметь имена, но каждое имя может встречаться только один раз. Поскольку имя, задаваемое по умолчанию для раздела фактов, определенно- го вне класса, есть dbasedom, то может быть только один, не имеющий имени, раздел фактов, и он должен быть глобальным. Инициализирующие предложения для глобальных фактов могут быть реализованы только после раздела goal в главном модуле; □ в программе должна быть задана одна и только одна цель, однако цель может появиться где угодно; □ если скомпилированный файл (проектный модуль) не содержит раздела цели, то компилятор нужно "проинформировать'1 о том, что этот файл — проектный мо- дуль. Это можно сделать опцией компилятора -r<-ProjectName> в командной строке (среда визуальной разработки делает это автоматически) или директивой компилятора project "ProjectName”; □ начиная с версии 5.2, Visual Prolog предоставляет усовершенствованное управле- ние глобальными объявлениями: • глобальные и локальные объявления могут быть заданы в любом порядке (в старых версиях требовалось, чтобы все глобальные объявления помещались перед локальными); • главный проектный модуль (с целью) должен содержать объявления всех гло- бальных доменов, глобальных разделов фактов и абстрактных классов, объяв- ленные во всех проектных подмодулях; • любой другой (не главный) проектный модуль может содержать объявления только тех глобальных доменов, глобальных разделов фактов и классов, кото- рые используются в данном модуле; • если какое-либо глобальное объявление (класса) изменено, то все модули, включающие это объявление, должны быть перекомпилированы. Раздел доменов Раздел domains содержит объявления доменов. При этом используются следующие форматы: my_name = d my_list = elementDom* my_CompDom = f_l(d_ll,d_12,...,d_ln) % Синоним стандартного домена % Списковый домен % Домен составных элементов % с несколькими альтернативами f_m(d_ml,d_m2 f ... fd_mn) my_SingleAltCompDom = struct f_single(dl,d2,...,dn) % Домен составных % элементов с единственной альтернативой рredefdom = namel;name2;...;nameN % Специальные домены типа % db_selector и file my_PredicateDom = determSpec retDom (args) — flow langSpec % Предикатный домен my__Obj PredicateDom = object determSpec retDom (args) — flow langSpec % Объектный предикатный домен
580 Часть IV. Программирование на Visual Prolog Сокращение объявлений доменов Левая часть объявления доменов (кроме специальных предопределенных доменов file и db_selector) может состоять из списка имен: mydom 1, mydom2, . . ., mydomN = . . . Эта особенность позволяет объявлять несколько доменов одновременно. Например: firstname, lastname, address - string Синонимы стандартных доменов my_name = d Такое описание объявляет домен my_name, состоящий из элементов стандартного домена d; он должен быть char, real, ref, string, symbol, binary ИЛИ одним из целочисленных стандартных доменов: byte, sbyte, short, ushort, word, integer, unsigned, dword, long, ulong. Для некоторых целочисленных доменов можно также использовать следующий синтаксис: my_name ~ [signed | unsigned] {byte I word | dword} • ч Это объявление используется для синтаксически похожих объектов, но различаю- щихся семантически. Например, rows и columns могут быть представлены целыми числами и, следовательно, перепутаны. Вы можете избежать этого, объявив два раз- личных домена типа unsigned: rows, columns = unsigned Такое объявление различных доменов позволяет Visual Prolog контролировать их, чтобы убедиться, что, например, rows и columns никогда не будут случайно пере- путаны, хотя оба домена могут быть взаимозаменяемо смешаны с целыми числами, и вы можете использовать знак равенства для преобразования между Rows и Columns. Общий синтаксис для синонимов стандартных доменов: my_dom Ь my_doml] = [reference] <basicdom> Здесь мы используем квадратные скобки для указания необязательных элементов. Ключевое слово reference используется для объявления ссылочных доменов (см. разд. "Объявления ссылочных доменов" данной главы). Списковые домены mylist = elementDom* Это удобная форма записи для объявления спискового домена, mylist является до- меном, состоящим из списков элементов из домена elementDom. Домен elementDom может быть как доменом, определенным пользователем, так и доменом одного из стандартных типов. Звездочка (*) означает “список'*. Например, следующее объявле- ние домена: numberlist = integer* объявляет домен для списков целых чисел, таких как [1, -5, 2, -6].
Гпава 23. Систематический обзор языка Visual Prolog 581 Общий синтаксис для списковых доменов: mylist [t mylist1]= [reference] [align {byte I word Idword}] elementDom* Ключевое слово reference используется для объявления ссылочных доменов (см. разд. "Объявления ссылочных доменов" данной главы). Необязательное ключевое слово align указывает тип выравнивания памяти. Домены составных элементов с множеством альтернатив myCompDom = f_l(d_ll, . .., d_lN); f_2(d_21, d_22, . .., d_2N); ... Чтобы объявить домен, который состоит из составных элементов, необходимо ука- зать функтор и домены для всех подкомпонентов. Например, вы можете объявить домен owners, составленный из следующих эле- ментов: owns(john, book(wuthering_heights, bronte)) с таким объявлением: owners = owns(symbol, book) book = book(symbol,symbol) где owns является функтором составного объекта, a symbol и book — доменами под- компонентов. Правая часть этого типа объявлений доменов может определять различные альтерна- тивы, разделенные точкой с запятой Каждая альтернатива должна содержать единственный функтор и описание доменов для подкомпонентов функтора. Напри- мер, может быть использовано следующее объявление домена, чтобы сказать: “Для некоторых предикатов значением key может быть up, down, left, right или char с символьной величиной1'. key = up; down; left; right; char(char) Возможно включение комментариев после имени домена, например: person= р(string Name, integer Age) Общий синтаксис для составных доменов с множеством альтернатив: my_dom [,my_dom_M]* = [reference] [align {byte I word Idword}] alternative^1 [; alternative^N] * Здесь my dom м — это имена объявленных доменов. Они могут иметь любые допус- тимые в Visual Prolog имена, a alternative_N — это объявления альтернатив доменов в форме: alternative^functor ( [subcomponent—! [, subComponent_2]* ]) . alternative functor — это уникальный функтор, именующий альтернативу. Он мо- жет иметь любое допустимое в Visual Prolog имя. Альтернативные подкомпоненты subcomponent N ДОЛЖНЫ иметь форму sub_Domain_N [sub_Name_N], где sub_Domain _N
582 Часть IV. Программировагаю на Visual Prolog может быть как стандартным, так и определенным пользователем доменом. Нако- нец, sunjamej — это необязательное имя для подкомпонента. Необязательное ключевое слово reference указывает на объявление ссылочных до- менов. Необязательное ключевое слово align указывает тип выравнивания памяти. Здесь мы используем: квадратные скобки указывают необязательные элементы, а фигурные скобки означают, что один из элементов, разделенных символом (t), обязательно должен быть выбран; □ символ * (звездочка) указывает на произвольное количество непосредственно предшествующих элементов (ноль или более элементов); □ символ ; (точка с запятой) читается как "или1*. Домены составных элементов с единственной альтернативой Если перед объявлением составного домена поставить ключевое слово struct, то тем самым вы объявите специальный домен составных элементов с единственной аль- тернативой. my_FunctorlessDom ~ struct [align {byte I wordIdword}J singleFunctor(doml [namel],dom2 [name2]z...,domN [nameN]) Основное отличие такого домена в том, что он может объявлять только одну альтер- нативу с единственным функтором. Следовательно, внутреннему представлению термов с единственной альтернативой не нужно хранить номер функтора, что позво- ляет определять бесфункторные домены. Такие бесфункторные внутренние представ- ления прямо совместимы со структурами языка С, а основной целью использования бесфункторных термов является связь с другими языками. Заметьте, что бесфункторные домены (по техническим причинам) нельзя объявлять ссылочными доменами. Во всех других аспектах термы бесфункторных доменов можно использовать так же, как и другие термы ваших программ. Домены file и deselection file - symbolicFileNamel; symbol icFi lename2;...; symbol icFilenameN Домен file (файловый домен) используется, когда вши необходимо ссылаться на файлы (определенные ранее) по символическим именам. Программа может иметь только один домен такого типа (который должен быть назван file). Символические имена файлов задаются как альтернативы для домена file. Например, объявление: file = sales; salaries представляет два символических имени файлов: sales и salaries.
Глава 23. Систематический обзор языка Visual Prolog 583 Следующие альтернативы предопределены в домене file: keyboard stdin screen stdout stderr Заметьте, что среда визуальной разработки генерирует определения по умолчанию для доменов file и db_selector в файле <ProjectName>.INC. Они такие: global domains db_selector = browseiist_db % Для пакета TreeBrowse file = fileselectori; fileselector2 % Для editor Следовательно, если в программе нужно использовать файлы внешних баз данных, то следует добавить ваши альтернативы к этим определениям. Специально заданные предопределенные домены В табл. 23.2 приводится полный список специальных предопределенных доменов. Таблица 23.2. Специально заданные предопределенные домены Домен dbasedom bt_selector db_selector place accessmode denymode ref reg Описание Неявно сгенерированный домен для термов в неименованной глобальной базе данных Возвращенный селектор бинарного дерева Определенные пользователем селекторы внешней базы данных In_memory; interns; in_file read; readwrite (чтение; чтение и запись ) denynone; denywrite; denyall (нет запрета ни на что; запрет на запись; запрет на все) Домен для чисел-указателей базы данных Символические имена файлов reg(AX,BX,CX,DX,Sl,DI,DS,ES) используется с bios/4 Объявление ссылочных доменов Ссылочный домен может содержать свободные переменные в качестве входных ар- гументов. Чтобы объявить ссылочный домен, начните правую часть объявления до- мена ключевым словом reference. domains reflist = reference refint* refint ~ reference integer term = reference int(refint); symb(refsymb) refsymb - reference symbol
584 Часть IV. Программирование на Visual Prolog При объявлении составного домена как ссылочного, все его поддомены автомати- чески (неявно) переобъявляются как ссылочные домены. Если какой-либо домен, например integer, неявно переобъявлен как ссылочный, то его переменные рассматриваются как ссылки на значения оригинальных доменов. Например, целые переменные будут содержать не целые значения, а указатели на целые. Это может быть опасно для многомодульных проектов. Если глобальный предикат, определенный в другом модуле (например, в библиотеке С), не знает, что целые аргументы являются не обычными целыми значениями, а указателями на них, то вызовы этих предикатов могут сгенерировать ошибки. Следовательно, вам нужно всегда явно объявлять все домены, которые станут ссылочными, в разделе domains, как, например, refint в предыдущем примере, и не использовать основные домены как ссылочные. Объявление предикатных доменов Предикатный домен объявляет тип предикатов. В последующем объявлении пре- дикатов вы можете объявить один или более предикатов принадлежащими такому предикатному домену, и эти предикаты затем могут быть заданы аргументами для других предикатов. Таким образом, предикаты, объявленные как экземпляры преди- катных доменов, могут быть использованы как предикатные значения (см. разд. "Предикатные значения”гл. 17). В объявлениях и реализациях классов эти "обычные" предикатные домены могут быть использованы как объявления статических предикатных значений (см. разд. "Статические предикаты и факты" гл. 18). Для объявлений нестатических (объект- ных) членов-предикатов классов нужно использовать объектные предикатные доме- ны (см. разд. "Объектные предикатные значения" гл. 18). Объявление объектного предикатного домена имеет форму: [global] domains PredDom = object [DetermMode] [ReturnDom] ([ArgList]) [- [FlowPattern]] [Language] Синтаксис "обычных" предикатных доменов почти такой же: PredDom = [DetermMode] [ReturnDom] ([ArgList]) [- [FlowPattern]] [Language] Единственная разница в том, что появилось ключевое слово object, утверждающее объявление объектного предикатного домена (см. разд. "Предикатные домены" гл. 17). PredDom формулирует имя объявленного предикатного домена. DetermMode задает режим детерминизма для предикатов. Должно быть указано одно из ключевых слов: procedure, determ, nondeterm, failure, erroneous ИЛИ multi. ReturnDom задает домен возвращаемого значения (при объявлении функций). ArgList определяет домены аргументов; скобки, окружающие аргументы, надо ставить, даже если ArgList пуст. Спецификация Language "говорит" компилятору, какое использовать соглашение вызовов, и используется только при объявлении доменов для подпрограмм, напи- санных на других языках (подробнее см. гл. 24). Соглашение вызовов по умолчанию pascal.
Глава 23. Систематический обзор языка Visual Prolog 585 Поток параметров определяет, как использовать каждый из аргументов: входные аргументы, напомним, обозначаются буквой i, выходные — о. Вы не можете иметь более одного объявления потока параметров для предикатного домена, но оно должно быть задано, за исключением тех случаев, когда список аргументов пуст или все аргументы — входные. Итак, объявление предикатного домена для детерминированного предикатного зна- чения, принимающего целое на входе и возвращающего целое, будет таким: domains list—process = determ integer (integer) — (i) Этот предикатный домен теперь известен как list_process. В классах домен list_process может быть использован лишь для объявлений статических предикат- ных значений. Если нужны объектные предикатные значения, то объявить соответ- ствующий предикатный домен следует так: objectintint ~ object procedure integer (integer) — (i) Объектный предикатный домен objectintint можно использовать внутри классов для объявлений объектных предикатных значений. Эти предикаты (функции) вызы- ваются с одним входным аргументом из целого домена и возвращают целое значе- ние. Они имеют режим детерминизма procedure. Как и у нестатических членов класса, у них есть скрытые аргументы, указывающие на объекты (экземпляры клас- са), которым принадлежат эти предикатные значения. Раздел предикатов В Visual Prolog разделы, озаглавленные ключевым словом predicates, содержат объ- явления предикатов. Предикат объявляется при помощи его имени и доменов его аргументов: predicates predname(domainl [Namel]f domain2 [Name2], . . .,domainN [NameN]) В этом примере predname представляет собой имя нового предиката, a domainl, domain2, ..., domainN обозначают домены, либо стандартные, либо определенные пользователем. После каждого аргумента домена вы можете задать необязательное мнемоническое имя аргумента NameN. Эти имена аргументов используются для улуч- шения читаемости программы; компилятор же их удаляет на фазе первичной обра- ботки . Для одного предиката допускается несколько объявлений. Например, вы можете объявить, что предикат member работает как с числами, так и с именами, путем зада- ния следующих объявлений: predicates member(name, namelist) member(number, numberlist) В этом примере домены аргументов name, namelist, number и numberlist определены пользователем.
586 Часть IV. Программирование на Visual Prolog Можно объявлять предикаты с различными арностями: hanoi hanoi(integer) % chooses 10 slices as default % moves N slices Если вы даете более чем одно объявление одному имени (с разными доменами или арностями), эти объявления должны идти одно за другим, но в иерархии классов вы можете объявлять перегруженные предикаты (с разными доменами или арностями) и переопределенные предикаты (с одинаковыми доменами и арностями). Режимы детерминизма Вы можете задать тип детерминизма предиката, вставляя перед объявлением преди- ката ключевые слова procedure, determ, failure или erroneous. С другой стороны, можно определить недетерминированный предикат — вставляя перед его объявлени- ем ключевые слова nondeterm или mult у. Если вы объявляете предикат как детерми- нированный, то компилятор выдает предупреждение или ошибку, если найдет неде- терминированные предложения для этого предиката. Режимом детерминизма для предикатов по умолчанию является detenu. Вы можете изменить это значение по умолчанию на nondeterm или procedure в диалоговом окне среды визуальной разра- ботки Compiler Options, выбрав нужный переключатель в группе переключателей Default Predicate Туре. Можно задать для компилятора режим детерминизма по умолчанию, используя опцию компилятора — z{pro|dtm|ndt}. (Здесь pro соответст- вует procedure, dtm — determ, a ndt соответствует nondeterm.) Компилятор неявно принимает этот режим детерминизма по умолчанию для всех предикатов, объявленных без явно указанного режима детерминизма. nondeterm repeat % repeat недетерминированный по замыслу determ menuact(Integer,String) % menuact детерминированный. По умолчанию, компилятор проверяет определенные пользователем детерминиро- ванные предикаты (determ, procedure, failure и erroneous) и выдает предупрежде- ния или ошибки для предложений, которые могут дать в результате недетерминиро- ванные предикаты. Эта проверка особенно важна для генерации эффективного и надежного кода, поэтому ее нельзя отключить в среде визуальной разработки (ее можно отключить только опцией компилятора udtm). По умолчанию компилятор проверяет определенные пользователем предикаты, объ- явленные с ключевыми словами procedure, multi и erroneous, и выдает предупреж- дения или ошибки, если нет гарантии успешного завершения предиката. Для отклю- чения этой проверки вы можете снять флажок Check Type of Predicates в диалоговом окне Compiler Options (ее также можно отключить опцией командной строки upro). Краткое описание режимов детерминизма □ nondeterm Ключевое слово nondeterm определяет недетерминированные предикаты, которые могут совершать откаты назад и генерировать множественные решения. Предика- ты, объявленные с ключевым словом nondeterm, могут быть неуспешны.
Гпава 23. Систематический обзор языка Visual Prolog 587 □ procedure Ключевое слово procedure определяет предикаты, называемые процедурами, ко- торые всегда имеют одно и только одно решение (но возможны ошибки во время исполнения). Процедуры всегда успешны и не порождают точек отката. Боль- шинство из встроенных Visual Prolog предикатов внутренне объявлены как про- цедуры. □ determ Ключевое слово determ определяет детерминированные предикаты, которые мо- гут быть успешными или неуспешными, но не могут порождать точек отката. Та- ким образом, предикат, объявленный с ключевым словом determ, имеет не более одного решения. multi Ключевое слово multi определяет недетерминированные предикаты, которые мо- гут совершать откаты назад и генерировать множественные решения. Предикаты, объявленные с ключевым словом multi, не могут быть неуспешны. □ erroneous Предикат, объявленный с ключевым словом erroneous, всегда успешен и не порождает решения. Его обычно используют для управления ошибками. Visual Prolog предоставляет встроенные erroneous предикаты exit и errorexit. □ failure Предикат, объявленный с ключевым словом failure, не порождает решения, но может завершаться неуспешно. Visual Prolog предоставляет встроенный failure предикат fail. Неуспешные предикаты обычно используются для принуждения поиска с возвратом к ближайшей точке отката. Ниже предикат failurei демон- стрирует различие между предикатами failure и erroneous. failurei(0) errorexit(). failurei(_) :- fail(). % Этот предикат может быть неуспешен Потоки параметров При вызове предиката аргументы, значения которых известны, рассматриваются, напомним, как входные параметры (i), а неизвестные — как выходные параметры (о). Список входных и выходных параметров для данного предиката называют потоком параметров (flow pattern). Начиная с раздела цели, компилятор Visual Prolog анализирует все вызовы каждого определенного пользователем предиката и определяет все возможные потоки пара- метров, с которыми предикат можно использовать. Для каждого потока параметров, с которым вызывается предикат, анализатор потока параметров просматривает пред- ложения предиката, устанавливая переменные, начиная от головы предложения, либо входными, либо выходными, и, тем самым, определяет потоки параметров для вызовов используемых предикатов. Если анализатор узнает, что стандартный или
588 Часть IV. Программирование на Visual Prolog определенный пользователем предикат вызывается с несуществующим потоком па- раметров, он выдает сообщение об ошибке. Для локальных предикатов (т. к. локальный предикат используется только в одном модуле компиляции) компилятор может определить все потоки параметров, с кото- рыми вызывается предикат; следовательно, задавать потоки параметров для локаль- ных предикатов не требуется. Но если вы знаете, что локальный предикат действи- телен только с конкретными потоками параметров, то будет правильно явно задать их при объявлении предиката. Тогда анализатор потока параметров будет "ловить" любые неверные использования предиката. Вы также можете задать отдельный режим детерминизма перед каждым потоком параметров. Например, вы можете следующим образом детализировать объявление предиката plus/З в примере ch!7e02.pro: predicates nondeterm plus(integer, integer, integer) — procedure (i,i,o) procedure (o,i,i) procedure (i,о,i) determ (i,i,i) (o,o,i) (i,o,o) (o,i,o) (0,0,0) Если особый режим детерминизма явно объявлен перед потоком параметров (на- пример, procedure (i,i,o)), тогда он переопределяет "главный'' режим детерминиз- ма, объявленный перед именем предиката. Иначе, поток параметров имеет режим детерминизма, объявленный перед именем предиката (например, (o,i,o) имеет ре- жим nondeterm). Общая форма объявлений локальных предикатов такова: predicates [DeterminismMode] PredicateName [(ArgList)J [- [[FlowDetermMode] FlowPattern] [[,][FlowDetermMode] Flowpattern]*] Все элементы, используемые в этом объявлении, детально описаны в разд. "Глобаль- ные предикаты" гл. 17. Функции Поставив перед объявлением предиката имя домена, вы тем самым объявите функ- цию. Возвращаемое значение функции — это последний аргумент в предложениях в секции clauses. Функция, возвращающая куб своего аргумента, будет выглядеть так: predicates procedure integer cube(integer) А предложение для этой функции будет следующим: clauses cube(In,Out):- Out = In*In*In. Функция может возвращать любой домен.
Глава 23. Систематический обзор языка Visual Prolog 589 Предикатные значения Если вы объявили предикатный домен в разделе доменов, то можно объявить один или более предикатов, принадлежащих этому домену. Синтаксис таков: predicates predl: p_domain pred2: p_domain где predl, pred2 и др. — это предикатные имена, a p domain — это предикатный до- мен, объявленный в разделе доменов. Как и "простые" предикаты, эти предикаты могут быть вызваны с соответствующими аргументами, но их также можно рассмат- ривать и как предикатные значения, т. к. они имеют свойства значений. Их можно: □ передавать в качестве параметров и возвращать из предикатов и функций; □ хранить в фактах; □ держать в переменных; □ сравнивать на идентичность. Объектные предикатные значения Если вы объявили объектный предикатный домен (например, my_objDom) в разделе доменов, можно (в объявлении или реализации класса) объявить один или более нестатических членов-предикатов класса принадлежащими этому объектному преди- катному домену. Синтаксис таков: class с predicates my_obj__predl: my_obj_Dom my_ob j_pred2 : my_ob j_Dom Раздел фактов Раздел facts (устаревшее название — database) объявляет предикаты так же, как это делает раздел predicates. Однако предложения для таких предикатов могут содер- жать только простые факты, они не могут иметь связанного тела. Эти факты могут быть вставлены во время выполнения программы посредством предикатов assert, asserta, assertz или consult во внутреннюю базу фактов, и вы можете их снова удалить посредством предикатов retract или re fractal 1. Предикаты save/1 и save/2 сохраняют содержимое внутренней базы фактов в файле. Предикаты consult/1 и consult/2 читают факты из файла и добавляют их во внутреннюю базу фактов. Можно иметь в программе множество разделов фактов; одни из них могут быть гло- бальными, а другие — локальными. Вы можете давать имена разделам facts, и каж- дое имя должно быть уникальным внутри модуля. Если вы не даете имя разделу facts, объявленному вне класса, компилятор по умолчанию задаст ему имя dbasedom. Возможен только один такой безымянный раздел фактов, и он должен
590 Часть IV, Программирование на Visual Prolog быть объявлен глобальным в многомодульных программах (это принято по умолча- нию для проектов, созданных в среде визуальной разработки). Когда объявлен раз- дел фактов, компилятор объявляет соответствующий домен с таким же именем, как у раздела фактов; это позволяет предикатам обрабатывать факты как термы. Заметьте, что внутри реализации и объявления класса вы можете объявить несколь- ко безымянных разделов фактов; для таких баз данных компилятор генерирует спе- циальные внутренние имена, на которые нельзя ссылаться из программного кода. Общая форма объявлений разделов фактов такова: [global] facts [ — <databasename>] [nocopy] [{ determ | single | nondeterm }] dbPredicate [’(’ [Domain [ArgamentName]]* ')'] Каждый раздел фактов может объявлять любое количество предикатов баз фактов. По своей природе предикаты баз фактов недетерминированные; следовательно, их режим детерминизма по умолчанию — nondeterm. Вы можете перед объявлением предикатов баз фактов ставить следующие ключевые слова: □ determ — если вы знаете, что для этого предиката базы данных может быть толь- ко один факт; □ single ~ если вы знаете, что для этого предиката базы данных всегда будет один и только один факт. Это помогает компилятору производить лучший код, и вы не будете получать неде- терминированные предупреждения для вызовов таких предикатов. Это полезно для флагов, счетчиков и т. п. Ключевое слово посору Обычно, когда предикат базы данных вызван для связывания переменной со стро- кой или составным объектом, данные, на которые ссылаются, копируются из кучи в глобальный стек Visual Prolog, посору объявляет, что данные не будут копировать- ся, а переменные будут ссылаться прямо на данные фактов, хранимых в куче. Это повышает эффективность, но пользоваться этим надо осторожно. Если копия не была сделана, то после удаления факта переменная будет указывать на ’'мусор”. Ключевое слово global Определяет раздел глобальных фактов (см. разд. "Модульное программирование" гл. 17). Заметьте, что техника надежного программирования требует, чтобы вы не использо- вали глобальных фактов. Вместо этого вы можете применять глобальные предикаты, обрабатывающие локальные факты. Пример: facts ~ tables part(name, cost) salesperson(name,sex)
Гпава 23. Систематический обзор языка Visual Prolog 591 predicates write_table_element (tables) clauses write_table_element {part (Name, Cost)) : - writef("\nPart1s Name= % Cost = %",Name,Cost). write_table_element(salesperson(Name,Sex)):- writef("\nSalesperson's Name- % Sex = ,Name,Sex). Раздел предложений Предложение — это либо факт, либо правило, соответствующее одному из объявлен- ных предикатов. Предложения помещаются в разделе clauses. Вообще говоря, пред- ложение состоит либо из факта, либо из заголовка предложения, за которым следует сначала двоеточие с дефисом а затем список вызовов предикатов, разделенных запятыми (,) или точкой с запятой (;). И факты, и правила должны заканчиваться точкой. Факт same_league(ucla, use). состоит из имени предиката same_league и заключенного в скобки списка аргумен- тов (ucla, use). Для совместимости с другими версиями Пролога вы можете использовать ключевые слова: if может ставиться вместо двоеточия с дефисом and — вместо запятой (,), or — вместо точки с запятой (;). Простые константы Простые константы принадлежат одному из стандартных доменов, перечисленных в табл. 23.3. Таблица 23.3. Домены простых констант Домен Описание char Символ ASCII может быть задан с помощью управляющего (escape) символа \, за которым следует код ASCII этого символа. \n, \t, \г обозначают соответственно символы новой строки, табуляции и воз- врата каретки. Любой другой символ, следующий за символом обрат- ный слэш (\), образует этот символ (\\ образует \, а \' образует') integer Положительные и отрицательные числа могут быть представлены це- лочисленными доменами Visual Prolog real Вещественное число принадлежит домену real и находится в преде- лах от 1,7x10"307 до 1,7х1О+308. Вещественные числа записываются в виде знака, мантиссы, десятич- ной точки, дробной части, символа е, знака и показателя экспоненты, все без пробелов. Например, вещественное значение -12345,6789x103 может быть записано как -1.2345б789е+7.
592 Часть IV. Программирование на Visual Prolog Таблица 23.3 (окончание) Домен Описание (прод.) Знак, дробная часть и показатель экспоненты необязательны (если опускаете дробную часть, то десятичную точку тоже нужно убрать). Visual Prolog автоматически преобразует целые числа в вещественные (когда это необходимо) string Строка (любая последовательность символов между парой двойных кавычек) принадлежит домену string. Строка может содержать сим- волы, образованные escape-последовательностями (как для char) symbol Символическая константа (имя, начинающееся со строчной буквы) принадлежит домену типа symbol. Строки тоже могут восприниматься как элементы типа symbol, но элементы symbol хранятся во внутренней таблице для более быстрого сопоставления. Таблица символов занимает некоторую область памя- ти. Если строковые элементы часто сравниваются, то их лучше разме- щать в ней binary Бинарная константа принадлежит домену binary. Она записывается в виде списка целых значений, каждое из которых меньше или равно 255, разделенных запятыми. Список заключен в квадратные скобки, перед которыми стоит знак доллара: $ [1,Oxff, ' а ’ ] Предикатное значение Объектное предикатное значение Предикатное значение — это имя предиката, предварительно объяв- ленного принадлежащим предикатному домену. Оно записывается просто как имя предиката без списка аргументов или скобок Объектное предикатное значение — это имя предиката, объявленного в классе принадлежащим объектному предикатному домену. Оно запи- сывается просто как имя предиката без списка аргументов или скобок Целочисленные домены Visual Prolog приведены в табл. 23.4. Таблица 23.4. Целочисленные стандартные домены Домен Описание Реализация short ushort long ulong integer Короткое, знаковое, количественное Короткое, беззнаковое, количественное Длинное, знаковое, количественное Длинное, беззнаковое, количественное Знаковое, количественное, имеет платформо-зависимый размер Все платформы 16 бит (-32 768—32 767) Все платформы 16 бит (0—65 535) Все платформы 32 бит (-2 147 483 648-2 147 483 647) Все платформы 32 бит (0—4 294 967 295) Платформы 16 бит (-32 768—32 767) Платформы 32 бит (-2 147 483 648-2 147 483 647)
Гпава 23. Систематический обзор языка Visual Prolog 593 Таблица 23,4 (окончание) Домен Описание Реализация unsigned Беззнаковое, количественное, Платформы 16 бит (0—65 535) имеет платформо-зависимый Платформы 32 бит (0-4 294 967 295) размер byte Все платформы 8 бит (0—255) word Все платформы 16 бит (0—65 535) dword Все платформы 32 бит (0—4 294 967 295) Целому значению может предшествовать Ох или Оо, указывая на шестнадцатеричный и восьмеричный синтаксис соответственно. Термы Терм — это, строго говоря, любой элемент Пролога. Это и объект стандартного до- мена, и список, и переменная, и составной терм, и функтор, за которым следует список термов (необязательные аргументы), заключенных в круглые скобки и разде- ленных запятыми. При использовании доменов, автоматически сгенерированных для имен разделов фактов, факты тоже можно обрабатывать как термы. Переменные Переменные — это имена, начинающиеся с прописной буквы или символа подчерки- вания (для представления анонимной переменной используется одиночный символ подчеркивания). Анонимная переменная используется, когда ее значение не пред- ставляет интереса. Переменная называется свободной, если она еще не ассоциирова- на с термом, а связанной или установленной, когда она связана с термом. Visual Prolog предлагает опцию, при выборе которой он выдаст предупреждение, если обнаружит, что переменная была использована в предложении лишь единожды. Это предупреждение не появится, если имя переменной начинается со знака под- черкивания. Если имя переменной начинается со знака подчеркивания, например Win, — это нормальная переменная, которую, в отличие от анонимной, можно ис- пользовать для передачи значений из одного вызова другому. Составные объекты Составной терм — это одиночный объект, который состоит из совокупности других термов (называемых подкомпонентами) и описывающего имени (функтора). Под- компоненты заключаются в скобки и разделяются запятыми. Функтор записывается непосредственно перед левой скобкой. Например, следующий составной терм со- стоит из функтора author и трех подкомпонентов: author(emily, bronte, 1818) Составной объект принадлежит домену, определенному пользователем. Объявление домена, соответствующее составному объекту author, может выглядеть следующим образом:
594 Часть IV. Программирование на Visual Prolog domains author_dom = author(firstname, lastname, year_of__birth) firstname, lastname - symbol year_of_birth - integer Однофункторные составные термы Если перед объявлением составного домена поставить директиву struct, то вы объ- явите составной домен с единственной альтернативой: domains author__dom = struct author (firstname, last name, year_of_birth) У составного домена с единственной альтернативой не может быть других альтерна- тив. Поэтому внутреннее представление терма составного домена с единственной альтернативой может не хранить функтор. Повторим, что такие домены можно на- звать бесфункторнымы доменами. Бесфункторные термы можно использовать так же, как и остальные термы в ваших исходных файлах, но их основной целью является совместимость со структурами языка С. Списки — специальный вид составных объектов Списки являютсй обычной структурой данных в Visual Prolog; список обычно явля- ется формой составного объекта. Синтаксически список состоит из последователь- ности аргументов, заключенных в квадратные скобки и разделенных запятыми. Спи- сок целых чисел может выглядеть следующим образом: [1, 2, 3, 9, -3, 2] Такой список принадлежит определенному пользователем домену, например: domains ilist = integer* Если элементы списка являются объектами смешанного типа (например, список содержит как целые числа, так и символы), то вы должны указать это в соответст- вующем объявлении доменов. Например, следующее объявление: domains element = c(char); i(integer) list = element* позволит списки следующего вида: (i(12), i(34), i(-567), c('x’), c(’y'), c(’z’), i(987)] Выравнивание памяти Поместив перед объявлением составного домена или списка описание выравнива- ния, вы можете переопределить выравнивание по умолчанию. Синтаксис таков: domains dom - align { byte j word | dword } domdecl
Гпава 23. Систематический обзор языка Visual Prolog 595 где domdecl — это обычное объявление домена: domains element = align byte c(char); i(integer) list = align dword element* Внутреннее представление elements будет выравнено по байтам, a list — по двой- ным словам. Если вы хотите переопределить выравнивание по умолчанию для бесфункторных доменов, то директива struct должна предшествовать директиве align. domains bbdom = struct align byte blm(char,integer). Первоначальной целью переопределения выравнивания является совместимость со- ставных объектов с внешним кодом, использующим выравнивание, отличное от то- го, что определено по умолчанию на вашей платформе. Если несколько программ разделяют внешнюю базу данных, то вовлеченные домены должны использовать одно и то же выравнивание. Раздел цели Раздел цели объявляется с ключевым словом goal, goal — это особый вариант пред- ложения, иногда называемый целевым предложением. По существу, раздел цели аналогичен телу правила. Раздел цели просто содержит список подцелей, разделенных запятыми (логическое "И") или точками с запятой (логическое "ИЛИ"). Но, в отличие от правила, раздел цели не содержит двоеточия с тире (: -). При запуске программы Visual Prolog автоматически вызывает цель и про- грамма выполняется, пытаясь разрешить тело целевого правила. Если все подцели раздела цели выполняются успешно, то программа завершается успешно. Иначе го- ворят, что программа завершилась неуспешно. Например: goal write("Hello world"), readchar (_). Раздел констант Вы можете определять и использовать константы в программах на Visual Prolog. Объявление раздела констант обозначается ключевым словом constants, за которым следуют сами объявления с использованием следующего синтаксиса: <Id> ~ <definition> Каждое определение <definition> заканчивается символом новой строки, поэтому на одной строке может быть только одно объявление константы. На константы, объявленные таким образом, в программе можно ссылаться. Рассмотрим следующий фрагмент программы: constants blue = 1
596 Часть IV. Программирование на Visual Prolog green = 2 red = 4 grayfill = [Oxaa, 0x55, Oxaa, 0x55, Oxaa, 0x55, Oxaa, 0x55 ] language = English project—module - true b Перед тем как компилировать программу, Visual Prolog заменит каждую константу на актуальное значение, которое ей соответствует. Например: « • • menU—Colors (red, green, blue), my_fill_pattern(grayfill) , text_convert(prolog, language), status(project_module), « « « будет обработано компилятором так: menu_colors(4, 2, 1), my_fill_pattern([Oxaa, 0x55, Oxaa, 0x55, Oxaa, 0x55, Oxaa, 0x55]), text_convert(prdlog, english), status(true), На использование символических констант накладываются некоторые ограничения. □ Определение константы не может ссылаться само на себя. Например: list - [1, 2 I list]. % Не дозволено сгенерирует сообщение об ошибке Recursion in constant definition (рекурсия в определении констант). В объявлениях констант система не различает пропис- ные и строчные буквы. Следовательно, когда идентификатор константы исполь- зуется в разделе предложений, первая буква должна быть строчной, чтобы избе- жать путаницы с переменными. Поэтому правильной конструкцией будет такая: constants Two == 2 goal A=two, write (A) . □ В программе может быть несколько разделов объявления констант, но каждая константа должна быть объявлена до использования. □ Идентификаторы констант глобальны для всего модуля и могут быть объявлены только один раз. Многократное объявление одного и того же идентификатора приведет к сообщению об ошибке. Вы можете использовать константы, чтобы переопределить имена доменов и предикатов, кроме специально определенных предикатов (см. разд. "Специально определенные предикаты" данной главы).
Гпава 23. Систематический обзор языка Visual Prolog 597 Предопределенные константы В зависимости от выбранной целевой платформы, одна или более констант, описан- ных в табл. 23.5, могут быть предопределены. Таблица 23.5. Предопределенные константы Константа os_dos os_os2 os_nt os unix ws_win wsjpm dosx28 6 Platform_16bit Platform_32bit use_coff_obj format use_elf__obj format use_omf__obj format Целевые платформы, при выборе которых константа определена DOS, Phar Lap и 16-битная Windows 32- или 16-битная OS/2 32-битная MS Windows SCO UNIX и Linux 32- или 16-разрядная MS Windows Presentation Manager Phar Lap286 16-битные платформы 32-битные платформы COFF object format: 32-битный Visual C++ ELF object format используется на Linux OMF object format Если DOS выбрана целевой платформой, то будет предопределена константа os dos, при выборе 16-битного MS Windows — os_dos и ws_win. Эти предопределенные константы дают возможность контролировать платформо- зависимую условную компиляцию с директивами компилятора ifdef/ifndef. Условная компиляция Условная компиляция применяется, если вам необходимо сгенерировать различные версии одной и той же программы; например, одну версию, использующую графику, и другую, использующую только текстовый вид. Синтаксис директив условной ком- пиляции таков: [ifdef I ifndef] <constantID> « * * elsedef enddef Здесь <constantiD> представляет собой идентификатор константы, объявленной в разделе констант. Значение константы роли не играет, важно только ее присутст-
598 Часть IV. Программирование на Visual Prolog вие. Директива if def завершается успешно, если константа определена, в то время как директива ifndef завершается успешно, если константа не определена. Часть elsedef является необязательной. Следующая программа показывает типичное ис- пользование директивы условной компиляции. constants restricted = 1 ifdef restricted % если restricted определено, использовать это savebase (_) : - write("\nBase cannot be saved in demo version"), readchar(_). elsedef % иначе, использовать это savebase(Name) write("\nSaving ",Name), save(Name). e ndde f Включение файлов в программу Используйте директивы include в программах или модулях, чтобы включить содер- жимое другого файла в вашу программу во время компиляции. Синтаксис: include "OSFileName" OSFileName может включать путь к файлу, но необходимо помнить, что символ \ (обратный слэш) используется в Visual Prolog как управляющий символ. Имея это в виду, вы должны ставить два символа \\ в случаях, когда символ \ используется внутри исходного текста: include "\\vip\\include\\error.con" В диалоговом окне Options | Project | Directories вы можете задавать один или более путей, разделенных точками с запятой для указания того, где системе следует искать включаемые файлы (здесь, конечно, требуется только один знак \). Если вы не ука- зываете абсолютный путь в OSFileName, то компилятор попытается подсоединить каждый из путей, заданных в поле Include Directories диалогового окна Options | Project | Directories, к имени включенного файла, чтобы определить его местонахож- дение. Использовать включение файлов вы можете только на границах разделов програм- мы, поэтому include может появиться только там, где разрешается одно из ключе- вых слов: constants, domains, predicates, goal, facts, clauses, class ИЛИ implement. Включаемый файл сам может содержать директивы include. Однако включенные файлы не должны использоваться рекурсивно таким образом, что один файл включается во время компиляции более одного раза, ч Включенные файлы могут содержать объявления цели, доменов и предикатов при известных ограничениях на структуру программы (см. разд. "Разделы программы” дан- ной главы).
Гпава 23, Систематический обзор языка Visual Prolog 599 Модули и глобальные программные конструкции Реальные программы Visual Prolog всегда состоят из нескольких модулей. В этом разделе мы рассмотрим основные принципы разработки многомодульных программ. Модули компиляции Программист пишет весь код в исходных файлах. Исходные файлы передаются ком- пилятору в виде "порций", называемых модулями компиляции. Каждый модуль ком- пиляции отдельно компилируется в промежуточный файл, именуемый объектным файлом. Эти промежуточные файлы связываются вместе (возможно, с другими фай- лами) в редакторе компоновки PDC (PDC Link Editor) или другими компоновщика- ми для создания целевых файлов. Целевые файлы могут быть либо выполнимыми файлами, либо библиотеками динамической компоновки (DLL, Dynamic Link Library). До сих пор в этой главе мы преимущественно говорили о программах, имеющих один модуль компиляции. Но одномодульные программы могут состоять из не- скольких исходных файлов, которые включаются (директивой include) во время компиляции в "корневой" исходный файл модуля. Если ваша программа состоит из единственного модуля, то нужны только локальные объявления (которые уже обсуждались в этой главе). Поскольку все пользователь- ские имена определены в этом модуле, компилятор во время компиляции модуля их "видит". Область видимости локальных имен — модуль, в котором они определены. Однако реальные программы редко состоят из одного модуля. На это есть несколько причин: ограничения на количество доменов и предикатов, которые могут быть объ- явлены в модуле, необходимость в использовании инструментов, поставляемых в отдельных модулях, и др. Обратите внимание, что любая VPI-программа (т. е. про- грамма, использующая Visual Programming Interface, см. гл. 26) состоит как минимум из двух модулей. Имена с глобальной областью видимости Поскольку областью видимости локальных имен является модуль, в котором они объявлены, то для взаимодействия между двумя модулями нужны какие-то "широ- комасштабные" элементы, к которым можно получать доступ из обоих модулей. В Visual Prolog такими именами являются: □ глобальные домены и предикаты, объявленные в разделах global domains или global predicates (а также global facts); □ открытые домены и предикаты (а также факты), объявленные в объявлениях классов. Объявления глобальных доменов и предикатов описаны в разд. "Модульное програм- мирование" гл. 17. С точки зрения межмодульного взаимодействия, открытые домены и предикаты, объявленные в классах, ведут себя так же, как и глобальные (конечно, элементы, объявленные в классах, нужно задавать с именами классов или объектов, которым они принадлежат).
600 Часть IV. Программирование на Visual Prolog Основные правила: 1. Модуль может использовать все имена, объявленные в области его видимости. Это означает, что: • модуль может использовать все глобальные предикаты и домены (а также факты), чьи объявления включены в модуль; • модуль может использовать все открытые предикаты и домены (а также фак- ты), которые объявлены в классах, чьи объявления включены в модуль. Мо- дуль также может использовать все домены, соответствующие именам этих классов. 2. Главный модуль проекта (с целью) должен содержать объявления всех глобаль- ных доменов, глобальных фактов и абстрактных классов, объявленных во всех подмодулях проекта. 3. Любой другой проектный модуль может содержать объявления только тех гло- бальных доменов, глобальных фактов, ’’обычных" и абстрактных классов, которые используются в этом модуле. 4. Если какое-то глобальное объявление или объявление класса изменяется, то все модули, включающие это объявление, должны быть перекомпилированы. Структура многомодульных программ Рассмотрим "стили" включений файлов многомодульных программ, которые удовле- творяют описанным выше требованиям. Включение всех глобальных объявлений в каждый модуль Сначала давайте рассмотрим самый простой для использования "стиль" несложного программирования, моделирования и т. д., хорошо подходящий для новичков. Это установленный по умолчанию механизм структурирования среды визуальной разра- ботки. Согласно ему, каждый модуль включает файл <ProjectName>.INC, который, в свою очередь, включает файлы <ModuleName>.DOM и <ModuleName>.PRE для каждого модуля. В файле DOM нужно объявить глобальные домены (и факты), представленные в модуле. В файле PRE нужно объявить глобальные предикаты и поместить объявления классов. Поскольку каждый модуль включает файл INC (ко- торый, в свою очередь, включает файлы DOM и PRE), у вас есть доступ к любому глобальному домену и предикату в каждом модуле. Следуя этому механизму, для создания нового модуля вам просто нужно в диалоговом окне File Inclusion for Module среды визуальной разработки: 1. Выбрать флажок Create <ModuleName>.DOM для каждого исходного модуля, который может представлять новые глобальные домены. 2. Выбрать флажок Include <ModuleName>.DOM для задания того, что утверждение include для этого файла должно быть сгенерировано в файле <ProjectName>.INC. 3. Выбрать флажок Create <ModuleNameXPRE для каждого исходного модуля, ко- торый может представлять новые глобальные предикаты или объявлять новые классы.
Гпава 23. Систематический обзор языка Visual Prolog 601 4. Выбрать флажок Include <ModuleName>.PRE для задания того, что утверждение include для этого файла должно быть сгенерировано в файле <ProjectName>.INC. Это, повторим, хорошо подходит для "простого” программирования. С точки зрения правильной обработки глобальных доменов, этот механизм доста- точно успешно работал в предыдущих версиях Visual Prolog (до версии 5.2), по- скольку они требовали, чтобы все глобальные домены были объявлены во всех модулях (и даже в одинаковом порядке). Имена классов, в некотором роде, рассмат- риваются как глобальные домены, и, до появления версии Visual Prolog 5.2, объяв- ления классов тоже должны были быть включены в каждый модуль. Обратите вни- мание, что все, что мы говорим здесь о глобальных доменах, также относится и к доменам, соответствующим именам разделов глобальных фактов. Локальное включение глобальных объявлений Недостатки выше описанного стиля проявляются при создании больших программ: □ если вы изменяете какое-то глобальное объявление или объявление класса, то необходимо перекомпилировать все модули проекта; □ "мешают" ограничения на количество предикатов и доменов, которые можно ис- пользовать в модуле; □ при создании больших программ (в особенности при участии в проекте группы программистов) наличие доступа "ко всему" и "отовсюду” может привести к "ма- каронным” программам, содержащим ссылки из каждой части программы во все другие ее части. Следовательно, необходимым делом является структурирование больших программ. При их создании вам придется перемещать включения PRE- и DOM-файлов из INC-файла только в те модули (файлы PRO), которые фактически используют соот- ветствующие файлы PRE и DOM. _____Замечание J В текущей версии также рекомендуется помещать объявления классов в файлы <ModuleName>.PH отдельно от объявлений глобальных предикатов. Преимущества перемещения включений файлов PRE, DOM и PH из файла INC в использующие их файлы PRO таковы: □ если вы изменяете глобальное объявление предиката или домена, или объявление класса, необходимо перекомпилировать только те модули проекта, которые включают измененные объявления. Это существенно уменьшает время переком- пиляции; □ поскольку общее количество предикатов и доменов, которые можно объявить в модуле, ограничено, то большее количество глобальных предикатов и доменов может быть использовано в проекте и большее количество локальных предикатов и доменов может быть объявлено в каждом модуле; □ если модуль не включает конкретный PRE-, DOM- или PH-файл, то вы не мо- жете вызывать предикаты или использовать домены, объявленные в этом файле. Таким образом, легче поддерживать надежную структуру связи между модулями
602 Часть IV. Программирование на Visual Prolog (как следствие, уменьшается количество ’’макаронного" кода). Снижается вероят- ность того, что новый программист проекта случайно испортит структуру про- граммы. С другой стороны, новички могут изучить структуру программы, иссле- дуя структуру включений. Теперь, когда главный модуль стал единственным модулем, в который надо вклю- чать объявления всех глобальных доменов и абстрактных классов, можно сосредото- читься на том, как сделать его небольшим, чтобы перекомпиляция была быстрой. Простым способом создания такого главного модуля является замена цели в ее пер- воначальном месте на предложение нового глобального предиката (назовем его run). Затем создадим новый модуль, содержащий все включения, необходимые главному модулю, и цель: goal run () . Объявление глобального предиката run должно, очевидно, быть видимым как в ста- ром главном модуле, так и в новом. Правила видимости для членов классов Подробное описание синтаксиса классов и объектов было дано в гл. 18. Здесь отме- тим правила уточнения (квалификации) и областей видимости для имен, объявлен- ных в классах. П Любой элемент класса (домен, предикат, факт), объявленный в реализации клас- са, является локальным для данной реализации (внутри которой этот элемент используется). П Любой открытый элемент класса (домен, предикат, факт), объявленный в декла- рации класса, может быть использован непосредственно во всех классах, которые наследуют (транзитивно) от этого класса (включая сам класс). Домены, объяв- ленные в декларации класса, можно использовать как в объявлении, так и в реа- лизации этого класса. П В классах, которые не наследуют (транзитивно) от класса, объявляющего домен (и вне классов), домен и все функторы должны быть уточнены (квалифицирова- ны) именем объявляющего класса. Уточнение должно содержать класс, который действительно объявляет домен, но не класс, который просто наследует от этого класса. Другими словами, производные классы лишь наследуют способность ис- пользовать домен. П Статические члены класса принадлежат классу, а не индивидуальным его объек- там. Вне реализации классов, которые наследуют (транзитивно) открытые стати- ческие члены класса, их нужно указывать с именем класса, используя следующий синтаксис: class_name :: static_class_member[(arguments)] Открытые статические члены класса можно использовать в любом месте, где объявление класса видимо (в любом модуле, включающем это объявление клас- са) даже без создания какого-либо объекта этого класса. Конечно, уточнение со ссылкой на объект также возможно.
Глава 23. Систематический обзор языка Visual Prolog 603 □ Напротив, нестатические члены классов принадлежат экземплярам (объектам). Следовательно, для того чтобы использовать открытый нестатический член клас- са вне разделов реализации классов, которые его наследуют (транзитивно), необ- ходимо создать объект, предоставляющий этот член, а при вызове этого нестати- ческого члена нужно указывать идентификатор объекта, используя следующий синтаксис: object : non_static_class_member[(arguments)] □ В Visual Prolog все открытые предикаты (кроме new и delete) являются виртуаль- ными. Виртуальные предикаты позволяют производным классам предоставлять разные версии предикатов родительских классов. Вы можете объявить предикат в родительском классе, а затем переопределить его в любом производном классе. Общий синтаксис этого такой: [ObjectVariable: ] [name_of__class: : ] virtual_jpred_name [ (arguments) ] Все домены, объявленные в классах, статические. Домен принадлежит классу, а не индивидуальным объектам (но, как и остальные статические элементы, их, конечно, свободно могут использовать объекты). Объявления доменов в классах открывают возможность использования классов как модулей. Если класс объявляет только статические элементы, его можно рас- сматривать как модуль. Статические элементы класса можно, однако, использо- вать как глобальные элементы (не забывая о необходимости уточнять их именем класса). Преимущество создания модулей состоит в том, что такой модуль имеет отдельное пространство имен (как результат уточнения именем класса). Это оз- начает, что вы можете выбирать имена в модуле более свободно. Другое преиму- щество, — классы не нужно включать в главный модуль, даже если они содержат открытые домены. Опции компилятора для многомодульных проектов Для управления многомодульными программами компилятор Visual Prolog использу- ет концепцию проектов. Она служит для выполнения следующих двух задач: □ Для определения имени SYM-файла. Visual Prolog хранит термы symbol домена в так называемой символьной таблице, которая находится в специальном (объектного формата) SYM-файле. По умолча- нию, расширение такого файла — SYM. Эта символьная таблица разделяется всеми модулями. Поэтому компилятор должен собрать в одном SYM-файле все термы symbol, используемые во всех модулях. Следовательно, при компиляции каждого проектного модуля компилятор должен знать имя общего SYM-файла. □ Для оповещения компилятора о том, что компилируемые модули являются час- тями проекта, и поэтому не обязаны содержать раздел цели. Программы Visual Prolog должны содержать внутреннюю цель. Следовательно, по умолчанию компилятор требует, чтобы каждый компилируемый модуль имел раздел цели, но в проектах только главный модуль имеет цель.
604 Часть IV. Программирование на Visual Prolog Это можно сделать в командной строке опцией компилятора -г [ProjectName]. На- пример, пусть компилятор во время компиляции файла <ModuleName>.PRO полу- чает опцию: -rProjectName Эта опция оповещает компилятор о том, что: □ компилируемый файл <ModuleName>.PRO является модулем проекта Project Name и не должен содержать раздел цели; П имя SYM-файла — <ProjectName>.SYM. Такое же оповещение можно сделать директивой компилятора project, но опция компилятора -г отменяет ее. Опция компилятора -г командной строки гораздо бо- лее удобна для работы в среде визуальной разработки, и потому директива компиля- тора project сейчас уже практически не используется. Если вы применяете VDE, то эксперт приложений автоматически задает требуемые опции компилятора командной строки. Найти описание опций компилятора командной строки можно в интерактивной справке по Visual Prolog. Директивы компилятора Некоторые свойства компилятора контролируются через директивы компилято- ра. Вы можете ввести одну или более из следующих директив в начале текста про- граммы: check determ code config diagnostics errorlevel heap gstacksize nobreak nowarnings printermenu project Многие из директив компилятора могут быть установлены как в среде разработки программ на Visual Prolog (из меню) посредством опций командной строки, так и в исходном коде. Если директива компилятора стоит в исходном коде, то ее значение перекрывает значение, установленное где-либо в других местах. Заметим, что большинство директив компилятора на данный момент (для Visual Prolog версии 5.2) уже устарели. Директива check_determ Если вы указываете директиву check_determ, компилятор Visual Prolog будет выда- вать предупреждения для каждого недетерминированного предложения программы, которое не было явно объявлено предикатом nondeterm. Эта директива компилятора может перекрывать опцию компилятора командной строки -udtm- или опцию VDE Options | Project | Compiler Options | Check Type of Predicates. Вы можете использовать check_determ, чтобы проследить за отсечениями (cuts). Visual Prolog обладает широкими возможностями, позволяющими решить, является ли предикат детерминированным или недетерминированным, поэтому необязатель-
Гпава 23, Систематический обзор языка Visual Prolog 605 но заполнять программы отсечениями для того, чтобы сохранить место в стеке (что необходимо в других версиях Пролога). Visual Prolog предоставляет эффективное управление детерминизмом, основанное на объявлениях режимов детерминизма для предикатов и фактов. Посредством ключевых слов: multi, nondeterm, determ, procedure, failure или erroneous можно объявить шесть различных режимов де- терминизма для предикатов. Для объявления режима детерминизма предиката вам нужно перед объявлением предиката поставить одно из этих слов. Заметьте, что соб- ственный режим детерминизма может быть объявлен для каждого потока параметров предиката, если перед потоком параметров поставить требуемое ключевое слово. Например: predicates procedure difModes(string, integer) — determ (i, i) (i,o) (o,i) Этот пример объявляет предикат difModes детерминированным для (i,i) потока и процедурой для всех остальных потоков параметров. Вы также можете использовать ключевые слова determ или single, объявляя предикаты баз фактов, которые будут иметь не больше одного или ровно один факт соответственно. Вызов и удаление фактов, объявленных детерминированными, также детерминированные; вы можете использовать объявления determ или single фактов для того, чтобы избежать преду- преждений о недетерминированных классах. Напомним, что режим детерминизма для предикатов по умолчанию — determ. Вы можете изменить его опцией компилятора командной строки на procedure (Value = pro) или nondeterm (Value = ndt). Среда визуальной разработки генерирует эти опции, когда вы выбираете соответствующий флажок для режима предикатов по умолчанию в диалоговом окне Compiler Options. Обратите внимание, что если предикат объявлен с режимами determ, procedure, failure или erroneous, то компилятор всегда проверяет предикат на наличие неде- терминированных предложений. Есть два вида недетерминированных предложений: П если предложение не содержит отсечение и имеется одно или более предложе- ний, которые могут быть сопоставлены при тех же входных аргументах для дан- ного потока параметров; П если предложение вызывает недетерминированный предикат, и за вызовом этого предиката не стоит отсечение. Директива errorlevel Директиву компилятора errorlevel можно задать опцией среды визуальной разра- ботки Options | Project | Compiler Options | Errorlevel. Директива компилятора error level дает вам возможность определять, насколько должны быть детализированы сообще- ния об ошибках в ЕХЕ-файле. Синтаксис таков: errorlevel = d где d — это 0, 1 или 2, представляющие следующие уровни ошибок, описанные в табл. 23.6.
606 Часть IV. Программирование на Visual Prolog Таблица 23.6. Уровни сообщений об ошибках Уровень Описание О Генерирует наиболее эффективный код. Никакая дополнительная информа- ция в коде не дается, отображается только номер ошибки в случае ее появ- ления 1 По умолчанию. Когда встречается ошибка, то место ее появления (имя моду- ля и присоединенного файла) выводится на экран. Место, где была обнару- жена ошибка, также выводится (в количестве байтов от начала файла) 2 Выводятся также некоторые ошибки, сообщений о которых не было на уров- не 1, включая переполнение стека, переполнение памяти и проч. В проекте опция errorlevel в каждом модуле служит для управления таким свойст- вом модуля, как позиция сообщений об ошибках. Если, однако, опция errorlevel в главном модуле больше, чем в подмодулях, Visual Prolog может выдать дезориенти- рующую информацию. Например, если ошибка встречается в модуле, скомпилиро- ванном с уровнем ошибок о, который включен в главный модуль, скомпилирован- ный с уровнем 1 или 2, система не будет способна верно показать место ошибки. Вместо этого она укажет позицию какого-нибудь ранее выполненного кода. Управление памятью в Visual Prolog С "точки зрения" Visual Prolog доступная память делится на следующие области: □ Стек (stack). Стек используется для пересылки параметров и точек возврата в предикатных вызовах. Стек также хранит информацию о точках отката. □ Куча (heap). В области, отведенной под кучу, хранятся все более или менее по- стоянные объекты, такие как факты баз данных, файловые буферы и др. □ Глобальный стек, обычно называемый GStack, это место, где располагаются спи- ски, сложные структуры и строки. GStack освобождается только во время отката при поиске с возвратом. П След (trail). Область, отведенная под след, используется в том случае, если в про- грамме есть ссылочные переменные. След хранит информацию о том, какие пе- ременные нужно освобождать во время поиска с возвратом. След размещен в куче. (^ Замечание j Если вы получаете ошибку переполнения памяти, то нужно либо откорректировать программу, либо увеличить размер соответствующей области памяти. Размер стека На 32-битных Windows-платформах компоновщик должен указать максимальный размер стека в виртуальной памяти приложения (размер стека). Это определит раз-
Глава 23. Систематический обзор языка Visual Prolog 607 мер стека для процесса. Такое резервирование задает диапазон последовательных адресов под стек в полном виртуальном адресном пространстве процессов (размером 2 Гбайт). Не будь этого типа защиты, такие операции, как загрузка DLL, могли бы занять адреса стека и подвергнуть опасности возможность их дальнейшего использо- вания для нужд стека. Заметьте, что зарезервированный размер стека нельзя увели- чить динамически в исполняемых процессах; следовательно, если процесс пытается разместить под стек больше памяти, чем зарезервировано размером стека, то генери- руется ошибка памяти. Размер стека по умолчанию равен 1 Мбайт. Этот размер можно изменить опцией VDE Options | Project | Compiler Options | Miscellaneous | Stack Size. Она эквивалентна опции PDC компоновщика -s[stacksize]. Задание директивы stacksize в файле определений (DEF-файле) перекрывает эту опцию. Такое резервирование адресов в виртуальной памяти процесса не занимает страниц физической памяти, и пространство в файле страниц (swap-файле) не резервируется. Адреса в этом диапазоне просто сохраняются свободными, пока они не понадобятся стеку, тем самым эти адреса защищаются от других запросов на размещение. По- скольку никакие ресурсы во время загрузки программы в память здесь не размеща- ются, эта операция выполняется с максимальной скоростью и совершенно не зави- сит от размера зарезервированного диапазона виртуальных адресов (будь он 500 Мбайт или 4 Кбайт). Также обратите внимание, что это резервирование диапазона виртуальных адресов для стека не гарантирует того, что позднее по этим адресам можно будет разместить физическую память. Во время выполнения программы стек будет динамически рас- ширяться на физическую память, доступную из ОС. Размер глобального стека На 32-битных Windows-платформах Visual Prolog-компилятор должен полностью задать количество адресов, зарезервированных под глобальный стек (GStack) в вир- туальной памяти процесса. Это определит максимально возможный размер глобаль- ного стека для процесса. Размер GStack по умолчанию равен 100 Мбайт. * Его можно изменить опцией VDE Options | Project | Compiler Options ] Miscellaneous | Gstack Size. Эта опция VDE эквивалентна опции компилятора командной строки -k[GStackSize]. Директива компилятора gstacksize (если она задана в тексте глав- ного программного модуля) перекрывает эту опцию. Минимально возможное значе- ние этой опции равно 128 Кбайт. Отметим, что, как и в случае со стеком, такие адреса, зарезервированные в вирту- альной памяти процесса, не размещают страницы физической памяти, и в swap- файле пространство не резервируется. Следовательно, эта операция быстрая, пол- ностью независимая от размера зарезервированного под GStack диапазона виртуаль- ных адресов, но резервирование не гарантирует того, что позднее по этим адресам можно будет разместить физическую память. В 16-битных приложениях GStack может динамически расширяться на доступную память. Обратите внимание, что за один раз в стеке можно разместить не более 64 Кбайт.
608 Часть IV. Программирование на Visual Prolog Размер кучи Куча динамически распределяется на всю доступную память. На 32-битных плат- формах все адреса виртуального адресного пространства процесса, не зарезервиро- ванные под стек и глобальный стек, доступны для кучи. Экономия ресурсов памяти Во время выполнения программы ресурсы памяти могут быть сэкономлены различ- ными методами. □ Чтобы минимизировать использование стека, избегайте ненужного недетерми- низма; применяйте директиву check_determ, чтобы проследить за установкой от- сечений (cuts), и используйте преимущества оптимизации хвостовой рекурсии, записывая ваши рекурсивные предикаты так, чтобы рекурсия была хвостовой. □ Глобальный стек используется для построения строк и структур. Для сохранения места в глобальном стеке пишите программу так, чтобы внешний цикл был цик- лом repeat.. .fail. Предикат fail эффективно освобождает неиспользуемый GStack. Более гибким способом освобождения GStack является вызов предикатов mem_MarkGStack и mem_ReleaseGStack. □ Память для следа редко бывает проблемой в Visual Prolog. Во всех версиях Visual Prolog след динамически размещается в куче и может увеличиваться в размерах, если это необходимо. Однако в 16-битных версиях размер следа имеет ограниче- ние сверху в 64 Кбайт, и первое, что надо сделать, если система "жалуется" на переполнение следа, — это минимизировать использование в программе ссылоч- ных доменов (reference domains). Если вы все-таки хотите их использовать, то не- обходимо сократить число точек возврата, применяя отсечения (используйте check_determ). Комбинация repeat.. .fail также освобождает след. Наконец, пе- ресмотрите вызовы предикатов с тем, чтобы уменьшить число ссылочных пере- менных. Куча используется для вставки фактов в базу данных и размещения в памяти файловых буферов и т. д. Эти области освобождаются автоматически, когда уда- ляются факты, закрываются файлы и т. п.
ГЛАВА 24 Интерфейс с другими языками Visual Prolog является универсальным средством для решения самых разных пользо- вательских задач, но иногда возникают причины для использования других языков программирования. Например, работу с числами легче организовать на языке С, обработку прерываний и программирование низкого уровня лучше делать на ас- семблере. И разумеется, если вы уже разработали программу на другом языке, ре- шающую какой-то аспект проблемы, эта ваша работа не должна оказаться в корзи- не. Visual Prolog позволяет организовать интерфейс ваших программ на Visual Prolog с другими языками, если эти языки производят OBJ-файлы и следуют определен- ным соглашениям. В этой главе приведены примеры взаимодействия процедур языка С и Visual Prolog. Исходные файлы находятся в каталогах \FOREIGN или на прилагаемом CD-диске в каталоге <CDROM>:\RUN\FOREIGN. Чтобы их запустить, вам необходимо иметь соответствующую систему разработки и/или компилятор С и инсталлированные библиотеки. Процесс компиляции и компоновки примеров для различных операционных систем и разных компиляторов С весьма отличаются друг от друга. В подкаталоге FOREIGN вы найдете инструкции для разных платформ и компиляторов (включая VC++, Delphi, Java, Visual Basic). На начальном уровне, используя VDE, вам необязательно знать, как компилировать программы на С, как запускать их линковщик, какие задавать библиотеки и т. д. Все это делается автоматически. Но понимание языка С, способность писать, компили- ровать и компоновать необходимы при разработке сложных многомодульных про- грамм. Использование DLL DLL (Dynamic-Link Library, библиотека динамической компоновки) — это бинар- ный файл, который действует как разделяемая библиотека предикатов и может быть использован одновременно несколькими приложениями. Visual Prolog может сгенерировать и скомпоновать DLL в каталоге \VPI \EXAMPLES\DLL статически или загрузить динамически. Информация о Visual Prolog и DLL на прилагаемом CD-диске (<CDROM>:\RUN\VPI\EXAMPLES\DLL).
610 Часть IV. Программирование на Visual Prolog Вызов других языков из Visual Prolog В этом разделе мы расскажем, что необходимо знать, чтобы вызвать подпрограммы С, Pascal и ассемблера из Visual Prolog. Перед вызовом процедур и функций, написанных на других языках, вам надо объ- явить их как внешние предикаты в Visual Prolog. Рассмотрим правила корректного вызова и передачи последовательностей парамет- ров, а также правила обозначения внешних предикатов с различными вариантами потоков параметров. Объявление внешних предикатов Чтобы ’‘проинформировать’’ систему Visual Prolog о том, что данный глобальный предикат написан на другом языке, вам надо добавить спецификацию языка к объ- явлению глобальных предикатов: global predicates add(integer,integer,integer) — (i, i,o),(i,i,i) language c scanner(string,token) — (i,o) language pascal triple(integer,real) — (i,o) language asm В Visual Prolog вы явно указываете язык, с которым взаимодействуете; это упрощает проблемы, связанные с соглашениями о вызовах, такими как формат записи активи- зации, соглашения об именовании и возврате параметров. Соглашения о вызове и передаче параметров Семейство процессоров 8086 при запуске 16-битных программ дает возможность выбора между вызовами подпрограмм NEAR и FAR. Visual Prolog требует, чтобы все глобальные подпрограммы были FAR. Аналогично и с указателями на объекты дан- ных. Во многих компиляторах с языков для семейства 8086 вы можете выбирать ме- жду 16- и 32-битными указателями. Для организации доступа ко всей памяти Visual Prolog всегда использует 32-битные указатели. Для 32-битных программ NEAR означает использование 32 бит, и все выше изло- женные соглашения не учитываются. Входные параметры Для входных параметров значение помещается непосредственно, а размер параметра зависит от его типа. Выходные параметры Выходной параметр — это 32-битный указатель на ячейку памяти, в которую должно быть помещено значение.
Гпава 24. Интерфейс с другими языками 611 Возвращаемые значения Visual Prolog следует наиболее распространенному соглашению об использовании регистров для получения значений функций. Это не имеет особого значения в большинстве случаев и приводится здесь для полноты информации (табл. 24.1). Таблица 24.1, Регистры для возвращаемых значений Размер операнда Тип программы byte (8 бит) AL word (16 бит) АХ dword (32 бит) DX:AX ЕАХ Указатели имеют размер 32 бит и обрабатываются как двойные слова. Тип програм- мы (Program Туре) определяется операционной системой. Обрабатывать значения с плавающей точкой очень сложно. Они могут возвращаться в регистры, в стек сопроцессора, а соглашение о вызовах Pascal будет часто возвра- щать их через указатели. На данный момент Pascal-функции не могут возвращать значения с плавающей точкой (см. инструкции в каталоге <CDROM>:\RUN\DOC \EXAMPLES\FOREIGN для конкретной платформы). В любом случае, значения с плавающей точкой всегда можно возвращать в аргумен- тах. Однако обратите особое внимание на то, что real в Visual Prolog соответствует double в С (8 байт). На данный момент внешние функции С не могут возвращать структуры С, но они, безусловно, могут быть возвращены в указателях на структуру. Множественные объявления В Visual Prolog один предикат может иметь разные варианты типа, арности и пото- ков параметров. Для каждого типа и потока параметров нужно вызывать отдельную процедуру. Когда вы реализуете предикат, имеющий несколько версий на С, каждая функция С должна иметь имя, соответствующее имени на Visual Prolog. Для таких процедур в Visual Prolog есть соглашение об обозначениях: имя предиката использу- ется как корень, а суффикс _Х добавляется для указания номера варианта, где х — целое число, начиная с 0. Если вариант только один, суффикс не используется. Рассмотрим следующую программу: global predicates add(integer,integer,integer) — (i,i,o) , (i,o,i) , (o,i,i) , (i, i, i) language c square(integer,integer) — (izo) goal add(2,3,X), write("2 + 3 = ",X), nl, add(2,Y,5), write("5 — 2 ~ ",Y), nl,
612 Часть IV. Программирование на Visual Prolog add(Z,3,5), write("5 — 3 = ”,Z), nl, add(2,3,5), write("2 + 3 is 5”), nl, square (5, Sq) , write("5 squared is ’’, Sq) . Загрузочный модуль этой программы должен содержать следующие функции С: □ add_0 — для потока параметров (i, i, о); □ add_l — для потока параметров (i, о, i); □ add_2—для (о,i,i); □ add 3 — для (i,i,i); I □ square. В качестве примера следующий модуль на С, кроме всех шаблонов потока парамет- ров для предиката add, реализует процедуру square: add_0(int х, int у, int *z) % (i,i,o) поток параметров { *z = х + у; } add_l(int х, int *y, int z) % (i,o, i) поток параметров { *y = z — x; } add_2(int *x, int y, int z) % (o,i,i) поток параметров { *x = z - y; } add_3(int x, int y, int z) % (i,i,i) поток параметров { if ( (x + у) '.= z) RUN_Fail(); } square(int i,int *i_sq) { *i__sq = i*i; } Порядок расположения параметров При взаимодействии с подпрограммой на языке С параметры помещаются в стек в обратном порядке и, после возврата, Visual Prolog автоматически устанавливает указатель на стек. При вызове подпрограмм на языках, отличных от С, параметры помещаются в стек в нормальном порядке и вызванная функция сама должна удалить параметры из стека. Префиксный знак подчеркивания На 16-битных платформах компиляторы С перед именем открытой (public) С-функ- ции ставят префикс в виде знака подчеркивания. Следовательно, глобальные преди- каты, объявленные как language С, также будут иметь перед именем префиксный знак подчеркивания, если, повторим, целевая платформа 16-битная. Соглашение об именованиях для 32-битных Windows API-функции Windows 32-бит (Windows 95/98/NT/2000) используют специальное соглашение о вызовах stdcall.
Гпава 24. Интерфейс с другими языками 613 При этом ^stdcall использует следующие правила формирования имен: □ перед именем функции ставится префикс _ (знак подчеркивания); □ после имени функции идет суффикс @nn, где nn — количество байт в списке ар- гументов. Например, функция fName, которая имеет два целых аргумента, должна быть названа _fName@8. Когда компилятор Visual Prolog генерирует объектные файлы для 32-битной плат- формы Windows, задавая language st deal 1 в объявлении глобального предиката (или домена глобальных предикатов), вы можете определить, что предикат должен иметь такое же соглашение о вызовах, как stdcall в языке С. Рекомендуется использовать объявление глобальных предикатов language stdcall для вызова 32-битных Windows API-функций. Важно отметить, что когда задано объявление language stdcall, компилятор пре- доставляет усложненное управление для явного определения имен предикатов в объ- ектном коде as "object_name": □ компилятор проверяет и (если нужно) добавляет/корректирует префиксный знак подчеркивания; □ компилятор добавляет/корректирует (если нужно) суффикс (@) с количеством байт, помещенных в список аргументов. Например, со следующим объявлением глобального предиката для вызова 32-битной Windows API-функции sndPlaySaundA sndPlaySound(string, unsigned) — (i,i) language stdcall as "sndPlaySoundA" компилятор сгенерирует объектное имя _sndPlaySoundA@8. Если предикат имеет несколько вариантов (например, несколько потоков парамет- ров), то используется соглашение об именах для множественных объявлений. pName(integer) — (i), (о) language stdcall компилятор сгенерирует объектное имя __pName_0@4 для первого потока параметров (i) и _joName~l@4 для второго (о). Преобразование имени к верхнему регистру (Pascal) Язык программирования Pascal использует соглашение, что имя преобразуется к верхнему регистру. Так, если используется соглашение о вызовах языка Pascal, то имя во время генерации модуля OBJ будет преобразовано к верхнему регистру. Выравнивание указателя на стек Есть две возможности выравнивания регистра SP. Это можно сделать как вызван- ной, так и вызывающей функцией. Обычно Pascal делает это в вызванной функции, а С — в вызывающей. Соглашения о вызовах см. в табл. 24.2.
614 Часть IV. Программирование на Visual Prolog Таблица 24.2. Соглашения о вызовах Преобразо- Добавление Помещение Вырав- Тип ванне имени префиксного аргументов нивание Соглашение вызова к верхнему знака под- в обратном SP после об именах регистру черкивания порядке возврата pascal X с XXX stdcall XX X syscall X X Использование объявления as В качестве альтернативы этому автоматическому соглашению об обозначениях мож- но использовать ключевое слово as (внешнее имя) в глобальных объявлениях: global predicates scanner(string^token) — (i,o) language c as "_myscan" В результате Visual Prolog будет ссылаться в объектном файле на имя jayscan вместо __scanner. Вы можете ссылаться на имя scanner в исходном тексте на Прологе. Можно использовать опцию as лишь в том случае, если ваш предикат имеет только один вариант потока параметров. Реализация доменов Большинство типов, используемых в языке С, образует подмножество доменов Visual Prolog, а потому каждый имеет свой аналог. Обсудим эквиваленты простых и составных доменов в С. Простые домены Реализация простых доменов Visual Prolog представлена в табл. 24.3. Таблица 24.3. Простые домены Visual Prolog Домен Реализация 16-битная ОС 32-битная ОС char, byte (u) short, word (u) long, dword 1 байт (см. замечание) 2 байта 4 байта 1 байт (см. замечание) 2 байта 4 байта
Глава 24. Интерфейс с другими языками 615 Таблица 24.3 (окончание) Домен Реализация 16-битная ОС 32-битная ОС unsigned, integer real ref 2 байта 8 байт (IEEE-формат) 4 байта 4 байта 8 байт (IEEE-формат) 4 байта Замечание J Домены char и byte, когда помещаются в стек, занимают машинное слоао (2 байта для 16-битных программ, 4 байта для 32-битных программ). Составные домены Все составные домены реализуются как указатели. Домены string и symbol — указатели на массивы символов, завершающихся нулем, домен symbols — указатель на кэшированные строки, хранящиеся в таблице симво- лов в "куче" (heap). Домен binary — указатель на блок памяти, с двойным словом в качестве префикса (на 32-битных платформах) или словом (на 16-битных платформах), указывающим на размер блока. Резервирование памяти для сложных доменов будет детально рассмотрено в сле- дующем разделе. Многоальтернативные составные домены и структуры Определенные пользователем составные домены Visual Prolog представляются как указатели на записи (structs и unions в С). Общий их формат — байт, представ- ляющий функтор func (доменную альтернативу), а затем индивидуальные компонен- ты терма. Они могут различаться в зависимости от домена, с которым вы имеете дело. В любом случае, компоненты, принадлежащие простым доменам, хранятся непосредственно в самой записи терма, в то время как составные компоненты сами хранятся как указатели. Например, в этом фрагменте кода: domains mydom = i(integer); c(char); s(string) функтор func будет равен 1 для первой альтернативы, i (integer); 2 — для второй, с (char); и 3 — для третьей s (string). Подходящий typedef в С для mydom может быть таким: typedef struct { unsigned char func;
616 Часть IV. Программирование на Visual Prolog union { int i; char c; char *s; } u; } MYDOM; Здесь func будет иметь значение 1, 2 или 3, в зависимости от альтернативы домена. Он указывает, какой из компонентов объединения (union) следует использовать. Бесфункторные термы (структуры). Если поместить префикс в виде директивы ком- пилятора struct перед объявлением составного домена, термы, принадлежащие к этому домену, не будут нести с собой функторов: domains rec = struct record(dl, d2, ...} He считая директивы struct, термы, принадлежащие к бесфункторному домену, используются и пишутся в точности так же, как и другие термы программы, кроме того, что в таких доменах не может быть альтернатив. Бесфункторные термы позволяют вам дублировать структуры языка С при взаимо- действии с подпрограммами и библиотеками С. Помимо этого, они сохранят немно- го памяти, если вам не нужны альтернативы в доменах. Списки Списки реализуются, как и обычные составные домены, с полем в конце записи, указывающим на следующую запись. В терминологии С это называется скомпоно- ванными списками. С точки зрения Пролога, списки — это всего лишь удобная за- пись. Вот пример объявления списка строк: domains strlist ~ string* Структуры С, соответствующие этому, идентичны следующему домену: domains strlist = elem(string,strlist}; endoflist() Запись для альтернативы elem будет содержать: □ функтор; □ указатель на строку; □ указатель на следующий элемент в списке. А запись для альтернативы endoflist будет содержать только функтор. Эта совокупность полей отображается в структуре данных С для strlist: struct node { unsigned char functor; // Тип
Гпава 24. Интерфейс с другими языками 617 char *value; struct node *next; // Указатель на строку // Указатель на узел структуры } strlist; Поле functor указывает тип записи списка. Значение равно 1, если это элемент спи- ска, 2 — если конец списка. О памяти В Пролог-программах все управление памятью происходит автоматически, при взаимодействии же с другими языками следует соблюдать особую осторожность. Рассмотрим некоторые аспекты этой специфики. Выравнивание памяти Компиляторы С для 32-битных платформ обычно выравнивают данные по границам двойных слов, в то время как компиляторы для 16-битных платформ выравнивают данные по границам байт. Причина предпочтительного выравнивания по словам или двойным словам — скорость. На 32-битных платформах выравнивание по двойным словам даст на 10—12% более быстрое выполнение, чем выравнивание по байтам. Хотя выравнивание простых переменных не существенно в Прологе, но с целью совместимости со структурами языка С данные, содержащиеся в записях, должны быть одинаково выровнены. Поэтому Visual Prolog дает вам возможность выбора разных способов выравнивания, отличных от тех, что используются по умолчанию на вашей платформе: выравнивание по умолчанию — это выравнивание по двойным словам, если вы используете 32-битную версию Visual Prolog, и выравнивание по байтам — в 16-битной версии. Обратите внимание, что некоторые компиляторы С для 32-битных платформ (например, Visual C++) по умолчанию выравнивают дан- ные по 8 байт. Следовательно, вам нужно произвести коррекцию и выровнять дан- ные по 4 байта с целью совместимости с выравниванием по двойным словам в Visual Prolog. Схема выравнивания может быть выбрана командой Options | Project | Com- piler Options или набором опции -А в командной строке. Директива компилятора align может быть использована для явного указания выравнивания в выбранных доменах, как-то*. domains dom ~ align { byte I word | dword } func (dl, d2, ...) [; fund (. ..}; ... ] Директива align должна появиться до альтернатив домена, и все альтернативы будут иметь заданное выравнивание. Невозможно задать различные выравнивания разным альтернативам. Для бесфункторных термов директива align должна появиться после директивы struct.
618 Часть IV. Программирование на Visual Prolog Замечание J Когда несколько процессов разделяют базу данных, важно, чтобы затрагиваемые домены использовали одинаковое выравнивание. Выравнивать по байтам просто: каждый элемент помещается после предыду- щего. Будучи объявленным dom == struct my_struct(char,short,char,long) (уч- тем, что директива struct объявляет терм бесфункторным), терм my__struct (’р', 29285, 'в1,1702063209) хранится в памяти так, как показано на рис. 24.1. Номер байта: |0|1|2|3|4|5|6|7| I ’Р’ | 29285 I 'В' | 1702063209 | I I I I I Рис. 24.1. Хранение терма в памяти Выравнивать по словам и двойным словам немного труднее. Здесь элементы хранят- ся в памяти так, что доступ к ним не будет пересекать границу слова или двойного слова. Это означает, что за индивидуальными элементами термов могут следовать неиспользуемые байты, количество которых зависит от размера следующего элемен- та. При выравнивании по двойному слову терм, показанный выше, хранился бы так, как представлено на рис. 24.2. Номер байта: |0|1|2|3|4|5|6|7|8|9|10|11[ । i i i i i i i Г |’P'|PAD| 29285 |'B'IPAD|PAD|PAD| 1702063209 I I i I 1 I I I . I 1 Рис. 24.2. Хранение терма в памяти при выравнивании по двойному слову Здесь PAD обозначает неиспользуемые байты, позволяющие значениям, следующим за ними, храниться на правильных границах. Заметьте, что значению 29 285 достаточно быть выровненному по границе слова, потому что оно типа short (16 бит); доступ к нему по границе слова не пересечет никаких нежелательных границ. Распределение памяти Когда вы создаете и возвращаете составные объекты в Visual Prolog, память под эти объекты должна быть правильно размещена в глобальном стеке (Global Stack, GStack). Память автоматически освободится, если вы откатитесь назад в точку, предшествующую ее размещению. Память GStack размещается, используя: void *MEM_AllocGStack(unsigned size);
Гпава 24. Интерфейс с другими языками 619 Обычно используется функция С sizeof для определения, сколько памяти надо раз- мещать. Если взять для примера домен mydom, обсуждаемый ранее, то объявление на Прологе для С-подпрограммы (листинг 24.1), возвращающей терм, принадлежащий этому домену, в аргументе будет следующим: project "mydom” global domains mydom = i(integer); c(char); s(string) global predicates determ make_mydom(mydom) — (o) language C goal make mydom(MD), write (MD), nl. Код на С для mydom и make mydom мог бы выглядеть так же, как в листинге 24.2. ........................................................................................................................................................................................................................................................ •• •* МО... I*?; ’"Ч-f!’ *7!! •!•*• 4' - *Л-йг ЧТ-з’’ - Ji j•" : Л" - ! •’ • "J... r •..'.'г г ' t '? >: истинг24,2. Программа mydorn_cc Лi 1*г*а>*аа*к*О^1*ат«таааааьа*аО**'«'**а 'iooaaa«'aaaaira лааЬма*1* * * •« »!<! м «*i*i « |*|*Г4ввв*В||В||*в*ввв»аВ|М>в^11вв*вввв|вв* iBn<»rBiBB< »» typedef struct { unsigned char func; union { int i; char c; char *s; } u; } MYDOM; void *MEM_AllocGStack(unsigned); char *MEM_SaveStringGStack(char *); void make_jnydom (register MYDOM **md) { *md = MEM_AllocGStack (sizeof (MYDOM)); (*md)->func - 3; (*md)->u.s = MEM_SaveStringGStack("wombat”) ; Так как термы в Прологе управляются через указатели, аргумент make_mydom — это указатель на указатель на терм. Этот пример также использует относящуюся к гло- бальному стеку функцию MEM_SaveStringGStack, которая размещает пространство GStack под строку (основываясь на ее длине), затем копирует строку в размещенное пространство, возвращая на него указатель. В библиотеке Visual Prolog есть еще не- сколько удобных функций: char *MEM_SaveStringHeap(char *String); /* Копирует строку в кучу */
620 Часть IV. Программирование на Visual Prolog unsigned STR_StrLen(char *String); /* Возвращает длину (исключая конечный нулевой байт) строки */ void MEM_MovMem(void *Source,void *Dest,unsigned Len); /* Передвигает Len байт из Source в Dest; они могут перекрываться */ Выделение памяти заранее Многие из функций библиотек языка С требуют от вас определения указателя на структуру, которую затем заполняет подпрограмма С. В этом случае надо использо- вать составной поток параметров для глобальных предикатов. global domains off_t, time_t = long dev t - short stat = struct stat(dev_t,ushort,ushort,short,ushort,ushort, dev_t, off_t, time_t, time_t, time_t) global predicates determ integer stat(string, stat) — (i,stat(o,o, 0,0,0,0,0,0,0,0,0)) language C Когда вы вызываете stat О = stat("/unix",Stat), !, write(Stat) Visual Prolog выделит память под структуру stat перед вызовом. Функция sizeof Visual Prolog имеет функцию sizeof, которая дублирует одноименную функцию С и возвращает размер заданного домена или переменной. Для составного домена с альтернативами функция sizeof вернет размер наибольшей из альтернатив. Получив переменную, sizeof вернет размер соответствующего домена (альтернати- вы), за тем исключением, что для строковых переменных или констант функция sizeof вернет количество байт в строке, включая заключительный нулевой байт. Программа align.pro (листинг 24.3) иллюстрирует выбор выравнивания и использо- вание функции sizeof. domains dom = struct f(char,integer) doml = align word f(integer,integer,long); g(string) refint = reference integer. predicates refint(refint)
Гпава 24. Интерфейс с другими языками 621 clauses refint(_) . goal % Нахождение размера бесфункторного домена А - sizeof(dom), write(”\nSize=",A), % Если есть альтернативы, возвращается наибольшая В = sizeof(doml), write("\nSize=",B), % Нахождение размера единственной альтернативы С = sizeof(doml, д), write("\nSize=",C), % Нахождение размера терма, на который указывает переменная X = f(l,l,l), % Это из doml D - sizeof(X), write("\nSize=", D), % Нахождение размера строки, на которую указывает переменная Y = "hello there", Е = sizeof(Y), write("\nSize=",E), % Нахождение размера ссылочной переменной refint(Z), F = sizeof(Z) , write("\nSize=", F). Загрузите и запустите эту программу. Попытайтесь изменить домены и их аргументы и посмотрите на результаты. Функции malloc и free При написании функций на других языках вам часто необходимо распределять па- мять динамически. Вы уже видели функцию MEM_AllocGStack, но она размещает па- мять в глобальном стеке Пролога, который освобождается автоматически. Долговре- менные размещения следует делать в куче, а т. к. Visual Prolog уже имеет подходя- щие процедуры для выделения памяти, предпочтительнее использовать их. Фактически, в DOS это надо делать обязательно, т. к. "чужие" пакеты по выделению памяти будут выделять ту же самую физическую память, что и Visual Prolog. На дру- гих платформах вы можете использовать функции С malloc и free, но это будет уд- ваивать количество кода и данных, и оба пакета будут держать освобожденную па- мять порознь. Более того, система размещения кучи Visual Prolog гораздо произво- дительнее той, что поставляется с большинством компиляторов С. На всех платформах, кроме UNIX, открытые подпрограммы для malloc и free, со- стоящие из связей с процедурами размещения памяти кучи Visual Prolog, поставля- ются с инициализационными ассемблерными и объектными файлами. Эти файлы находятся в подкаталогах для разных платформ и компиляторов каталога <CDROM>:\RUN\FORE1GN.
622 Часть IV. Программирование на Visual Prolog Замечание J При связывании важно, чтобы соответствующие инициализационные файлы появ- лялись перед библиотеками С, содержащими malloc и free. Примеры обработки списков В этом разделе мы дадим полезный пример (листинг 24.4), который покажет, как преобразовать список в массив и обратно. С-подпрограмма ListToArray принимает список целых чисел, преобразует его в мас- сив, расположенный в глобальном стеке, и возвращает число элементов. Преобразо- вание выполняется в три этапа. □ Список просматривается для того, чтобы определить количество элементов. □ Отводится память для массива. □ Список просматривается снова, его элементы переносятся в массив. Программа С ArrayToList принимает целочисленный массив и его размер как аргу- менты, затем преобразует их в список целых чисел. Эта программа делает только один прогон, строя список по мере просмотра массива. Все это используется в закодированном на С предикате inclist. Если задан список целых чисел, то inclist преобразует входной список в массив, увеличивает число элементов массива на 1, затем преобразует массив обратно в список целых чисел. j Листинг 24.4. Программа lstar_p.pro project "Istar" global domains ilist = integer* global predicates inclist(ilist,ilist) — (i,o) language c goal inclist([1,2,3,4,5,6,7],L), write(L) . Ниже приведена программа C lstar_c.c (листинг 24.5), определяющая две процеду- ры С — ListToArray и ArrayToList — и внешний предикат Visual Prolog inclist. < ’ * ** *! ! ' , - * • F * *”*'**• ‘ “ • % * ' * » ** ** * • ’ * ’ *'**’•’ !* * । Листинг 24.5. Программа lstar__c.c ft* г* та«г J**4*1'«г г г г г «г .«rf’iaa «а h**.*.*b *b b «a ь ttr< M 6 •* #define listfno 1 ^define nilfno 2 typedef unsigned char BYTE; void *MEM_AllocGStack(unsigned); typedef struct ilist ( BYTE Functor;
Гпава 24. Интерфейс с другими языками 623 int Value; struct ilist *Next; } INTLIST; int ListToArray(INTLIST *List,int **ResultArray) { INTLIST *SaveList - List; int *Array, len; register int *ArrP; register int i; /* Счетчик количества элементов в списке */ i = 0; while (List->Functor == listfno) { List = List->Next; } len = i; Array = MEM_AllocGStack(i*sizeof(int)); ArrP = Array; /* Пересылка элементов из списка в массив */ List = SaveList; while (i != 0) { *ArrP++ = List->Value; List - List->Next; i—; *ResultArray == Array; return(len); void ArrayToList(register int *ArrP,register int n, register INTLIST **ListPP) { while (n != 0) { *ListPP = MEM_AllocGStack(sizeof(INTLIST)); (*ListPP)->Functor = listfno; (*ListPP)->Value - *ArrP++; ListPP = &(*ListPP)->Next; n—; } *ListPP = MEM_AllocGStaCk(sizeof((*ListPP)->Functor)); /* Конец списка */ (*ListPP)->Functor = nilfno;
624 Часть IV. Программирование на Visual Prolog void inclist(INTLIST * InList,INTLIST **OutList) { register int *ArrP, i, len; int * Ar ray; len = ListToArray(InList,SArray) ; ArrP = Array; for (i =0; i < len; i++) ++*ArrP++; ArrayToList(Array,len,OutList); Эта программа принадлежит к тому типу программ, в которых выравнивание памяти имеет существенное значение. За этим необходимо следить, если вы собираетесь компилировать ее на нескольких платформах. На первом шаге проверьте, что разме- ры структур, разделяемых С и Прологом, одинаковы; дополнение, применяемое при выравнивании по не байтовым границам, увеличивает размеры. В этом случае будет удобна функция sizeof. Вы можете написать такую функцию на С: unsigned c_ilsize(void) { return(sizeof(INTLIST)) ; } возвращающую размер структуры intlist. Это затем можно использовать в предика- тах Пролога для проверки того, что размеры intlist и ilist одинаковы. global predicates unsigned c_ilsize() language C predicates scheck clauses scheck:- ILSize = sizeof(ilist), ILSize = c_ilsize(), i scheck:- write("ilist element sizes differ\n”), exit(1). Вызов Visual Prolog из других языков Если глобальный предикат объявлен как существующий в другом языке и для него заданы предложения, то такой предикат может быть вызван из другого языка. Он будет иметь доступ к параметрам, коды входа и выхода, включая резервирование регистров, такими же, как в языке, из которого он вызван.
Гпава 24. Интерфейс с другими языками 625 Программа "Hello" Маленький ‘приветствующий мир” проект приведен в листинге 24.6. Листинг 24.6. Программа hello_p.pro • beb 4* 4*аЬ «Ь 4 b b^afl'a« 4 * 4* ,1 b* b« bftibab b b *4 * <«>« Kttbl.b * *aa*a** a*« * «* «««ab**** ..H4kb<i444r4»4»o<b< global predicates char prowinjnsg(string) — (i) language c hello_c — language c clauses prowinjnsg(S,C) :- write(S," (press any key)"), readchar(C). goal prowinjnsg("Hello from PDC Prolog"), hello_c. Глобальный предикат prowin_msg теперь доступен из С и может быть вызван как любая другая функция С (листинг 24.7). char prowinjnsg(char *); void hello c() while (prowinjnsg("Hello from C (press 'С')") != 'C’) Очевидно, что значения могут возвращаться в другие языки. Стандартные предикаты Большинство стандартных предикатов Visual Prolog можно вызывать из С, но их открытые имена и точная функциональность могут быть изменены. Настоятельно рекомендуем написать небольшой набор интерфейсных процедур, если вы хотите вызывать стандартные предикаты Visual Prolog из С. Следующая программа spred_p.pro (листинг 24.8) иллюстрирует связывание с набором DOS Textmode пре- дикатов ввода/вывода Visual Prolog. ................................................................ b Л««4«^«,ь и,,,,,,.Т4,Ь,<И,1 4 4..Ь.^.bbb.b'kbt4b44b...« 4 4 b^f « . « 4 « Л V 4 « Л V. 4 b^ i й - 4 * • 4 4 • • • .. 4 b 4 b 4 b 4 - - - b . ............ project "spred" global predicates myfail language c as "_fail" write_integer(integer) — (i) language c as ,r_write_integer"
626 Часть IV. Программирование на Visual Prolog write_real(real) — (i) language c as "_write_real" write_string(string) — (i) language c as "__write_string” myreadchar(char) — (o) language c as "_readchar" myreadline(string) — (o) language c as "_readline” extprog language c clauses myfail:- fail. write_integer(I):- write(I). write_real(R)write(R). write_string(S):- write(S). myreadchar(CH):- readchar(CH). myreadline(S)readln(S). goal extprog. Они могут быть свободно доступны из С, что иллюстрирует extprog (листинг 24.9). ” 7 ' .............- * [Листинг 24.9. Программа spred_c.с ЙЪ к h< »*'r*m**m*« « <»<<*♦.<»(<»< .ttiitx.irt mmrk,, ь, • void extprog(void) { char dummychar; char *Name; write_string(”\n\nlsn't it easy"); readchar(&dummychar); write__string ("\nEnter your name: " readline(&Name); write_string(”\nYour name is: "); write__string (Name) ; readchar(Sdummychar); } Вызов ассемблерной подпрограммы из Visual Prolog Также можно вызывать ассемблерные подпрограммы из Visual Prolog. Запись акти- вации здесь такая же, как в Pascal, т. е. параметры помещаются слева направо, а вызванная подпрограмма должна освобождать стек самостоятельно. Если у вас есть компилятор С, поддерживающий встроенный ассемблер, то он существенно облег- чит задачу. В любом случае, использование ассемблера больших преимуществ не дает, т. к. аб- солютное большинство операций можно программировать на С, но для полноты изложения мы все-таки приведем ниже небольшой пример в программе csum_p.pro.
Глава24. Интерфейс с другими языками 627 Очевидно, что код отличается для 16- и 32-битных платформ. Предположим, что вы хотите написать подпрограмму, возвращающую 32-битную сумму символов в строке, которая также проверяет, что все символы принадлежат определенному диапазону, скажем, A—Z. Код на Прологе для этого мог бы быть таким (листинг 24.10): ! -• ---^ : ' •- '!: ' > ' -- ”'' - J' j'- . r"" .' : ! •.:T.; . r i "i"-' 4 J t**4Ot**4 r"**^**«***<i *V*<^*<<«*.***><«**< «<+ Чгг»>*Н«««ь« h*i(i T***.**l4**«**'**h ш ь **.«*. *.ттГ*г¥ЪТть г» ь*4 !♦♦ rrrrni* к* т*Ь*ь 4***тТтттттттт*>ттть Г^н «r"rrrrr*rrr«rrrrrr**r*k*r"k*krrrkr‘«rmrrrrrrrrr*.kOO*k*k«H*««*r*r<^ project "csum" global predicates integer sum_verify(char,char,string, ulong) — (i,i,i,o) language asm predicates uc_check(string) clauses uc_check(S) :- 0 = sum_verify (’ A’, ' Z ', S, Sum) , t • f write('"’,S,"\" OK, sum = ",Sum,'\n'). UC—check(S) :- write(',S,"\" fails\n”)• goal uc_check (’’UNIX") , uc_check(’’Windows") . Здесь мы принимаем, что если возвращается 0, то строка правильная. В листинге 24.11 описан подходящий 16-битный ассемблерный код. ; 16-bit version CSUM_A16_TEXT CSUMJkl 6_ТЕХТ _DATA. _DATA CONST CONST _BSS _BSS DGROUP SEGMENT WORD PUBLIC 'CODE' ENDS SEGMENT WORD PUBLIC 'DATA' ENDS SEGMENT WORD PUBLIC ’CONST’ ENDS SEGMENT WORD PUBLIC 'BSS’ ENDS GROUP CONST, _BSS, _DATA ASSUME CS: CSUM—Al6—TEXT, DS: DGROUP, SS: DGROUP aatHt't CSUM_A16—TEXT SEGMENT ASSUME CS: CSUM Al6 TEXT
628 Часть IV. Программирование на Visual Prolog PUBLIC sum_verify push mov lolim equ 16 hilim equ 14 string equ 10 sum equ 6 xor xor les mov mov xor ALIGN 2 loopy: add adc mov inc cmp jb cmp jbe end_check: or jnz les mov mov inc go_home: dec mov pop ret sum_verify sum__verify PROC FAR bp bp,sp UZi f bx, bx di,[bp+string] cl,byte ptr [bp+lolim] ch,byte ptr [bp+hilim] ax, ax bx, ax dx, 0 al,byte ptr di al, cl end_check al, ch loopy es:[di] di,[bp+sum] es:[di],bx es:[di+2] ,dx ax; ax: 0 -> 1 ax sp,bp bp 12 ENDP CSUM_A16_ТЕХТ ENDS END ; Считаем сумму в dx:bx ; Указатель на строку ; Нижняя граница в cl ; Верхняя граница в ch ; Добавить сумму ; ах: 1 ~> 0, or 0 -> -1 Когда вы пишете ассемблерный код, обращайте особое внимание на то, чтобы раз- меры элементов в стеке следовали естественному машинному размеру слова. Он ра-
Гпава 24. Интерфейс с другими языками 629 вен 2 байтам на 16-битных машинах и 4 байтам на 32-битных. В качестве первой попытки неплохо будет скомпилировать макет процедуры на С с правильными па- раметрами и локальными переменными в ассемблерный код, а затем использовать код входа, выхода и доступа к переменным, сгенерированный компилятором С. Когда мы вызываем подпрограммы на других языках из Пролога, нет необходимости резервировать регистры, но, если вы вызываете их из С или ассемблера, то считает- ся, что вы должны зарезервировать si и di (esi и edi на 32-битных платформах). На 32-битных платформах также надо зарезервировать ebx.
ЧАСТЬ Разработка графического интерфейса пользователя Глава 25. Создание программы с графическим интерфейсом Глава 26. Средства создания графического интерфейса
ГЛАВА 25 Создание программы с графическим интерфейсом Визуальное программирование на Visual Prolog Полагая, что содержание части IV этой книги вами усвоено, попробуем разобрать несколько конкретных примеров работы в системе программирования Visual Prolog. Даже если вы не знакомы с синтаксисом Visual Prolog, это не помешает вам работать с приведенными далее примерами, но вам нужно научиться пользоваться системой интерактивной справки, если вы не сделали этого ранее. В особенности вам необхо- димо уметь находить информацию по предикатам VPI (Visual Programming Interface, интерфейс визуального программирования). Пример и экранные дампы, представленные здесь, были созданы в Win- dows 95/98/МЕ или Windows NT/2000. Итак, запускаем среду визуальной разработки Visual Prolog (VDE, Visual Development Environment). Начало работы с экспертом приложений Эксперт приложений (Application Expert) используется для создания нового прило- жения. Его можно вызвать при помощи пункта меню Project | New Project. Диалого- вое окно эксперта приложений представлено на рис. 25.1. Эксперт приложений поддерживает несколько возможных комбинаций значений Platform и UI Strategy, и для этих комбинаций — несколько компиляторов С. Вы можете изменить эти свойства, выбрав вкладку Target в верхней части диалогового окна Application Expert. Более подробное описание этих возможностей см. в гл. 27. А пока оставьте настройки по умолчанию, а именно: платформа Windows32 в списке Platform, "VPI” — в списке UI Strategy, "ехе" — в списке Target Туре и "Prolog" — в списке Main Program. Необходимо заполнить поле Project Name и выбрать/опре- делить основной каталог в поле Base Directory для проекта. Если вы задаете несуще-
634 Часть V. Разработка графического интерфейса пользователя ствующий основной каталог, эксперт приложений создаст его. Возможно, вы не за- хотите поместить проект в каталог, предложенный экспертом приложений по умол- чанию. Чтобы выбрать другой (существующий) каталог, нажмите кнопку Browse, которая вызывает диалоговое окно с деревом каталогов (рис. 25.2), где вы можете просматривать локальные (или сетевые) диски для поиска подходящего места для нового проекта; здесь же можно напечатать название необходимого подкаталога (то- гда вы непосредственно сможете редактировать содержимое поля Base Directory). Для нашего примера мы выберем каталог C:\TEMP, куда в его собственном подка- талоге TST будет помещен проект. il. Й & Application Expert few? -у--.VО| ® 1 МурГО|. Vpf ±ka^iiiii£ ' fn’i’r^ ir1-1-!- .i.i.Ri.HI!l!l!!!liWH>...44:rJ«W4^!i^sferi-j:; .......................................Л,-. Kt;^;.:::^;.!;,^s.Ti ШЙШЙ Рис. 25.1. Диалоговое окно Application Expert i ч вявч^Рвв И|С;\ТЕМР\Т5Т ____ Рис. 25.2. Просмотр каталогов В эксперте приложений вкладка VPI Options (рис. 25.3) позволяет определить раз- личные свойства интерфейса визуального программирования для вашего проекта.
Глава 25. Создание программы с графическим интерфейсом 635 Отметьте флажок Tree Package, т. к. мы будем использовать этот инструмент в на- шем демонстрационном приложении. Application Expert Geh©falj Тййде( | VPI Options | Other Options | User Info | Help Maker | ••• a • • •’ ••• l' • . • • | • J*.. • • • • • ’d, ' , ’ : ' ' :•**•**••*** . .j’m !• •, . . • • .•* ’«.• . ". • :e • » h - i MD1 Mode :; ' th : Р 3D Look for Controls ., ;. ; J . ^".’d- T. Ъ< |\ч^"ьЧи^ 1*^Хуь‘ *i [ Wi*A'.'AMj4Ii *. VW’.'S US'; <Z**>*b Ъ V ' «V* ’.УЛ’лА' 4-A'Z-*- » VWi*. W» 4« • •ЧЧ 4**/A S * WV * C • •• " । VPI Packages to Include into the Project - z <1 t Toolbar and Help Line . Г~ -IreeBrowset Package; / j : Z 1 p Editor . . j > :' П DHk6row$er Package <-. h: p £Halog Package .,' Г“ TabControlPackage ..; ' - 1 IP lire© Packager I Grid Package ’ / ?i ,J: .r~ Property List Editor i ' i i' :: j Г~ 0 wgerdraw Package < 1 Г~ J D rawing Palette < ? ;,: t i r DgteTooU r** PrggressBar ' .'.'r:;’;'\'.'-L‘V'.: I Filename to 1 nclude Packages: ]vplT0CLS.PR0 Settings... | ; ; Рис. 25.3. Вкладка VPI Options диалогового окна Application Expert Все свойства VPI, за исключением Toolbar and Help Line, могут быть изменены поз- же. Заданные по умолчанию панели инструментов для проекта создаются только тогда, когда создан новый проект командой Project | New Project. Если флажок Toolbar and Help Line не был отмечен во время создания проекта, можно активизи- ровать эксперт приложений в существующем проекте (при помощи комбинации клавиш <Ctrl>+<A>) и включить опцию Toolbar and Help Line. Это даст возмож- ность использовать в проекте панель инструментов, но при этом код и расположе- ние панели инструментов придется генерировать '’вручную” при помощи редактора панели инструментов (Toolbar Editor) и эксперта панели инструментов (Toolbar Expert). После установки свойств VP1 можно нажать кнопку Create для генерации всех необ- ходимых файлов и создания приложения по умолчанию. Кнопка Create расположена в нижней части диалогового окна Application Expert. В окне проекта среды визуальной разработки сейчас должны быть два исходных мо- дуля: VPITools.pro и Myproj.pro. Использование команды Project I Run После того как проект создан, команда меню Project | Run откомпилирует и выпол- нит его. Запустите проект командой меню Project | Run, или используя "горячую” клавишу <F9>, или нажав кнопку Щ на панели инструментов. Должно появиться небольшое приложение, как показано на рис. 25.4. На этой стадии выполняемые функции доступны только в некоторых пунктах меню. Проект по умолчанию — это основа для формирования вашего собственного прило- жения. В меню Help уже есть пункт About, а в дальнейшем будет сгенерирован и
636 Часть V. Разработка графического интерфейса пользователя небольшой справочный файл. Кроме того, ваше приложение будет иметь панель инструментов, строку подсказки и окно Messages, которое вы можете использовать для отладки и тестирования результатов. Рис. 25.4. Приложение по умолчанию Некоторые из сгенерированных пунктов меню, например File | Save, еще не имеют функциональности — в таком случае эти пункты неактивны (изображены серым цветом). Как изучать сгенерированный код На этой стадии вы, возможно, захотите изучить код и компоненты, сгенерирован- ные для вашего приложения по умолчанию. Это может быть сделано при помощи окна проекта. Окно проекта Все компоненты вашего приложения зарегистрированы в окне проекта (Project window). Кнопками на левой стороне окна проекта выбирают тип (исходный модуль, диалоговое окно, меню, панель инструментов, пиктограмма и т. д.) компонентов. Все компоненты выбранного типа будут перечислены в центральной части окна проекта. Можете выбрать один из них мышью или клавишами управления. Кнопки на правой стороне окна проекта выполняют действия над выбранным компонентом: New — добавить новый компонент выбранного типа, Edit — отредактировать вы- бранный компонент, Delete — удалить выбранный компонент и т. д. Окно проекта представлено на рис. 25.5.
Глава 25. Создание программы с графическим интерфейсом 637 Myproj.exe Myproj.pro Рис. 25.5. Окно проекта г CAProgram Files\ViP52\vpi\iNCLUDE\VPI.CON CAProgram Files\VIP52\vpi\INCLUDE\VPI.DOM - C:\Program Files\VIP52\TYPES.DOM CAProgram Files\VIP52\vpi\INCLUDE\VPI.PRE myproj.pre r myproj.inc - myproj.con г C:\Program Files\VIP52\vpi\INCLUDE\DIALOG\DIALOG.DOM CAProgram Files\VIP52\vpi\INCLUDE\EDIT.DOM jvpitools.dom ______________________________________________________ - CAProgram Files\ViP52\vpi4NCLUDE\TOOLBAR\TOOLBAR.DOM C:\Program Files\VIP52\vpi\INCLUDE\TREE\VPITREE.DOM CAProgram Files\VIP52\vpi\fNCLUDE\DIALOG\DIALOG.PRE CAProgram Files\VIP52\vptMNCLUDE\EDIT.PRE vpitools.pre C:\Program Files\VIP52\vpi\INCLUDE\MESSAGES\MESSAGES.PRE CAProgram Fi)es\VIP52\vpi'JNCLUDE\TOOLBAR\TOOLBAR.PRE L CAProgram Files\VIP52\vpiMNCLUDE\TREE\VPITREE.PRE hiptopic, con | myproj.inc CAProgram Files\VIP52MNCLUDE\ERROR.CON CAProgram File s\VIP52MNC LU DEMO DECL. DOM Рис. 25.6. Структура программы, показанная в окне дерева проекта
638 Часть К Разработка графического интерфейса пользователя Сгенерированный исходный код Вы видите, что для вашего проекта были сгенерированы два файла: Myproj.pro — основной модуль и VPITools.pro — модуль, который включает в себя инструменталь- ные средства, выбранные в эксперте приложений. Чтобы посмотреть структуру кода основного модуля проекта, выделите файл Myproj.pro и нажмите кнопку Edit. Следует обратить внимание, что код сгенериро- ван для следующих элементов: □ обработка событий для окна Task; □ основная цель (goal) проекта; □ создание в проекте панели инструментов и строки подсказки; □ создание окна About; □ отображение заданной по умолчанию справки. Дерево проекта Дерево проекта обеспечивает обзор файлов, используемых в проекте (рис. 25.6). Оно может быть активизировано при помощи команды меню Project | Tree, комбинации клавиш <Ctrl>+<T> или кнопки Д| панели инструментов. Двойной щелчок мыши на имени текстового файла внутри дерева проекта вызовет текстовый редактор для этого файла. Браузер исходного кода Попробуйте активизировать браузер исходного кода (Source Code Browser) (рис. 25.7), используя пункт меню Project | Browse. Code Browser dialoQ_cfeatemodal dialog_createmodele$s dialog_getcheck dialog_getcustom dialog_getdataheld dlalog_getint dialog getlistbox diahg_getfctbuUon dialogLgetlistedit dialoa_getlong dialogjjetradiobutton dialog getfeal dialogLgetscrollbar dialog_getsfr dialogLgetvalue dialogLgetvalues dialog_no_handler dialogLsetcheck dialogLseiconUolbtle dialogLsetcustom dialogL$etdatafi§ld Declaredнт - : Clauses in: tfialo^pre ($[PRODIR)vpAinclude\dialog\) >*** I- IIi'll II* IOi!ift i ft|й I Hrtwwi:m""" '<».***<'J (1И H. ..I. I.ItH'?.?• и сЫрвдЮ: l^PROO IR )vpi\iruilude\di«log\) n*^*i** iii.iWW4 Gojo ft? »|| 1*11^1111*1 Closg MM* ммивдо. * r main' и ******** ...... ...*...................ь.-. _ * т_ -J. _ Рис. 25.7. Окно браузера кода
Гпава 25. Создание программы с графическим интерфейсом 639 Это простой способ найти: □ объявления и реализации классов; □ объявления доменов; □ объявления и предложения предикатов и фактов, используемых в проекте. Браузер кода также можно вызвать нажатием кнопки РЦ] панели инструментов или при помощи комбинации клавиш <Ctrl>+<B>. Основные "горячие" клавиши Полезно будет запомнить несколько "горячих" клавиш, они представлены в табл. 25.1. Их использование значительно ускоряет работу. Кроме того, в большинстве окон среды визуальной разработки есть локальное всплывающее меню, которое появляет- ся при нажатии правой кнопки мыши. Таблица 25.1. Основные "горячие" клавиши Клавиша Функция <Ctrl>+<l> <Ctrl>+<T> <Ctrl>+<B> Активизируется браузер идентификаторов ресурсов Активизируется браузер дерева проекта Активизируется браузер исходного кода <Alt>+<F9> Выполняется команда построения проекта <F9> Выполняются команды построения и выполнения проекта для созданного приложения <Ctrl>+<0> <Ctrl>+<V> Копирование в буфер обмена Вставка из буфера обмена <Ctrl>+<X> <Ctrl>+<W> <Shift>+<Ctrl>+<W> Вырезание в буфер обмена Активизируется окно Dialog and Window Expert Активизируется диалоговое окно Insert Call of, в котором можно вставить вызовы по умолчанию общих или определяемых поль- зователем диалоговых окон, окон и панелей инструментов <Shift>+<Ctrl>+<V> Активизируется диалоговое окно VPI Predicates, в котором вы можете выбрать предикат из списка VPI-предикатов и вставить вызов по умолчанию выбранного VPI-предиката <S h ift>+<Ct rl >+<S > Активизируется диалоговое окно Standard Predicates, в котором вы можете выбрать предикат из списка встроенных в Visual Prolog предикатов и вставить вызов по умолчанию выбранного предика- та <Shift>+<Ctrl>+<K> <Shift>+<Ctrl>+<C> Активизируется диалоговое окно Visual Prolog Keywords, в ко- тором вы можете выбрать и вставить зарезервированные слова Переходы к разделу предложений (clauses) для отмеченного предиката
640 Часть V, Разработка графического интерфейса пользователя Таблица 25.1 (окончание) Клавиша Функция <Shift>+<Ctrl>+<D> Переходы к разделу объявлений (Declaration) для отмеченного предиката Развитие приложения "Hello World" Сначала мы попытаемся создать небольшое диалоговое окно, отображающее ‘'Hello World*' (Привет, Мир!). Допустим, мы хотим добавить пункт меню, который, когда он выбран, будет активи- зировать предопределенное общее диалоговое окно dlg_Note. Для этого необходимо сделать следующее: 1. Добавить пункт меню. 2. Добавить предложение, чтобы определить действия, выполняемые при выборе пункта меню (обработчик событий в меню Task Menu для захвата события). 3. В этом предложении сделать вызов предиката dlg Note (наща реакция на выбор пункта меню). Изменение меню в редакторе меню В окне проекта (рис. 25.8) нажмите кнопку Menu на левой панели инструментов. Когда в окне проекта появится список меню, зарегистрированных в проекте, дважды щелкните мыщью на пункте Task Menu для активизации редактора меню (рис. 25.9). jgCATEMPUSTVMyproipri HS ЕЗ Двойной щелчок Рис. 25.8. Окно проекта, показывающее зарегистрированные меню
Гпава 25. Создание программы с графическим интерфейсом 641 J Task Menu / Hem: Root Constant Prefix: id Hem Constant id edit Cut I |«File ММЙЙйЙМН^МйММА^' •" New Attributes SEdit «Help Menu hem Attributes Text.... Del Constant Help I : :Г Checked Г“ ’ £ isabled П Hglp : : ;- ’ , .<: ‘Л !" /. ; . .- -Л = ,1; ; . '. r Accelerator ••-•-; '< . <• ЙЙ ».-ч- r.Shitt. Г Ail . 5K ; мМйййаим«йй«йамам<амтй < Cancel - - Рис. 25.9. Добавление пункта меню в редакторе меню Выберите пункт меню Edit и добавьте новый пункт меню верхнего уровня с именем Test нажатием кнопки New (рис. 25.10). Вам не нужно вводить имя-константу для пункта меню: он автоматически получит константу id test по умолчанию. Кстати, символ &, который вы видите в именах некоторых пунктов меню, определяет, что при отображении меню символ, следующий за &, отображается подчеркнутым. Это используется для определения "горячих" клавиш. "Горячую" клавишу можно опреде- лить, напечатав символ в поле Accelerator и выбрав соответствующую комбинацию клавиш <Shift>, <Alt> или <Ctrl>. до Task Menu Item: Root Constant Prefix id 4^1 м^ммаамаммы Item С о n stan t: i detest Cut &File &Edit Copy «Test «Help Help Рис. 25.10. Редактор меню после добавления пункта меню Test
642 Часть V. Разработка графического интерфейса пользователя Далее нажмите кнопку Submenu для создания подменю с одним пунктом Hello World, который мы будем использовать, чтобы активизировать приглашение Hello World (рис. 25.11). Task Menu 4. - ’ \ Item: TestJ У: Constant Prefix: id_Test • ?, ItemCo n slant , Г A, C ': ,...,f, rr- 7 Hello World ^'New? Cut -: . f: Г - . г . :: v. .•.•. f 'лЙ.' Attributes) ^мйаммнЦммйшйнймЙМ*!;У ' *' "i ' w rf'i?' • ,ч * и :4y A®. «ММЙ*вМЙШММ№:^;:; Paste Pel Help ,r .!H.!4RI8WJP W шмн1Ыимвымм^*ммй4(мнМШ-^—_. . . .—. - --FV; '' J>':- iZ -<’4:! V'-.^ **eee*^4*A*4w^H**e*ii Close; ммотйййймм ЙМ Bai i w <?4 *& Рис. 25.11. Редактирование подменю в редакторе меню Позднее вы подключите пункт меню к выполняемому коду, используя значение константы, которое мы сейчас определим. Нажмите кнопку Attributes или просто дважды щелкните на пункте меню Hello World. Это активизирует диалоговое окно Menu Item Attributes для пункта меню Hello World (рис. 25.12). Замените автомати- чески сгенерированную константу id_Test_hello world на id_hello. Нажмите кноп- ку ОК, чтобы подтвердить сделанное изменение. Рис. 25.12. Редактирование атрибутов меню В диалоговом окне Menu Item Attributes вы можете определить, будет ли пункт меню неактивным (закрашен серым цветом) или отмечен галочкой. Если необходимо, здесь можно определить "горячую" клавишу. Теперь можно протестировать ваше меню. Нажимайте кнопку Back, пока она не станет неактивной. Затем вы можете выбрать режим Test для предварительного про- смотра меню. В режиме Test тестируемое меню появляется вместо меню задачи (т. е.
Глава ^Создание программы с графическим интерфейсом 643 взамен обычного меню) среды визуальной разработки; можно открыть и просмот- реть его подменю (рис. 25.13). Рис. 25.13. Редактор меню в режиме Test Чтобы выйти из режима Test, нужно щелкнуть мышью в любом окне среды визуаль- ной разработки за пределами меню или снова нажать кнопку Test. Закройте редак- тор меню при помощи кнопки Close. Использование эксперта окон и диалоговых окон Теперь можно воспользоваться текстовым редактором VPI для того, чтобы создать диалоговое окно Hello World. Однако в Visual Prolog проще и правильнее использо- вать эксперты кода. Эксперты кода могут выполнять часть стандартной работы, и, что более важно, они помогут в организации кода. Кроме того, они позволяют легко ориентироваться в коде. Рекомендуем всегда использовать эксперта окон и диалоговых окон (Dialog and Window Expert) при добавлении исходного кода Пролог для пунктов меню. Попро- буйте сделать это сейчас. Сначала нажмите кнопку Window в окне проекта; будет отображен список имен, зарегистрированных в проекте окон. Выберите окно с име- нем Task Window, это окно пока единственное зарегистрированное в проекте (рис. 25.14). Теперь нажмите кнопку Code Expert на правой стороне окна проекта (или исполь- зуйте комбинацию клавиш <Ctrl>+<W>) для вызова эксперта окон и диалоговых окон (рис. 25.15). После того как появится эксперт окон и диалоговых окон, вам следует выполнить следующие шаги: 1. Выбрать пункт Menu в списке Event Туре слева. 2. Выделить строку id_hello или любую другую константу, которую вы в качестве идентификатора ресурса связали с пунктом меню Hello World в списке Event or Item. Возможно, придется использовать полосу прокрутки для поиска. 3. Нажать кнопку Add Clause, чтобы сгенерировать Пролог-предложение для собы- тия. Название кнопки Add Clause изменится на Edit Clause, когда код для собы- тия будет создан. 4. Нажать кнопку Edit Clause.
644 Часть V. Разработка графического интерфейса пользователя ! gCAT Е MPU S Т\Myprojprj t New; ^ййшйШЙЙйм Task Window Code 1 i JE Xpert j = 4! Рис. 25.14. Вызов эксперта окон и диалоговых окон из окна проекта Dialog and Window Expert Dialog or Window S election : Г* Dialog r <• Window I Task Window Гавань : ::Л''' ':''^МШйй4йШМйНМММ^ММ«й^^ 1 Module J F? Automatic Source] Event Handling E vent Type I Window ^/ent or Item 14йДНйа|^аа4йнннннЬмЙМЙМММЬмми1 id_edit_redo id_edit_undo id_file_exit idjile_new id_file_open id_file_save id file save as Menu DDE Client DDE Server Misc :id hello ' • t . . . :— -3 1 F Cancgl дейшашяшжвшт ^4***"**WM4RMMt*a^aW^*>M***>> J .j J Edit £ode Выберите события (Event) для меню (Menu) 4МММЙ ***¥*Й*«Й**^«Й«^^ !• • • • -p.p. '•• /••’p; p 'P^’1’ PPP’iTP! Выберите idjiello l':'?" Toolbars... j 1 1 H ...? к |id_help_about 1 irl hpln rnnipnl^ E vent Help I Add Clause I "J •;• I! Update Code j; Рис. 25.15. Эксперт окон и диалоговых окон создает код меню Нажатие кнопки Edit Clause вызовет редактор (для файла Myproj.pro). Он позицио- нируется на соответствующем предложении обработчика событий. Обратите внима- ние, что к предикату обработчика событий для окна Task было добавлено следующее предложение: %BEGIN Task Window, id__hello task_win_eh(_Win,e_Menu(id_hello,_ShiftCtlAlt),0):-!, i %END Task Window, id_hello
Гпава 25. Создание программы с графическим интерфейсом 645 Комментарии %Begin - %End анализируются экспертом окон и диалоговых окон, чтобы определять положение кода для компонентов пользовательского интерфейса. Переменная win содержит дескриптор окна, в данном случае это дескриптор окна Task. Переменная ShiftctlAlt содержит флаг, из которого вы можете получить комбинацию клавиш <Shift>, <AIt> и <Ctrl>, которая использовалась, когда меню было активизировано. В обоих случаях символ подчеркивания — это синтаксическое соглашение, которое отменяет предупреждение компилятора для однократного ис- пользования имен переменных (что обычно бывает с неиспользованными перемен- ными). Добавить код можно с помощью меню Insert редактора. Попробуйте поместить кур- сор редактора в точке, где вы хотите вставить код (в данном случае, в начале строки после первого отсечения (!) в предложении), затем щелкните правой кнопкой мыши и выберите команду Insert | Predicate Call | Window, Dialog or Toolbar (рис. 25.16). Tfe £dk groject Qptront J^ndowHelp : Myproj.pri - Visual Prolog I hello help concents J: ЦМурюгрго Insert Predicate Call Delete SglectAH ; Ult) ,0) Change Case ; Search... ' ' Search Again ;; FJeplaca / lad to Line Number Goto Position... 'Go to Declarationi < Go to Clause Autoindent (on/off) Cbl+N £y Typed Example... i Ctrl+Shilt+I ; Standard Predicate... : Ctrl+SMt+S • • ' + Z . ....'..... УР1 Predicate... . | Ctrl+S hilt+V <Ё£Йог1УР1). . i . I U^ack:(VPI)... ; • MessagePack (VPi)..... ' QwnerdrawPack (VPI).. .; ToolberPack (VPI)... i " 7/TieePackjyPi)i i :; &odt.'“ Qiak %BEGIN Task Window, id_hello «у** task_win_eh (int e_Menu (id_he 11о,_Shi f tCtlAlt), 0) Undo Difectary... - Keywords.. • . •• Ctrl+S Wt+K id_help_contents !_Menu(idJielp_contents,_Shif tCtlAlt) ,0): , 4''Byprog.hlp") / S’#®-'!**' CUHShft+D Qrl+SMt+C Рис. 25.16. Меню вставки редактора Window. Dialog or Toolbar.. Ctrl+Shitt+W В появившемся сейчас диалоговом окне используйте кнопку раскрытия списка [*] для выбора общего диалогового окна dlg Note и напечатайте '’Hello World” (включая кавычки) (рис. 25.17).
646 Часть V. Разработка графического интерфейса пользователя Рис. 25.17. Определение предиката для вставки Заданный по умолчанию запрос к указанному предикату (вызов общего диалогового окна dlg_Note с определенными параметрами) будет затем вставлен в текст таким образом, чтобы окончательный код предложения выглядел так: task_win_eh (JWin, e_Menu (id__hello,_ShiftCtlAlt) ,0) : -!, Title="Title", dlg_Note(Title,"Hello World"), i • Последнее отсечение в действительности не является необходимым и будет автома- тически удалено компилятором Пролога, но это облегчает возможность вставить код при помощи меню вставки. Теперь выберите пункт меню Project | Run (или нажмите кнопку И) для того, чтобы приложение выполнилось. При выборе команды Test | Hello World появится диало- говое окно, представленное на рис. 25.18. Рис. 25,18. Диалоговое окно, которое появится при выполнении приложения Создание окна "Cross Window" Теперь освоим основы обработки событий. Для этого создадим небольшое окно, в котором нарисованы диагонали окна. Создание нового исходного модуля Нам нужно будет добавить исходный код Пролога для окна Cross, и, т. к. окно Cross будет иметь код, независимый от остальной части кода в проекте, мы поместим этот
Гпава 25. Создание программы с графическим интерфейсом 647 код в отдельный (новый) модуль. В Visual Prolog возможно обрабатывать множество окон и диалоговых окон в одном и том же исходном модуле. Как организовать про- ект — это решение программиста. В данном случае, повторим, мы создадим новый модуль. В окне проекта сначала нажмите кнопку Module (на левой стороне), а затем кнопку New, в результате чего появится окно, изображенное на рис. 25.19. ;;w.. Л.С. =: Add file Мелу = fSH Jpolbar :-:й: HU:'-; rj: •J,: • -!#£ те •Tti 3^.? <'5^5-j " S&gggg? > is* Cross| pro 21 obj 21 Res SI Myproj.pro и % Myproj В|й ’ Mi!prpi.p[0 ЙЙ] VPITools,pro New г Edit i Напечатайте имя нового модуля (Cross) iiiaii йаннананннаамна^й^йнн^а|^йннн^йямшна^йв^н|М|141аКММйММ1 Е Рис. 25.19. Добавление нового исходного модуля Когда вы наберете имя нового модуля, нажмите кнопку Open, появится диалоговое окно, в котором можно определить, генерировать ли файлы DOM и PRE для модуля (рис. 25.20). Хорошая идея — поместить любые экспортируемые из модуля объявле- ния глобальных доменов в файл Cross.dom, а любые экспортируемые глобальные предикаты в файл Cross.pre. В нашем проекте используются только значения по умолчанию (см. рис. 25.20), но для реальных (больших) проектов вы должны иметь в виду следующее. □ Не включайте никакие файлы вида <ModuleName>.PRE в файл Myproj.inc. Од- нако включите файлы вида <ModuleName>.PRE в каждый модуль, который ис- пользует глобальные предикаты, экспортируемые в файл <ModuleName>.PRE. Это уменьшит количество файлов для просмотра и число предикатов, используе- мых в каждом модуле (компилятор имеет ограничение на число предикатов, ис- пользуемых в модуле). □ Файл, содержащий символьные константы, которые определяют идентификаторы ресурсов (Myproj.con), следует включать в модуль, только если на него есть ссыл- ка из модуля (модуль использует идентификаторы ресурсов). □ Файл, содержащий символьные константы для контекстных номеров страниц справки (Hlptopic.con), следует включать в модуль, только если на него есть ссылка в этом модуле.
648 Часть У, Разработка графического интерфейса пользователя Если вы новичок в программировании на Visual Prolog, то вы можете не знать, что файлы с расширением pre содержат определения предикатов и функций Пролога (подобно файлам с расширением Н в языке С), или что файлы с расширением dom содержат информацию относительно определенных пользователем доменов. Впро- чем, эти примеры не требуют глубокого знания языка Visual Prolog. File Inclusions for Module < ~ Create Header Files • -.... • •— = Г Create 'Cross, dom1 •1;P. Create 'Crdss.pre' \ ' . ’ЛАЧЧЧ-Ж-и-Л Vi i V* V- V ЪЪъЛ Ч«ЧЛ , Vi > * "A < rt" г ч «‘чЧ* WM. Mu A'AV.-'A- .r Include Header Files in ‘Myproj.inc' : П jndude ‘Cross, dom' - - ) P Include “Cross, pre’ Include Header Files tn 'Cross.pro’ s Include’Myproj. con '..'v ; P Include’Hlptopic.con' : j Г" Include'Cross.pre' X Рис. 25.20. Диалоговое окно атрибутов для модуля Нажмите кнопку ОК, чтобы принять установки по умолчанию. Если вы рассмотрите новый сгенерированный исходный модуль, то увидите, что создан заголовок, осно- ванный на параметрах, заданных в эксперте приложений. Кроме того, включен гло- бальный файл для проекта (include "myproj .inc"). Copyright (c) Prolog Development Center Project: MYPROJ FileName: CROSS.PRO Purpose: No description Written by: Visual Prolog Comments: include ’’myproj . inc" include "myproj.con" include "hiptopic.con" Новый файл Cross.pre также был сгенерирован и помещен в каталог проекта. Если вы посмотрите на глобальный включаемый файл, вы увидите, что Cross.pre включен в нижней части файла Myproj.inc: include "cross.pre" Эксперты окон и диалоговых окон размещают объявления глобальных предикатов в файле <ModuleName>.PRE.
Гпава 25. Создание программы с графическим интерфейсом 649 Как создать новое окно Начнем с регистрации нового окна в проекте. Нажмите кнопку Window (рис. 25.21) на левой стороне в окне проекта и затем нажмите кнопку New (наверху справа). Появится диалоговое окно Window Attributes. В этом окне нужно набрать имя "Cross" и нажать кнопку ОК. Рис. 25.21. Диалоговое окно Window Attributes Рис. 25.22. Редактор окон
650 Часть V. Разработка графического интерфейса пользователя Когда диалоговое окно Window Attributes закроется, автоматически появится редак- тор окон (Window Editor) (рис. 25.22). Оно используется для размещения средств управления в окнах или изменения размера окна и атрибутов. Так как мы не соби- раемся выполнять эти действия для нашего окна, то можно просто закрыть его (комбинация клавиш <Ctrl>+<F4>). Генерация кода для окна по умолчанию Далее, для окна Cross нужно сгенерировать его стандартный исходный код Visual Prolog, генерируемый по умолчанию. Этот исходный код даст нам следующее: □ код, фактически создающий окно Cross с указанными атрибутами размещения; □ предикат обработчика событий, делающий возможной реакцию окна Cross на события, которые система GUI посылает, когда что-либо происходит с окном. Простой способ создавать и управлять исходным кодом Visual Prolog для окна со- стоит в том, чтобы использовать эксперт окон и диалоговых окон (рис. 25.23). Что- бы активизировать его, вы можете нажать комбинацию клавиш <Crtl>+<W> или кнопку Code Expert, когда окно Cross выбрано в окне проекта. Dialog and Window Expert ₽ T О ialog orWindow S election Place Source Code in, •• 1 '?S: OKI •W*: Cross, pro %: ^^4 |win_Createt. ) F J P Automatic Source Update Г : !, 'Л ’ .: ' ’ - ’ ' ' > , ' =: •Jf.4 • • • t*.~-4SA J-X.X<>-Л-x-v**4- r Event HMlMjg'— У Event Type Event or Uem Event or Uem •I IS Window fie CloseRequest Menu Scrollbar Control Key Mouse OwnDraw Miso ;e Create e_Destroy : e_End^pplication e_EraseBackGround t e_Get Focus e_LoseFocus e_Move . e_Native j p 4i?p.......... 4 Toolbars...^ гдалИ Qefauit Code Г Event Help 3 £ € S, , . .;;.7 у »j ; Рис. 25.23. Добавление кода по умолчанию в эксперте окон и диалоговых окон После того как эксперт окон и диалоговых окон активизирован, вам нужно выбрать исходный модуль Пролога, чтобы разместить там код для окна. Раскройте список и выберите исходный модуль Cross.pro. Затем нажмите кнопку Default Code. Когда заданный по умолчанию код будет сгенерирован, кнопка Edit Code (и некоторые другие) станет доступной. Нажатие кнопки Edit Code вызовет редактор (для файла Cross.pro), позициониро- ванный на сгенерированном коде. Вы увидите, что в нижнюю часть выбранного мо- дуля (Cross.pro) был добавлен следующий код:
Глава 25. Создание программы с графическим интерфейсом 651 %BEGIN_WIN Cross Создание и управление событиями для окна: Cross constants %BEGIN Cross, CreateParms, 21.11.2000, Code automatically updated! win—cross_WinType = w_TopLevel win_cross_Flags = [wsf_SizeBorder,wsf_TitleBar,wsf_Maximize, wsf_Minimize,wsf_Close,wsf—Clipsiblings] win_cross_RCT ~ ret(100,80,440,240) win_cross_Menu = no_menu win_cross_Title = "Cross" win_cross_Help = idh_contents %END Cross, CreateParms predicates win_cross—eh : EHANDLER clauses win_crosS—Create(Parent) win_Create (win_cross_WinType, win_cross_RCT, win_cross__Title, win_cross_Menu,Parent,win_cross—Flags, win_crosS—eh,0). %BEGIN Cross, e_Create win_cross_eh(_Win,e_Create(_),0):-!, %BEGIN Cross, InitControls, 21.11.2000, Code automatically updated! %END Cross, InitControls %BEGIN Cross, ToolbarCreate, 21.11.2000, Code automatically updated! %END Cross, ToolbarCreate i e %END Cross, e_Create %MARK Cross, new events %BEGIN Cross, e_Size win_cross_eh (_Win, e_Size (_Width,_Height) ,0) : - !, ifdef use—tbar toolbar_Resize(_Win), enddef i • • %END Cross, e_Size %BEGIN Cross, e Menu, Parent window win cross eh(Win,e Menu(Id, Cas), 0):-!, Parent « win_GetParent(Win), win_SendEvent(Parent,e_Menu(Id,Cas)), i « • %END Cross, e_Menu, Parent window %END—WIN Cross Система Visual Prolog добавляет различные комментарии к исходному коду. Она ис- пользует их для того, чтобы позднее находить сгенерированный код.
652 Часть V. Разработка графического интерфейса пользователя Если вы видите блок, окруженный такими комментариями: "Code automatically updated!" это значит, что эксперт кода автоматически обновит эту часть кода, когда соответст- вующее расположение или параметры будут изменены редакторами визуальной сре- ды разработки. По этой причине вам не следует добавлять свои изменения в эти разделы, т. к. эксперт кода перезапишет их во время последующих модификаций. Фактически, только несколько разделов будут модифицированы автоматически. Остальную часть кода вы свободно можете изменять. Если вы не хотите иметь части кода, обновляемые автоматически, обратитесь к экс- перту окон и диалоговых окон и снимите флажок Automatic Source Update для этого окна. Теперь, когда мы закончили создавать окно, необходимо активизировать его. Добавим выполняемые функции в меню Test в нашем окне Task. Для начала мы добавим один дополнительный пункт меню и назовем его CrossWin. Используйте кнопки Menu и Edit в окне проекта, чтобы редактировать меню Task. Когда Menu Editor активизируется, выберите пункт Test и нажмите кнопку Submenu. Используй- те кнопку New, чтобы добавить пункт меню для CrossWin (рис. 25.24). Рис. 25.24. Пункт меню для активизации окна CrossWin Выполняемые функции для нового пункта меню могут быть добавлены очень легко. Сначала активизируйте эксперта кода для окна Task: выберите переключатель Window на левой стороне окна проекта, отметьте в списке значение Task Window и нажмите кнопку Code Expert. Когда появится эксперт окон и диалоговых окон, в списке Event Туре выберите строку Menu, в списке Event or Item выберите иденти- фикатор ресурса id__Test_crosswin и затем нажмите кнопку Add Clause (рис. 25.25). Далее нажмите кнопку Edit Clause, чтобы выбрать редактор. Заметьте, что к обра- ботчику события основного окна (Task) вашего приложения было добавлено сле- дующее предложение: task_win_eh (_Win, e_Menu (id_Test_crosswin, _ShiftCtlAlt), 0) : - !, i
Гпава 25. Создание программы с графическим интерфейсом 653 Dialog and Window Expert !' ptece S ource Code in... Й|а1й»в® OK Cancel dft Dialog or.Window Selection- "" С7Dialog ' indow IT ask Window й|1|1ЯНМШМ1М1МММ1ЙШММйМ1М1МИМ*МИИМММ*МН{1*ММШМ**** [ Event Type Event or Item . 7 j I Window 5J^S^niiattc^jOce:ffid’ate' Menu DDE Client DDE Server Misc ft id_file_new id_file_open id_file_save id_file_save_as id_hello id_help_about id_help_content$ id help local ,1 * u * .. г ... * II > v i 11 vi 11 >i VI u i v * :id Test crosswin ММ*Ш«ММ*М1«№ММММММ ': '!.7:<w =7- и : i i i i? Edit Code Layout Toolbars. V | I -:X U , j'lVl'F 'l!" J Л111 11 riii iiiHjiixji ij;ji ir n i A Рис. 25.25. Добавление предложения в эксперте окон и диалоговых окон Здесь (в начале второй строки) нажмите правую кнопку мыши и выберите пункт меню Insert | Predicate Call | Window, Dialog or Toolbar (или нажмите комбинацию клавиш <Ctrl>+<Shift>+<W>). Эти действия активизируют диалоговое окно, изо- браженное на рис. 25.26. Рис. 25.26. Генерация вызова предиката для создания пользовательского окна Выберите переключатель User Defined Window. После того как вы нажмете кнопку ОК, будет вставлен вызов предиката для создания окна Cross. task_win_eh (__WiN, e_Menu (iD_Test__Crosswin, _ShiftCtlAlt) ,0) : - !, win__cro'ss_Create (_Win) f i
654 Часть И Разработка^рафического интерфейса пользователя Сейчас запустите приложение и проверьте, что вызов команды Test | CrossWin акти- визирует небольшое окно с заголовком Cross. Проверка того, что окно реагирует на события Следующим шагом будет проведение небольшого эксперимента. Поместите курсор на начало предиката обработчика события в модуле Cross и наберите новое предло- жение (которое должно быть первым предложением обработчика событий), которое распечатает полученное событие и вызовет предикат fail (будьте внимательны, не добавляйте никаких отсечений!). Это не будет влиять на логику окна Cross, но в окне Messages (нашего приложения Myproj) вы можете теперь просматривать все события, которые посылаются обработчику событий окна Cross (рис. 25.27). win_cross_eh(Win, Event, 0):- % <— Введите эти три строки write(Win, Event),nl, %BEGIN Cross, e_Create win cross eh( Win, e Create( ),0) Попытайтесь запустить приложение. Выполните различные действия, например: создание, движение и изменение размеров окна, набор на клавиатуре, работу мыши и закрытие окна. Теперь вы можете наблюдать, какие события посылаются окну в различных обстоя- тельствах. Можно добавить код (предложения обработчика событий win cross eh), который реагирует выборочно на каждое из этих событий. Рис. 25.27. Изучение событий
Гпава 25. Создание программы с графическим интерфейсом 655 Сейчас мы изучим основы рисования в окне. Приложение всегда должно быть под- готовлено к тому, чтобы перерисовать окно. Если окно было закрыто и затем вы- двинуто на передний план, содержимое должно быть повторно перерисовано. Всякий раз, когда окно должно быть перерисовано, обработчик событий для окна получит событие ejjpdate. Поэтому нам нужно добавить код, чтобы выполнить это изменение. В качестве примера мы будем рисовать диагонали окна. Чтобы сделать это, обратим- ся к эксперту окон и диалоговых окон и добавим предложение для события ejjpdate (рис. 25.28). Рис. 25.28. Добавление предложения для управления событием е Update Чтобы добавить вызов предиката VPI, вам следует воспользоваться средствами вставки в редакторе. Можно использовать меню вставки (и всплывающее меню мы- ши), но существует также комбинация клавиш <Shift>+<Ctrl>+<V>, которая немед- ленно вызывает список VPI-предикатов. Чтобы нарисовать диагонали, сначала мы должны найти координаты нижнего угла. Для этого находим клиентский прямоугольник запросом к .win_GetclientRect; этот прямоугольник содержит в двух последних параметрах ширину и высоту окна, так что код для выполнения этих действий выглядит следующим образом: win_cross_eh(Jtfin, ejjpdate(_), 0):-!, RCT = winjJetClientRect(Jtfin), RCT = rctJ^R/B)/ draw_Line(_Win, pnt(0,0),pnt(R,B)), draw_Line (Jin, pnt (0, B) ,pnt (R, 0)), i %1 Напечатать эти 4 линии здесь %2 Координаты прямоугольника %3 относительно окна
656 Часть V. Разработка графического интерфейса пользователя Рис. 25.29. Внешний вид окна Cross Когда вы запустите свое приложение и выберете команду пункта меню Test | CrossWin, на экране можно будет увидеть окно, изображенное на рис. 25.29. Вы увидите, что окно ведет себя почти правильно, что вы можете создавать несколь- ко экземпляров окна, перемещать его, перекрывать другим окном и делать снова видимым. Однако, если вы будете изменять размеры окна, обнаружится проблема. Окно перекрашивается неправильно. Вы увидите, что только заново открываемая часть окна перекрашена. Это происходит из-за того, что базовая система GUI пыта- ется перекрашивать по минимуму и для этого устанавливает область отсечения так, чтобы модификации затрагивали только новую часть окна. Это правильное поведе- ние для многих окон, таких как редакторы, изображения и т. д. Однако в нашем случае этого недостаточно: когда изменяется размер окна, нам нужно перерисовать все окно. Один из способов — вызвать предикат win invalidate в событии e Size. Используйте редактор окон, нажмите кнопку Edit Clause для события e_size (там есть уже некоторый код, заданный по умолчанию). После того как вы вставили вы- зов winjrnvalidate, ваш код для события e__Size должен выглядеть так: win_cross_eh(_Win, e_jSize(_Width, _Height),0):-!, win_Invalidate(_Win), ifdef use_tbar toolbar_Resize (__Win), enddef i Если вы снова запустите приложение, вы заметите, что теперь окно работает как нужно. Несколько операций рисования Теперь, раз уж мы занимаемся рисованием, рассмотрим еще пару дополнительных графических предикатов. Добавим такой код к предложению обработчика для собы- тия e_Update: draw_Text(_Win, 55, 20, "This is a text in the Cross Window"), draw_Ellipse(_Win, ret(30,50,60,100)), draw_PoLyGon(_Win,[pnt(130,130),pnt(180,130),pnt(145,100), pnt(150,150),pnt(170,110)]), draw Rect( Win, ret(250,50,300,100)),
Гпава 25. Создание программы с графическим интерфейсом 657 Имеются и другие графические примитивы. Можно установить шрифт, цвет, размер строки, тип линии, образец заполнения и т. д. (рис. 25.30). Эти параметры описаны в гл. 26, посвященной VPI, и в интерактивном справочнике. Рис. 25.30. Новые рисунки в окне Cross Создание окна Sweep Добавим окно со следующими свойствами: П окно должно иметь собственное локальное меню; □ окно должно иметь панель инструментов на правой стороне окна, из которой окно может быть очищено; □ мышь будет использоваться для создания рисунков. Перед тем как создавать окно, нам нужно создать меню и панель инструментов. Од- нако перед созданием панели инструментов следует спроектировать несколько рас- тровых рисунков, чтобы использовать их для кнопок. Итак, в таком порядке мы и будем создавать компоненты. Создание модуля SWEEP.PRO Как и раньше, мы будем создавать новый исходный модуль — т. к. окна полностью независимы, мы размещаем их в отдельные модули. Выберите модули, нажмите кнопку New и введите имя Sweep, чтобы создать модуль Sweep.pro, куда будет поме- щен исходный код для окна Sweep. Создание нового меню (меню Sweep) Для создания нового меню нажмите кнопку Menu в окне проекта, а затем — кноп- ку New. Дайте новому меню имя Sweep menu; идентифицирующая константа бу- дет вставлена системой. Нажмите кнопку ОК, чтобы закрыть диалоговое окно (рис. 25.31). Когда имя Sweep menu появится в списке меню, используйте редактор меню (на- жмите кнопку Edit), чтобы добавить одно меню верхнего уровня под названием Sweep (нажмите кнопку New). Теперь используйте кнопки Submenu и New для создания одного подменю Clear (рис. 25.32). Идентификатором по умолчанию является idr_Sweep_clear, При помо-
658 Часть V. Разработка графического интерфейса пользователя щи кнопки Attributes измените константу для меню на id_clear, т. к. мы будем ссы- латься на это значение позже. * New Menu Name Sweep menu -j:;:! Joolbar Task Menu Menu Attributes EdgJ ЙЙййШйЙй >• :?. U I JConstant I id_$ weep_menu i? fl: * File -iSii’Kt R*1;’ S-1-xJ1 |RES\Sweep menu mnu gftmap is •?-- ,£апойй :'ЧН**НН|й«*й>МЙмШ1ММВ»й ¥ $ авгмймммшшй Рис. 25.31. Создание нового меню Щ Myproj prj - Visual Prolog Этот пункт меню должен иметь константу id_clear Рис. 25.32. Новое меню в режиме Test Создание растрового рисунка Чтобы создать новый растровый рисунок, нажмите на кнопку Bitmap в окне проек- та, а затем — на кнопку New. Вы должны теперь ввести название растрового рисунка (mybmp_norm) и нужный размер. Если вы не определите константу, чтобы иденти- фицировать растровый рисунок, она будет сгенерирована автоматически. Вы также можете ввести имя файла для BMP-файла (если имя не задано, оно будет сгенери- ровано по умолчанию). Теперь нажмите кнопку ОК (рис. 25.33). Будет активизирован графический редактор, который можно использовать для ре- дактирования пиктограмм, курсоров и растровых рисунков. Заголовок окна редакто- ра показывает имя файла для редактируемого растрового рисунка. В дальнейшем, чтобы открыть графический редактор для ресурса, зарегистрированного в окне про- екта, дважды щелкните на названии ресурса. Теперь изобразите небольшой растро- вый рисунок, который может использоваться как кнопка. Графический редактор (рис. 25.34) очень прост в использовании, особенно при разработке маленьких кнопок, курсоров и растровых рисунков. Однако для больших растровых рисунков он непригоден: для этого вам придется использовать графический редактор MSPAINT.EXE или другие специализированные программы.
Гпава 25. Создание программы с графическим интерфейсом 659 Рис. 25.33. Создание нового растрового рисунка в окне проекта Рис. 25.34. Редактор пиктограмм Когда вы закончите рисовать вашу специальную кнопку, закройте графический редактор. Рисунок будет сохранен в файле. Затем нужно создать еще один растровый рисунок (mybmp_down), который может использоваться как "нажатая” версия кнопки. Удостоверьтесь, что картинка имеет такой же размер, как и первая. (Мы скопировали рисунок mybmp_norni и использо- вали кнопку инвертирования графического редактора.)
660 Часть V. Разработка графического интерфейса пользователя По умолчанию 32-разрядная визуальная среда разработки генерирует длинные имена файлов (например, Mybmp_Down.bmp). Напротив, 16-разрядная визуальная среда разработки не может обрабатывать имена файлов длиннее восьми символов и со- держащие пробелы. Поэтому в приведенных примерах все имена файлов имеют длину не более восьми символов. Создание панели инструментов Теперь, когда мы сделали изображения для кнопки, следующим шагом будет созда- ние панели инструментов. В окне проекта нажмите кнопку Toolbar для выбора типа компонента и нажмите кнопку New, чтобы создать новую панель инструментов. Дайте новой панели имя Sweep toolbar (рис. 25.35). Рис. 25.35. Создание новой панели инструментов в окне проекта После того как вы нажмете на кнопку ОК, в списке появится название новой пане- ли инструментов и активизируется редактор панели инструментов (Toolbar Editor) с диалоговым окном Control. В вашем распоряжении четыре типа средств управления, которые могут быть встав- лены в панель инструментов: □ кнопки, которые используются, чтобы вызвать действие (посылка команды меню); □ списки переключателей, которые используются, чтобы указать состояние; □ текстовые поля, контекстно-зависимые тексты которых являются подсказкой назначения кнопок; □ раскрывающиеся списки, которые отображают множество вариантов выбора.
Гпава 25. Создание программы с графическим интерфейсом 661 Используйте кнопку IBjMI панели инструментов, чтобы добавить кнопку на панель инструментов. Появится диалоговое окно Button Properties; нажмите кнопку ОК. Сейчас вы видите редактор панели инструментов для вашей панели инструментов Sweep toolbar (рис. 25.36). Рис. 25.36. Редактор панели инструментов для Sweep toolbar Чтобы установить свойства для новой кнопки, вы должны дважды щелкнуть на ней мышью. В появившемся диалоговом окне Button Properties для кнопки могут быть определены три растровых рисунка, один для каждого из трех возможных состоя- ний: освобожденная, нажатая и заблокированная (рис. 25.37). Button Properties Constant id clear •м- ?|=. Status Text Released Bitmap Name: Ъ-:, vW-Wl®- ; : w, .1 w: W:\ ' if®. Push Button s' ; •,< " * W !-J ’JI* Г* Check Button mybmp norm М*иЙ1Ймтммми«ммм i Pressed В itmap N ame mybmp.down : ! ".с- Edit Grayed Bitmap Name:/wW ® f. mybmp_down Cancel Й«!ЙЖ101М)М . О * » г W- W «чйшинЙкрмтнчжМмжяммм.;..:;'ri.?::• ;У i- f:।кf:,:;: КФ;;:1 1 nnW 11ИЙ1 •ф: Рис. 25.37. Задание свойств кнопок Вам также нужно ввести идентификатор для кнопки в поле Constant, чтобы про- грамма могла реагировать при активизации кнопки. Заданное по умолчанию пове- дение для панели инструментов состоит в том, что она посылает такое событие, как будто меню было активизировано. Это означает, что, если вы создали меню для ва- шего окна и вставили код, чтобы обработать некоторые пункты меню, вам не при- дется создавать какой-либо дополнительный код, чтобы добавить кнопки панели, которые соответствуют этим пунктам меню. Замените значение в поле Constant по умолчанию на id__clear так, чтобы оно соответствовало предварительно созданному пункту меню, и удостоверьтесь, что названия растровых рисунков ссылаются на рисунки, которые вы разработали ранее.
662 Часть V. Разработка графического интерфейса пользователя В соответствии с начальным планом панель инструментов должна располагаться в правой стороне окна. Мы можем легко изменять стиль панели инструментов двой- ным щелчком мыши внутри окна панели (чтобы активизировать диалоговое окно Toolbar Attributes) и изменением свойства Toolbar Style на Right (по умолчанию за- дано Тор) (рис. 25.38). Рис. 25.38. Задание свойств панели инструментов Создание окна Теперь, когда у нас есть и меню, и панель инструментов, самое время начать созда- вать окно Sweep. Процедура такая же, как и для других компонентов. Выберите тип компонента Window и нажмите кнопку New. Появится диалоговое окно Window Attributes (рис. 25.39). Для окна Sweep мы будем определять три параметра: 1. В поле Name напечатаем Sweep — имя окна. 2. В списке Menu Ваг Name выберем оконное меню Sweep menu. 3. Свяжем панель инструментов Sweep toolbar с окном Sweep. Обратите внимание, что окно должно быть создано прежде, чем будет присоединена панель инструментов. Поэтому после второго шага нажмите кнопку ОК, чтобы за- вершить создание окна. Появится редактор окна. Двойным щелчком в клиентской области можно снова вызвать диалоговое окно Window Attributes, но в данном случае кнопка Toolbars будет доступна. Теперь панель инструментов Sweep toolbar может быть прикреплена к окну Sweep, как описано далее.
~ава 25. Создание программы с графическим интерфейсом 663 Три изменения Window Attributes йНМММЙЙМЙМ КМмЫЙ RESXS weep, win £ •jim-iTrf 34 Рис. 25.39. Создание окна Sweep ЙййЙ^^ Мм** ide I op Level Window МШЙйм Size Border ^^ЦиИммйМйамаммк I Sweep menu i«erj J -СЛ И g Эксперт панели инструментов Чтобы добавить панель инструментов, мы нажимаем кнопку Toolbars для вызова эксперта панели инструментов, при помощи которого к окну можно добавить пане- ли инструментов (рис. 25.40). Рис. 25.40. Присоединение панелей инструментов к окнам
664 Часть V. Разработка графического интерфейса пользователя Обратите внимание, что вы можете добавить более одной панели инструментов к окну, хотя в нашем примере достаточно добавить одну панель Sweep. Диалоговое окно эксперта панели инструментов выполняет две различные операции: □ в левой части диалогового окна панели инструментов связываются с окном (кнопкой Add). В центральной области окна слева вы видите список панелей ин- струментов, связанных с окном, которое выбрано в раскрывающемся списке Window; □ в правой части диалогового окна можно добавить исходный код для управления панелью (рис. 25.41). Рис. 25.41. Добавление кода к панели инструментов После назначения Sweep toolbar окну Sweep, вам следует выбрать исходный модуль, который будет содержать исходный код для данной панели (при помощи кнопки раскрытия списка Module). В нашем примере код панели инструментов должен на- ходиться в модуле Sweep.pro. Нажмите кнопку Default code для генерации необходимого кода. Если вы войдете в редактор и изучите сгенерированный код, то увидите следующий текст: %BEGIN_JTLB Sweep toolbar, 22.11.2000, Code automatically updated! Создание панели инструментов: панель инструментов Sweep tb__sweep_toolbar_Create (_Parent) ifdef use_tbar toolbar__create (tb_right, 0x808080,__Parent, [tb_ctrl (id_clear,pushb, idb_mybmp_norm, idb_mybmp_down, idbjnybmp_down, ””,1,1)]), enddef true. %END_TLB Sweep toolbar
Гпава 25. Создание программы с графическим интерфейсом 665 Следующим шагом будет создание кода для окна Sweep; при помощи комбинации клавиш <Ctrl>+<W> вызовите эксперт окон и диалоговых окон (рис. 25.42). Dialog and Window Expert r Dialog or Window Selection - ; C. piafog;; ———« ! & Window FweeP ммШцийМмаммймйййМ - Place ScwrceCode in.. - j Module У r !: ''''i'CW:':" Sweep, pro J =^qe>^win_Create(...) : |..P Automatic Souce Update «рмм 1 $ Event Туре Event or Item Window e CloseR equest Menu Scrollbar Control \ |;| Key '/г’ Mouse । OwnDraw . I : Misc :: 1 e_Create e_ Destroy e_EndApplication e_EraseBackGround e_GetFocus e_LoseFocu$ e_Move e Native Л < ’> '•!•-'! IT. I' r > =: -J :j L; * S-*! '’’'"к-. T- A-'зrw:• :•.\:-v’ *<..-..- Toolbars... мм*едммммйййййм«№ ««там* £ f 4 j E^ent Helpt3< F£efautCod& I Рис. 25.42. Эксперт окон и диалоговых окон генерирует код для окна Сначала нужно выбрать надлежащее окно (или диалоговое окно), с которым мы хо- тим работать (в данном случае это окно Sweep). Затем необходимо выбрать исход- ный модуль для кода (Sweep.pro). Когда параметры будут установлены правильно, нажмите кнопку Default code, чтобы добавить код Visual Prolog по умолчанию для обработки окна. Теперь, когда мы закончили создавать окно, нужно активизировать его. В нашем приложении мы создаем окна из подменю Test в меню Task, поэтому необходи- мо войти в редактор меню и добавить дополнительную команду Sweep Window (рис. 25.43). Test ... .1.1.1. .1. .1 I. . Window Т HelbWorld;- S; QossWin ' Sweep Window Рис. 25.43. Меню Test с добавленной командой Sweep Window Теперь, как обычно, войдите в редактор окон и диалоговых окон и добавьте предло- жение для создания окна Sweep. Это окно должно создаваться, когда событие при вызове меню Test | Sweep Window приходит в окно Task (рис. 25.44). После того как будут нажаты кнопки Add Clause и Edit Clause, вы окажетесь в редак- торе на соответствующем предложении. Нажмите комбинацию клавиш <Shift>+
666 Часть И Разработка графического интерфейса пользователя +<Ctrl>+<W>, чтобы вызвать диалоговое окно, из которого вы можете вставить вы- зов на создание окна Sweep. Не забудьте переместить курсор редактора в начало второй строки. task_win_eh (Jflin, e_Menu (idJTest_sweep_window,_ShiftCtlAlt) ,0) : -1, win_sweep_Create (_Win), i * « Dialog and Window Expert i-w-;b : rpialog orWindow Selection Л i^4’i*iWHi4iViY iVS * I “-• |‘ l‘ • .• f i*. ~f -~f j . A* rUJn -it -i> » " । jyj %- Jx!J" ’\^S '!' . Жйж|| g”/i - y. .-? -S г • ; _,:' ' 7 i Menu DDE Client DDE Server Misc i4:' wj ft:': fi il' id_tile_open id_file_$ave id_file_$ave_a$ id_hello id_help_about i d_ h el p_ contents id_helpjocal id Test crosswin E vent ' orltem 1ам*аммм||аАмьймимааАМИИИЙ Window 1 i'id Test sweep window at Рис. 25.44. Добавление предложения для вызова окна Sweep Теперь запустите приложение и посмотрите, что произойдет после выбора команды Test | Sweep Window (рис. 25.45). Ваше окно Sweep должно появиться с добавленной панелью инструментов. Посмот- рите критически на меню после того, как активизируется окно Sweep. Меню в окне Task изменилось, появилось локальное меню для окна Sweep (Sweep menu). Это обычное поведение MS Windows в режиме MDI. Когда фокус ввода устанавливается на окне документа, окно контейнера MDI показывает меню для активного окна. (Поведение меню различно для разных платформ GUI). Когда вы устанавливаете фокус на данное окно, меню поменяется на меню, связанное с этим окном. Заме- тим, что, находясь в окне Sweep, вам придется щелкнуть мышью на окне Message (для возвращения Task menu), чтобы можно было активизировать окно Cross. Обратите внимание, что текущая версия пакета TOOLBAR получает цвет фона пане- ли инструментов из параметров настройки Windows (игнорируя цвет, указанный в редакторе панели инструментов).
Гпава 25. Создание программы с графическим интерфейсом 667 Рис. 25.45. Вызов окна Sweep в первый раз Рисование с помощью мыши в окне Sweep Теперь попробуем рисовать в окне. Попытаемся использовать те же самые методы, которые применяются в демонстрационном примере SWEEP.PRO из <CDROM>: \RUN\VPI\EXAMPLES на прилагаемом CD-диске. Всякий раз, когда нажата кнопка мыши, программа регистрирует координаты точек, через которые проходит мышь (захватывая события e_MouseMove в течение промежутка времени от нажатия до освобождения кнопка мыши), и рисует линию от каждой точки до других точек. Мы хотим получить примерно такое окно (рис. 25.46): Рис. 25.46. Внешний вид окна Sweep Сначала мы создадим предикаты, которые могут нарисовать линии из любой точки в списке к каждой из других точек в списке. Напечатайте следующие предложения
668 Часть V. Разработка графического интерфейса пользователя в верхней части модуля Sweep.pro, они должны быть помещены прямо под операто- рами include. Рисование линий sweep predicates connect(window,pntlist) drawlines(window,pnt,pntlist) clauses connect(_,(]):-!. connect(Win,[From | Rest]):- drawlines(Win,From,Rest), connect(Win,Rest). drawlines(_,_, []):-!. drawlines(Win,pnt(X,Y),[pnt(ToX,ToY) | Rest]):- draw__Line (Win, pnt (X, Y) , pnt (ToX, ToY) ) , drawlines(Win,pnt(X,Y),Rest). На следующем шаге рассмотрим логику обработки событий. Нам необходим факт базы данных для сохранения позиций мыши; мы будем называть этот факт point (точка). Так как события e_MouseMove возникают независимо от изменений состоя- ния кнопки мыши (событие e_MouseMove возникает как результат перемещений мы- ши), нам нужно зарегистрировать то, что кнопка мыши была нажата. Ну и, наконец, мы хотим, чтобы было более одного окна со своим локальным рисунком. Будем со- хранять дескриптор окна —- значение, которое гарантируется уникальным для каж- дого окна, вместе с другими необходимыми данными, в фактах. Напечатайте эти строки в верхней части программы Sweep.pro после конструкции include: Факты для окна sweep facts — apl nocopy point(WINDOW,PNT) mouse_isdown(WINDOW) Теперь рассмотрим сущность функционирования. Какие события нужно фиксиро- вать и как мы должны реагировать на них? Мы будем обрабатывать следующие че- тыре события, для которых (при помощи эксперта окон и диалоговых окон) должен быть добавлен код по умолчанию (к модулю Sweep.pro). Событие e^MouseDown Когда кнопка мыши нажата, мы сначала удалим любые факты, которые уже могут быть занесены в базу фактов для окна. Затем занесем факт, указывающий на то, что кнопка мыши сейчас нажата (в окне), и в конце сделаем вызов предиката win_invalidate, чтобы, если окно уже содержит рисунок, оно очистилось. Выберите тип компонента Window в окне проекта. Выделите окно Sweep. Исполь- зуйте комбинацию клавиш <Ctrl>+<W> для активизации эксперта окон и диалоге-
Гпава 25. Создание программы с графическим интерфейсом 669 вых окон. В списке Event Туре выберите строку Mouse, а в списке Event or Item — событие О—Mouse Down. Нажмите кнопку Add Clause, чтобы добавить код по умолча- нию для предложения обработчика событий, обрабатывающего событие e_MouseDown. После того как текст Add Clause на кнопке преобразуется в Edit Clause, нажмите кнопку снова. Вы окажетесь в редакторе (для файла Sweep.pro) на соответствующем предложении. Напечатайте 4 строки, показанные далее: win___sweep_eh (_Wiri, е_MouseDown (_PNT, _ShiftCtlAlt,—Button) ,0) : -!, retractall(mouse_isdown(JWin)), %1 Эти 4 линии retractall (point (_Win,_) ), %2 следует assert(mouse_isdown(_Win)), %3 вставлять win_Invalidate(_Win), %4 вручную i Событие e_MouseMove На событие перемещения мыши, если мышь нажата, мы будем запоминать позицию мыши. Чтобы показать, где двигалась мышь, мы изменим также цвет пиксела. Вставьте этот код, как было описано для события e_MouseDown: win_sweep_eh (—Win, e_MouseMove (_PNT, _ShiftCtlAlt,_Button) ,0) : - !, mouse_isdown (_win) , assert(point(_Win,_PNT)), draw Pixel( Win, PNT,color Black), I Событие e_MouseUp На событие e_MouseUp мы сначала удалим флаг, указывающий на то, что кнопка мыши нажата, и затем вызовем предикат win_lnvalidate, так что в окне может быть изображен заново созданный рисунок. win sweep eh( Win,e MouseUpt PNT, ShiftCtlAlt, Button),0):-!, retractall(mouse_isdown(—Win)), win_Invalidate(_Win), i Событие eJJpdate Событие обновления e_Update содержит код, который очищает окно, перед тем как собрать все точки в списке и соединить их друг с другом. Напечатайте код следующего предиката в начале программы Sweep.pro (после кода предиката drawlines): Рисование окна sweep predicates sweep—Draw(window Win,window AssertWin)
670 Часть V. Разработка графического интерфейса пользователя clauses sweep—Draw (_Win, AssertWin) win_Clear (_Win, color_White), f indal1(X,point(AssertWin,X),List), connect(_Win,List). Затем добавьте следующее предложение в предикат обработчика окна Sweep. Обра- тите внимание, что в эксперте окон и диалоговых окон вам следует выбрать строку Window в списке Event Туре и строку e_Update в списке Event or Item: win_sweep_eh (_Win, e_Update (_Ret) ,0) : - !, sweep_Draw (_Win,_Win) , i « Управление панелью инструментов Наконец, мы хотим обработать нажатие кнопки Clear на панели инструментов. Ко- гда кнопка панели инструментов нажата, посылается событие e_Menu, поэтому мы можем использовать эксперт окон и диалоговых окон, чтобы добавить код для собы- тия e Menu с идентификатором ресурса (константой) id clear: win_sweep_eh (_Win, e_Menu (id_clear,_ShiftCtlAlt) ,0) , retractall(mouse_isdown(_Win)), retractall(point(_Win,_))t win_Clear(_win, color_White), i t t Теперь можно открыть панель инструментов Sweep и проверить, что ваша кнопка имеет постоянный идентификатор id_clear. Для этого выберем тип компонента Toolbar (на левой стороне окна проекта), дважды щелкнем мышью на названии Sweep toolbar и затем на лицевой стороне вашей кнопки. Вы должны очистить окно вызовом предиката win_Clear, а также не забыть от- менить факты, чтобы окно затем не было повторно перерисовано (в событии е_Update). Окончание работы При закрытии окна нужно отменить все факты, которые были сохранены для этого окна. Воспользуемся специальным событием e_Destroy, которое является последним событием, получаемым окном: win_sweep_eh(_Win,е_Destroy, 0):-!, retractall(mouse—isdown(_Win)), retractall(point(—Win,—)), I Изменение курсора мыши Продолжим наши эксперименты. Давайте попробуем теперь изменить курсор мыши для окна Sweep. Курсоры зарегистрированы в окне проекта, поэтому нажмем кнопку
Гпава 25. Создание программы с графическим интерфейсом 671 Cursor из панели инструментов на левой стороне окна проекта и кнопку New, чтобы создать новый курсор с именем Sweep (рис. 25.47). Рис. 25.47. Создание нового курсора Рис 25.48. Созданный курсор Когда имя нового курсора появится в списке, вы должны дважды щелкнуть мышью на нем, чтобы активизировать редактор курсоров (графический редактор) (рис. 25.48). Здесь вам предоставляется возможность спроектировать курсор для окна Sweep. Об- ратите внимание, что для курсора можно применять четыре различных цвета: чер- ный, белый, прозрачный (цвет экрана) и инверсию. Попробуйте использовать все
672 Часть V. Разработка графического интерфейса пользователя четыре цвета в курсоре, который вы создаете, чтобы познакомиться с этими воз- можностями для дальнейшей работы. Обычно курсоры мыши создаются при исполь- зовании только двух цветов: экрана и инверсии. После того как курсор будет создан, проверьте его внешний вид и поведение, вы- брав команду Tools | Test cursor (рис. 25.49). Рис. 25.49. Элемент меню для тестирования курсора Установка "горячего" элемента для курсора Когда вы создадите курсор, нужно будет установить его "горячую" точку, т. е. пик- сел, который будет определять позицию мыши, для которой надо зарегистрировать щелчок. Чтобы установить "горячую" точку курсора, выберите команду Set Cursor Hotspot из меню Edit (рис. 25.50). Рис. 25.50. Элемент меню для установки "горячей" точки курсора Когда этот элемент меню будет активизирован, вы можете щелкнуть на изображении курсора в том месте, где должна быть "горячая" точка (рис. 25.51). Теперь нужно написать рабочий код. Для этого требуется вызов единственного пре- диката в событии создания для окна Sweep. Вызовом предиката cursorset возмож- но изменить курсор для окна.
Глава 25. Создание программы с графическим интерфейсом 673 win_sweep_eh. (_Win, e_Create (_), 0) : -!, cursor_Set(_Win, idc_sweep) jp Sweep.cut Size:32x32 Colors.2 Рис. 25.51. Изменение "горячей" точки курсора Когда вы добавляете вызов предиката cursor_Set, используйте предложенный ниже метод. 1. Активизируйте эксперта окон и диалоговых окон нажатием комбинации клавиш <Ctrl>+<W>; затем нажмите кнопку Edit Clause для события e__Create окна Sweep — теперь вы в нужном месте исходного кода. 2. Используйте комбинацию клавиш <Shift>+<Ctrl>+<V>, чтобы вставить вызов предиката cursor_Set. 3. Выберите второй аргумент Resid в вызове предиката cursor_Set. Используйте команду Insert | Resource identifier) Cursors, чтобы -вставить идентификатор кур- сора. Теперь запустите свое приложение и проследите, как ведет себя мышь. Создание всплывающего меню Во многих современных приложениях используется правая кнопка мыши, чтобы показывать локальное всплывающее меню для каждого окна. Воспользуемся и мы этой возможностью в окне Sweep. Всплывающие меню проектируются в редакторе меню так же, как и любые нор- мальные выпадающие меню. Сейчас вам следует создать новое (всплывающее) меню с именем Sweep Popup. Удостоверьтесь, что константа меню — это idr_sweep_popup, и определите четыре команды, изображенные на рис. 25.52, — Clear, Close, Maximize и Restore. Убедитесь, что константа, используемая для пункта Clear, такая же, как и для пунк- та Clear, который вы определили в меню Sweep и панели инструментов Sweep
674 Часть V. Разработка графического интерфейса пользователя (id_clear). Для остальных созданных пунктов меню должны быть константы id_close, idjnaximize и id_restore (проверьте это и исправьте, если ваша версия среды визуальной разработки генерирует иные значения по умолчанию). Рис. 25.52. Проектирование всплывающего меню Предложение, которое вызывает всплывающее меню при нажатии правой кнопки мыши, очень простое. Используя редактор, добавьте дополнительное предложение для события e_MouseDown, как показано ниже. Это предложение выясняет, нажата ли правая кнопка мыши, если не выполняется другое предложение для события е MouseDown. Не забудьте поместить новое предложение перед старым или альтернативно изме- нить первую строку старого предложения так, чтобы оно реагировало только на ле- вую кнопку мыши. win_sweep_eh (_Win, e_MouseDown (PNT,_,mouse_button_right) ,0) : -!, menU—PopUp (_Win, res_menu (idr_sweep_popup) f PNT, align_Right) . win_sweep_eh (_Win, e_MouseDown (_PNT,mouse_button_left), 0) : -!, Теперь запустите программу и попытайтесь вызвать окно и использовать правую кнопку мыши. Вы увидите, что пункт Clear уже работает (если вы не забыли исполь- зовать id_clear как имя константы для пунктов очистки) — рис. 25.53. Пункт меню Clear работает потому, что всплывающее меню генерирует те же собы- тия e_Menu, что и регулярные меню и панели инструментов. Когда вы попытаетесь добавить код для трех новых пунктов меню, вы обнаружите, что их нет среди пунктов меню для окна Sweep, т. к. они не зарегистрированы (и не могут быть зарегистрированы) в эксперте окон и диалоговых окон. Необходимые функции могут быть реализованы, если напечатать три предложения для этих событий вручную. Добавим следующие три предложения в нужном месте в вашем предикате обработчика событий:
Гпава 25. Создание программы с графическим интерфейсом 675 Рис. 25.53. Показ всплывающего меню win_sweep—eh (_Win, е_Menu (id_close,_ShiftCtlAlt) ,0) :-!, win_Destroy (_Win) . win_sweep_eh (—Win, e_Menu (id—maximize, —ShiftCtlAlt), 0) : -!, win_Setstate(_Win, [wsf_Maximized]). win_sweep_eh(_Win, e_Menu(id__restore,—ShiftCtlAlt) ,0) , win_SetState(_Win, [wsf—Restored]). Также вы можете обнаружить, что реакция на щелчок правой кнопкой мыши не соответствует поведению уже заданной обработки события e_MouseUp. Если это так, вам нужно изменить предложение, чтобы заданная обработка осуществлялась лишь при нажатии левой кнопки мыши, win_sweep_eh(_Win, e_MouseUp (_PNT,_,mouse—button—left) ,0) :-! или изменить порядок предложений. Изменение цвета рисунка Любое изображение строк и пикселов происходит посредством выбранного текущего пера, которое имеет ширину, стиль (сплошной или полый) и цвет. Текущее состоя- ние пера может быть получено при помощи вызова предиката win_Get?en и измене- но вызовом предиката win_SetPen. Чтобы выбрать новый цвет, мы можем использо- вать один из общих диалогов dlg_ChooseColor, который изменяет текущий цвет и возвращает новый цвет, выбранный пользователем. Нам необходимо активизировать изменяющую последовательность, сделаем это, до- бавляя один дополнительный пункт Color к всплывающему меню. Он будет иметь имя константы id-Color: win_sweep—eh (—Win, e_Menu (id_color,— ShiftCtlAlt), 0) : - !, Pen-win—Get Pen (—Win) , Pen = pen(Penwidth,Penstyle,Color) , NewColor = dlg_ChooseColor(Color), win_SetPen(_Win,pen(Penwidth,PenStyle,NewColor)), win_Invalidate(—Win).
676 Часть И Разработка графического интерфейса пользователя Окно часов Полагая, что действия компонентов вы освоили, попробуем решить такую задачу: создать окно, которое отображает текущее время. 1. Создайте новый исходный модуль Clock.pro. 2. Добавьте пункт Clock Window в подменю Test меню Task. 3. Создайте новое окно Clock. 4. Зайдите в эксперт окон и диалоговых окон и добавьте код по умолчанию для окна Clock в модуль Clock.pro. 5. Используйте эксперт окон и диалоговых окон, чтобы добавить предложение для вызова окна Clock из окна Task при выборе команды Test | Clock Window. 6. Запустите программу и посмотрите, создается ли окно при вызове команды Test | Clock Window. 7. Вернитесь в эксперт окон и диалоговых окон и добавьте предложение для собы- тия e_Update в окне Clock. Использование таймеров Использовать таймер очень просто. Вы можете в любое время создать новый таймер для окна вызовом предиката timer Set (Window, Milliseconds). Как только предикат timer_Set был вызван, окно в указанные интервалы времени будет получать событие e_Timer. Событие e__Update для окна часов На событие обновления ejJpdate мы будем выводить текущее время, центрирован- ное в окне. Мы можем получить время запросом к предикату time/4. Первые три целых числа, возвращенные time/4, затем преобразуются в строку предикатом format. Наконец, строка выводите^ в центре окна предикатом draw_TextInRect, ко- торому передается полный клиентский прямоугольник для вывода текста по центру этого прямоугольника. win_clock_eh (__Win, e_Update (_), 0) : - !, win_Clear(—Win,color_White), RCT=win_GetClientRect(_Win), time (Hours,Minutes, Seconds, , format(Str,"%02:%02:%02",Hours,Minutes,Seconds), draw_Text!nRect(_Win, RCT, Str, -1, [dtext_center,dtext_vcenter,dtext_singleline]) Событие e_Size для окна часов Каждый раз, когда окно изменяет свои размеры, изображение нужно перерисовы- вать. Правильнее будет не перерисовывать при событии изменения размера окна e__Size, а вызывать новое событие e jjpdate для всего окна. win__clock_eh (_Win, e__Size (_Width,_Heigdt) ,0) : - !, win_Invalidate(_Win)
Гпава 25. Создание программы с графическим интерфейсом 677 Событие e_Create для окна часов На событие e_Create мы запускаем таймер. Значение интервала в 1000 миллисекунд означает, что мы будем получать событие таймера каждую секунду. win_clock_eh(_Win,e_Create(_J ,0):-!, _NewTimerId =timer_Set(_Win, 1000) Событие e_Timer для окна часов На событие е Timer мы будем объявлять окно недействительным. Это вызовет об- новление окна в том случае, если оно не свернуто и не закрыто другим окном. (Вы найдете событие e_Timer под группой Misc в эксперте окон и диалоговых окон.) win_clock_eh(_Win,e_Timer (_TimerId) , 0) win_Invalidate(_Win) Обратите внимание, что в реальном приложении вы, вероятно, не стали бы объявлять недействительным целое окно, т. к. слишком много времени может потребоваться на то, чтобы перерисовать его. Если вы хотите, чтобы программа была быстрой и выглядела профессионально, нужно стараться не объявлять окно недействительным. Правда, это потребует дополнительных усилий уже после того, как основные функ- циональные возможности будут реализованы. Таймеры — ограниченный ресурс системы. В реальном приложении идентификатор таймера, возвращаемый timer_set, должен быть сохранен для освобождения в собы- тии е destroy, где он будет использоваться в качестве аргумента в предикате timer_Kill/l. Окно изображения Сейчас добавим еще одно окно, в котором будем показывать изображение. Изобра- жения в этом примере хранятся в файлах растровой графики, а управлять растровы- ми изображениями, используя VPI, очень просто. Выполните следующие шаги: 1. Создайте новый исходный модуль Picture.pro. 2. Добавьте пункт Picture Window в подменю Test меню Task. 3. Создайте новое окно Picture. 4. Зайдите в эксперт окон и диалоговых окон и добавьте код по умолчанию для окна Picture в модуле Picture.pro. 5. Используйте эксперт окон и диалоговых окон для добавления предложения для окна Task, которое вызывало бы окно Picture при выборе команды Test | Picture Window. 6. Запустите программу и посмотрите, создается ли окно при вызове команды Test | Picture Window. 7. Вернитесь в эксперт окон и диалоговых окон и добавьте предложение для собы- тия e_update в окне Picture.
678 Часть V. Разработка графического интерфейс^ пользователя 8. Добавьте следующий код для предложения e_Update: win_jpicture_eh (_Win, e__Update (__), 0) : - ’, % не забудьте использовать правильный путь к требуемому ВМР-файлу Picture=pict_Load (”С: \\VIP52\\Bin\\Win\\Prolog.bmp") , ClntRCT=win_GetClientRect(_Win)t pict_GetSize(Picture, Width, Height, _Size), pict_Draw(_Win,Picture, ClntRCT, ret (0,0, Width,Height),rop_SrcCopy), pict_Destroy(Picture) Это предложение нуждается в небольшом объяснении. Сначала предикат pict Load вызывается с именем файла, содержащего растровый рисунок. Предикат возвратит указатель растрового рисунка, который загружается в память. Вы можете использовать меню Edit | Insert, чтобы вставить имя файла для любого растрового рисунка. Изображение может быть растянуто до любого размера, поэтому мы должны найти размер и окна, и изображения. Затем мы вызываем предикат pict_Draw с дескрипторами окна (win) и изображения (picture), а также с двумя прямоугольниками. Последний параметр для pict_Draw определяет, как изображение совмещается с пикселами, которые уже принадлежат окну. Здесь же мы только определяем, что хотим копировать изображение в окно (замещение существующих пикселов). Наконец, уничтожаем изображение, чтобы оно не занимало место в памяти. Нам необходимо добавить предикат win invalidate к событию e_Size. Создание окна дерева Теперь создадим окно, которое покажет структуру дерева. Дерево изображается при помощи инструментального средства, которое можно найти на прилагаемом CD-диске в подкаталоге <CDROM>:\RUN\VPI\INCLUDE\TREE. Это одно из ин- струментальных средств, которое может быть включено в модуль VPITools.pro. Пе- ред созданием окна Tree следует удостовериться, что включена опция Tree Tool на вкладке VPI Options в эксперте приложения (это можно сделать комбинацией кла- виш <Ctrl>+<A>). Теперь подготовьте окно Tree, выполняя следующие шаги: 1. Создайте новый исходный модуль Tree.pro. 2. Добавьте пункт Tree Window в меню Task подменю Test. 3. Создайте новое окно Tree. 4. Зайдите в эксперт окон и диалоговых окон и задайте, что код должен быть в мо- дуле Tree.pro, затем вам следует выбрать стиль кода tree_Create для генерации экспертом кода (рис. 25.54). 5. После того как вы добавили код по умолчанию для окна Tree, добавьте новый пункт к меню Task и создайте предложение для вызова окна Tree.
Гпава 25. Создание программы с графическим интерфейсом 679 Dialog and Window Expert f г Dialog or Window Selection ^l Dialog ....a.. Jr? our ce Code' in. . !Й<хЙЙ;;Л:'Я ft|||®1Sc S ource Update; Event Type Event orltem Tree, pro tree Create! I A - - . — . . . J ^^^4 - - - A - A A L I A - b — - I J L A A . Window T ree Window e CloseR equest Help гй<0й№1рймамммемшМш0№ММ '• •!• • • •!••• •.*••!•.•.•:•'•: •:: ; • • •••:••;. Layout >i 11111 i и i ij муу!!МДЙ>1!«1ШГ>(^ЙЦ Menu Scrollbar Control Key Mouse OwnDraw Misc j e_Create e_Destroy e_End^pplication ' e_EraseBackGround e_GetFocus e_LoseFocus ? e_Move s e Native мм*мЙ4*й'**НЙ<^ЙН',**на* Hili IJiiiiMMjM'HH. /1 : ЁуйЙН^[- ?^dr6®£ I ' Рис. 25.54. Добавление кода по умолчанию для окна дерева Загляните в заголовочный файл VPITREE и посмотрите, как создано дерево. В Про- логе очень просто создавать дерево из различных структур данных, таких как списки файлов на вашем жестком диске или записи в базе данных. win_tree_Create (—Parent): - ifdef use_tree Tree = tree("0",unmarked, [tree(”3",unmarked, [],0), tree("2”,unmarked, [tree(”4”,unmarked,[],0)], 0) , tree(“I",unmarked,[],0) ],0), TreeDir - tree_dirright, Font = font—Create(ff—Fixed,[],10), TreeWinlnfo = tree—Wininfo(Font,TreeDir, []) , tree—Create(win—tree—WinType,win—tree-RCT, w i П—t re e_T i 11 e, w i n__ t r e e_Me nu, —Pa rent, win—tree—Flags,win-tree—eh,0,Tree, TreeWinlnfo,[]), enddef true. Обратите внимание, что хотя окно Tree представляет собой особый вид окна, его код имеет такую же структуру, как и код для окон, которые вы создаете самостоя- тельно: имеется вызов для создания окна и стандартный обработчик событий.
680 Часть V. Разработка графического интерфейса пользователя Работа с деревом Добавьте следующее предложение для события e_MouseUp и посмотрите, как легко получить информацию узла, щелкнув по нему мышью: win__tree__eh (_Win, e_MouseUp (_PNT,_ShiftCtlAlt,_Button) ,0) SelectorText=tree_GetSelectedNode(_Win), dlg_Note("Node Selected"f SelectorText)f I Создание окна редактора Так же просто, как и окно, показывающее дерево, можно создать окно редактора. Для настройки и использования редактора существует гибкий API (Application Programming Interface). Помимо специальных функций, редактор поддерживает за- крашивание лексемы и гипертекстовые поля. Теперь поработаем с окном редактора. Выполните следующие шаги: 1. Создайте новый исходный модуль Edwin.pro. 2. Создайте новое окно Editor. 3. Войдите в эксперт окон и диалоговых окон и определите, что код должен быть в модуле Edwin.pro. Выберите стиль кода edit Create для генерации экспертом кода. 4. Добавьте пункт Editor Window в меню Task подменю Test. Вставьте код для соз- дания окна редактора для этого события. 5. Попытайтесь запустить приложение: откройте окно редактора и попробуйте редактировать какой-либо текст в этом редакторе. Если посмотрим на сгенерированный код в модуле Edwin.pro, то увидим, что вызов создания окна редактора имеет задаваемый по умолчанию набор значений для раз- личных аргументов. win_editor_Create(_Parent) ifdef use_editor Text = f Font - font_Create(ff_Fixed,[],10), Readonly = b_false, Indent = b_true, InitPos = 1, edit_Create(win_editor_WinType,win_editor_RCT,win_editor_Title, win_editor_Menu,_Parentfwin_editor_Flags,Font,Readonly, Indent,Text,InitPos,win_editor_eh), enddef true. Вы можете легко изменить этот код, например, чтобы загрузить определенный текст в редактор: filejstr("С:\\TEMP\\TST\\Myproj.con",Text) Далее попытаемся добавить несколько возможностей окну редактора.
Гпава 25. Создание программы с графическим интерфейсом 681 Выбор лексем Чтобы проиллюстрировать, как делать выборку и находить часть текста в редакторе, организуем такую возможность: при двойном щелчке на слове (лексеме) пусть вы- зывается поле примечания с этим словом. Для этого необходимо использовать три предиката из редактора. Предикат edit_SelectWcrd высвечивает выбранную лексему, editjSetSelection находит на- чальную и конечную позиции текущей выборки и edit_GetText возвращает выбран- ный в редакторе текст. win_editor_eh(_Win,e_MouseDbl (_PNT,__ShiftCtlAlt,_Button) , 0) , WordSelected = edit_SelectWord (_Win) , WordSelected = b_true, edit_GetSelection(_Win,Posl, Pos2) , Text = edit_GetText(_Win), NoOfBytes = Pos2-Posl, substring(Text,Posl,NoOfBytes,Substring), format(Msg,"Got the token: >%<",Substring), dlg_Note(Msg) Вставка текста в редактор Вставить текст в редактор, вызывая предикаты, очень просто. Предикат edit_PasteStr может вставлять строку в любую позицию в тексте (эта позиция на- чинается с 1). В качестве примера мы можем добавить новую функцию, которая состоит в том, что, как только вы напечатаете начало комментария /*, автоматически будет встав- лен конец комментария */. Чтобы сделать это, мы будем следить за печатаемыми символами. Когда пользователь нажимает клавишу, активному окну посылается со- бытие e Char. Очень просто добавить пару предложений, которые отслеживают сим- волы / и *. Как только замечен символ /, устанавливается флажок для регистрации этого факта, и если следом идет символ *, тогда будет вставлена строка */, в про- тивном случае флажок будет сброшен. Используем предикат edit GetPos, чтобы оп- ределить позицию курсора в редакторе. Наконец, для удобства пользователя мы вы- деляем строку, которая вставлена таким образом, что если пользователь не хочет ее вставлять, ему всего лишь нужно напечатать любой символ, и строка исчезнет. win_editor_eh (_Win, e_Char ('/',_), 0) : - !, not (commentflag (_Win)) , assert(commentflag(_Win)) , fail. win__editor_eh (_Win, e_Char ('*’/_)/ 0) : - !, retract (commentflag (_Win) ) , Pos = edit_GetPos(_Win), edit_PasteStr(_Win, Pos, "* */"), Posl = Pos+1, Pos2 = Posl + 4, edit_SetSelection(_Win, Posl, Pos2),
682 Часть V. Разработкаграфического интерфейса пользователя win_editor__eh {_Win, е_Char (_,_), 0) : - !, retract {comment f lag (_Win) ), fail. Два предыдущих предложения, которые просто управляют состоянием флажка, тоже завершаются неуспехом; таким образом, редактор сам может выполнять стандартную обработку таких сообщений (вставить символы, обновить экран и т. д.) так, как если бы этих предложений не было. Чтобы определить базу данных, используемую выше, вставьте следующий код в на- чале файла: facts — editor commentflag(window) Сохранение текущего текста Текст в редакторе может быть сохранен на диске при помощи последовательных вызовов предикатов edit_GetText и file_str. Если в окне редактора мы хотим использовать команду File | Save, чтобы вызвать сохранение текста, то для этого нужно обратиться к диалоговому окну настройки атрибутов для окна редактора и отметить, что окно редактора должно иметь меню Task окна. Этот пункт меню не должен быть разрешенным, когда другие окна имеют фокус, поэтому сделаем его разрешенным в предложении, обрабатывающем e_GetFocus-co6biTHe, и, соответственно, неразрешенным в предложении, обрабаты- вающем e__LoseFocus-co6biTne: win_editor—eh {_Win, e__LoseFocus, 0) : - !, menu_Enable{_Win, id_file_save, b_false), ! . win editor eh( Win,e GetFocus,0):-!, menu_Enable{_Win, id_file_save, b_true), f • Должно быть добавлено следующее предложение, обрабатывающее событие e_Menu: win_editor_eh (_Win, e_Menu (id—file_save/—ShiftCtlAlt) ,0) : - !, Msg="Save As File", Flags-[dlgfn_Save], SaveFileName=dlg_GetFileName{"*.txt", ["* .txt","*.txt", Msg, Flags,_OutListFiles) , Text = edit—GetText(_Win), file_str{SaveFileName, Text), win_SetText{_Win, SaveFileName), t V В исходном коде файла Edwin.pro вы можете проверить, как используется событие e_initmenu для активизации функциональности пунктов меню из подменю Edit.
Гпава 25. Создание программы с графическим интерфейсом 683 Обработка буфера обмена Буфер обмена используется для обмена данными между приложениями. Прило- жения должны быть способны помещать данные в буфер обмена и получать их от- туда. Текст и изображения можно поместить в буфер обмена, вызывая преди- каты cb_PutString или cb_PutPicture, и восстановить из буфера обмена, вызывая cb_GetString или cb_GetPicture. В качестве примера мы можем улучшить наше окно Sweep таким образом, чтобы всякий раз, когда выбран пункт меню Copy to Clipboard, рисунок копировался в бу- фер обмена, а когда выбран пункт Paste from Clipboard, изображение вставлялось из буфера обмена. Для этого во всплывающем меню создадим новые пункты Copy to Clipboard (с кон- стантой id_edit_copy) и Paste from Clipboard (с константой id_edit_paste). Перед тем как помещать изображение в буфер обмена, его нужно взять из окна, в котором оно находится. Один из методов — использование предиката pict_Open, затем нужно совершить операции рисования, чтобы создать картинку, и вернуть изображение при вызове предиката pict_Close. Предикат sweep Draw находит все точки, затем вызывает предикат connect со списком точек, тем самым перерисовы- вая изображение. Таким образом, мы дублируем последовательность операций рисо- вания между вызовами pict_Open и pict__Close. Как и в случае других предложений, обрабатывающих функциональные возможности меню Sweep Popup, вам нужно до- бавить предложения для этих событий (к файлу Sweep.pro) вручную: win_sweep_eh(_Win,e_Menu(id_edit_copy,_), 0):-!, RCT=win_GetClientRect(JWin), PictWin - pict__Open (RCT) , sweep_Draw(PictWin,_Win) , Picture = pict_Close(PictWin), cb_PutPicture(Picture), pict_Destroy(Picture). win_sweep_eh(_Win,e_Menu(id_edit_paste,_), 0) : - Picture=cb_GetPicture(), pict__Dr aw (__Win, Picture, pnt (0,0) , rop_SrcCopy) , pict_Destroy(Picture) . Обратите внимание, что, т. к. мы копируем растровый рисунок, а не список точек, вставленное изображение не будет перерисовано в событии e_Update. Печать Печать инициируется вызовом предиката print__startJob и завершается вызовом предиката print_EndJob. Каждая страница должна быть выделена парой предикатов: print_StartPage и print_EndPage. Все предикаты изображения работают как обыч- но, за исключением того, что они должны иметь дескриптор окна печати в качестве параметра окна.
684 Часть V. Разработка графического интерфейса пользователя Printwin~print_StartJob("Printing Sweep drawing"), print_StartPage(Printwin), Draw to the Printwindow print_EndPage(Printwin), print EndJob(Printwin). Обычно у принтера разрешающая способность намного выше, чем у экрана монито- ра, поэтому если вы просто копируете код рисования на окне, то результатом, ско- рее всего, будут очень маленькие изображения. Решение — в применении предикатов масштабирования. Система GUI может пре- образовывать любые координаты в другую систему координат, пропорционально изменяя размеры изображения. Масштабирование выполняется вызовом предикатов set_MapMode И set_MapScale. Сначала мы выясняем ширину окна, вызывая предикат win Getciient, а затем — ширину бумаги на принтере, вызывая предикат vpi_GetAttrVal (attr_printer__width). Во всплывающем меню Sweep вам нужно создать пункт Print с константой id_print. Используя следующие предложения, вы можете распечатать окно Sweep. win_sweep_eh (_Win, e_Menu (id_print,_) ,0) : - !, ScreenRCT=win_GetClientRect(_Win), ScreenRCT=rct (_,_, WinSize,_J , PRINTWIN=print_StartJob("Printing Sweep drawing"), print_StartPage(PRINTWIN), PrnSize = vpi_GetAttrVal(attr_printer_width), win_SetMapMode(PRINTWIN,mm_arbitrary), win_SetMapScale(PRINTWIN,pnt(0,0),pnt(WinSize,WinSize), pnt(0,0),pnt(PrnSize,PrnSize)), sweep_Draw(PRINTWIN,__Win) , print_EndPage(PRINTWIN), print_EndJob(PRINTWIN). Обратите внимание, что предикат sweep Draw был изменен: теперь он принимает на вход два параметра: predicates sweep_Draw(window Win,window AssertWin) clauses sweep_Draw (_Win, AssertWin) win_Clear(_Win, color_White), findal1(X,point(AssertWin,X),List), connect(_Win,List). Причина в том, что один из параметров фактов point является дескриптором окна Sweep (в котором находятся сохраненные точки). Предикату необходимы дескриптор для принтера (окно печати) и дескриптор окна Sweep. Возможно, вам пригодятся эти навыки при создании собственных приложений, если потребуется открывать несколько окон одного и того же типа.
Гпава 25. Создание программыс графическим интерфейсом 685 Добавление элементов управления в окно часов Пришло время познакомиться со средствами управления. Средства управления — это специализированные дочерние окна, которые обычно используются в диалоговых окнах для выполнения различных задач ввода/вывода. Для начала мы добавим два средства управления к нашему окну Clock: □ кнопку, которая запускает/останавливает время; □ переключатель для определения, показывать время или нет. ч Разместить эти элементы управления в окне Clock можно, используя редактор окон и диалоговых окон. В окне проекта выберите пункт меню Window, выделите окно Clock и нажмите кнопку Edit. После того как откроется редактор окна, на экране отобразится панель инструмен- тов Controls. На этой панели инструментов вы можете нажимать на соответствую- щие кнопки элементов управления и перетаскивать их в окно Clock. Если отпустить кнопку мыши в окне, элемент управления будет помещен в выбранную позицию. Если отметить прямоугольник, элемент управления будет помещен в отмеченный прямоугольник. Когда вы отпустите кнопку мыши, активизируется диалоговое окно Control Attributes (диалоговое окно PushButton Attributes показано ниже на рис. 25.55), в котором вы можете определить атрибуты для элемента управления. Теперь нужно ввести имя элемента управления; оно должно быть определено в управляющем элементе редактирования (на рисунке имя кнопки — Start). Рис. 25.55. Добавление элементов управления к окну Clock Для элементов управления нужно определить параметры в полях Text и Constant: □ для кнопки: в поле Text значение start, в поле Constant значение idc_st art stop; □ для переключателя: в поле Text значение show Date, в поле Constant значение idc_show_date. Сейчас вы могли бы проверить несколько различных функций размешения/ком- поновки редактора окон и диалоговых окон. Рисуя охватывающий прямоугольник
686 Часть V. Разработка графического интерфейса пользователя или удерживая клавишу <Ctrl> щелчком мыши, вы можете отмечать группу элемен- тов управления и изменять их расположение при помощи меню Layout или панели инструментов (рис. 25.56). Clock HIsIEl I ' ?Start I • ’л 17 Show Date! '4.-Л w***»* И. ♦*•»»*+»« ***, «***/-: 2000/11/2-13:24:57 Рис. 25.56. Окно Clock с добавленными элементами управления Если вы теперь попытаетесь запустить приложение и откроете окно Clock, то увиди- те, что часы идут как раньше, а два элемента управления показаны, но пока не функционируют. После того как вы выполните команду Project | Run, эксперт кода автоматически обновит код для окна Clock. Вы можете посмотреть код для события e_Create окна Clock и обнаружите, что, как только родительское окно было создано, эксперт кода добавил следующие два вызова для создания двух элементов управления: win_CreateControl(wc_PushButton,ret(73,29,193,59),"Start",_Win, [wsf_Group,wsf_TabStop],idc_startstop), win_CreateControl (wc_CheckBox,ret (45, 91,165,116), ''Show Date", Win, [wsf_Group, wsf_TabStop,wsf_Auto], idc_show_date) Когда вы хотите добавить какую-либо функциональность для элемента управления, нужно войти в эксперт окон и диалоговых окон и выбрать строку Control в списке Event Туре. Затем можно добавить события, чтобы управлять действиями элементов управления в окне (рис. 25.57). Чтобы добавить предложение для каждого элемента управления, используем кнопку Add Clause. Сначала добавим код для управления запуском/остановкой таймера. Чтобы сделать это проще в обработчике события, мы определим два маленьких вспомогательных предиката: startTimer и stopTimer, а также локальную базу данных clock: facts — clock timer(window,long Timerld) predicates startTimer(window) stopTimer(window) clauses startTimer(Win):- Timerld = timer_Set(Win, 1000), assert(timer(Win,Timerld)).
Гпава 25. Создание программы с графическим интерфейсом 687 stopTimer(Win) retract(timer(Win,Timerld)), i • r timer_Kill (TimerId) . Рис. 25.57. Добавление функциональности в элементы управления Теперь нужно изменить событие e__Create для окна Clock: □ удалите прежний код для запуска таймера; □ добавьте код, обеспечивающий ручной запуск/останов таймера при нажатии кнопки Start. Выберите предложение обработчика для кнопки (используйте кнопку Add Clause/Edit Clause) и добавьте следующий код для запуска/останова таймера: win_clock__eh (_Win, e_Control (idc_startstop,_,_CtrlWin,_CtlInfo) , 0) : - Title = win_GetText (_CtrlWin) , Title = "Start”, stopTimer(_Win), win_SetText(_CtrlWin,"Stop”), i win_clock_eh (_Win, e_Control (idc_startstop,__,__CtrlWin, _CtlInfo) ,0) , startTimer (_Win) , win_SetText(_CtrlWin,"Start"), ! ,
688 Часть V. Разработка графического интерфейса пользователя Код изменяет название кнопки (Start/Stop), чтобы показать, запускать таймер или останавливать. Вызов предиката win_GetText может возвратить надпись на кнопке, а вызов предиката win SetText — установить новый текст для кнопки. Замечание j Чтобы изменить текст, мы обращаемся к идентификатору кнопки Start (_Ctrlwin), а не к дескриптору окна Clock. Дескриптор этого окна передается в структуре события e_Control. В качестве нашего второго изменения мы хотели бы модифицировать окно Clock в зависимости от состояния флажка Show Date. Для этого мы используем предикат win isChecked, который, принимая дескриптор окна флажка, может возвращать бу- левское значение, определяющее, установлен флажок или нет. Однако в предложении события eJJpdate имеется маленькая проблема — у нас нет дескриптора к окну Show Date. Чтобы получить его, мы используем предикат win_GetCtrlHandle, который, принимая дескриптор родительского окна и идентифи- катор элемента управления (константа idc_show_date), возвращает дескриптор окна для элемента управления. % This must be the first e_Update clause win clock eh( Win,e Update( ),0):- %Note: no Cut ! _CtrlWin ~ win_GetCtlHandle(_Win, idc_show_date), IsChecked ~ win_IsChecked(_CtrlWin) , IsChecked = checkbox_offf i • He забудьте удалить первое отсечение (!) в приведенном выше предложении. Третьим изменением будет удаление первоначального вызова предиката timer set из предложения е_С reate: win__clock_eh (_Win, e_Create (_) , 0) : - !, _NewTimerId =timer_Set(_Win, 1000), % <- удалите эту строку Теперь нужно обеспечить, чтобы окно обновлялось немедленно после того, как бу- дет изменено состояние флажка. Поэтому, когда мы получаем событие для флажка, окно становится недействительным. win_clock_eh(_Win,e_Control(idc_show_date,_CtrlWin,_CtlInfo) ,0) :-!, win_Invalidate(_Win) _____Замечание j В приведенном выше примере мы воспользовались преимуществом специфическо- го свойства MS Windows. При первоначальном создании элемента управления по умолчанию устанавливается флажок Ctl Auto. Это означает, что флажок автомати- чески изменяет свое состояние, когда пользователь щелкает на нем мышью. Обратите внимание, что этот простейший код показывает только время. Чтобы уви- деть, как отображать дату и время, — проверьте код в файле Clock.pro.
Гпава 25. Создание программы с графическим интерфейсом 689 Использование списка Список — это особая форма часто используемого элемента управления. Список по- казывает набор строковых элементов, которые можно прокручивать для просмотра. Для управления списками имеется несколько предикатов VP1, имена которых начи- наются с префикса 1Ьох_. Описание списков дано в гл. 26 и в интерактивной справке. В этом примере мы попробуем сделать простой браузер каталогов на жестком диске. Допустим, при щелчке мышью на строке каталога нам хотелось бы отображать фай- лы и подкаталоги для этого каталога (рис. 25.58). ^CAWINDOWSXSYSTEMX 16H_640.DRV 16И3640.DRV 256_1280.DRV 256S1280.DRV 64K_1024.DRV 64KS1024.DRV <dir> 117824 116000 144320 121008 146352 119792 20:52:50 20.-52.-52 18:46:26 18:46:26 18:46:26 18:46:26 1994/ 1994/ 1994/ 1994/ 1994/ 1994/ 9/19 9/19 9/23 9/23 9/23 9/23 АВ.DLL ACCESS.CPL :ACHCMPRS.DLL ADDREG.EXE ADVAPI32.DLL AFVXD.VXD ; APPLE380.SPD ЛnnCTUПТ »MT 97600 1993/11/ 1 57344 9:50: 0 1995/ 7/11 12800 0: 0: 0 1994/ 4/ 5 14336 9:50: 0 1995/ 7/11 13824 9:50: 0 1995/ 7/11 25402 9:50: 0 1995/ 7/11 6046 9:50: 0 1995/ 7/11 Рис. 25.58. Полное окно каталогов Как обычно, при подготовке кода для окна Directory Browser выполните следующие шаги: 1. Создайте новый исходный модуль Dirlist.pro. 2. Добавьте пункт Directory List в меню Task подменю Test. 3. Создайте новое окно Directory. 4. Установите атрибут Maximized. 5. Обратитесь к эксперту окон и диалоговых окон и добавьте код по умолчанию для окна Directory в модуль Dirlist.pro. 6. Затем добавьте предложение для окна Task, которое при выборе команды Test | Directory List будет активизировать окно Directory. 7. Запустите программу и проверьте, создается ли окно при активизации Test | Directory List. Теперь зайдите в редактор окна и поместите список в окно Directory. Позиция и размер не имеют значения, поскольку мы изменим размеры управляющего элемента списка так, чтобы он соответствовал окну (рис. 25.59). Когда вы добавите список, убедитесь, что изменили заданный по умолчанию иден- тификатор на idc_listbox. Затем нужно нажать на кнопку Control Flags. Для эле- ментов управления имеется большое количество опций (рис. 25.60). В гл. 26 дается их объяснение. А пока пропустите все, что вам незнакомо.
690 Часть V. Разработка графического интерфейса пользователя рЭПЙагД |idc_ list box ListBox Attributes Control Size Width fioo Height 100 3 tfMMi ||UIT | ^шиммам Control Flagyl Cancel I H^lp «ифмммймиммммйж^^ 1 E Рис. 25.59. Добавление списка в окно Рис. 25.60. Выбор управляющих флажков для списка Так как мы хотим добавить табуляцию, разделяющую строки в нашем списке, и иметь возможность устанавливать столбцы табуляции, отметим флажок UseTabStops. Опция MultiSelect позволяет выбирать более одного пункта в списке, и Другая опция, которую вы можете изменить, — NoBorder, но мы раскроем список на все окно, поэтому граница не нужна. Код для браузера каталогов Перед тем как продолжить, добавим пару инструментальных предикатов. Первое, что может понадобиться, — это предикат, возвращающий имена файлов в каталоге. В этом примере будет удобно создать такой инструментальный предикат на основе встроенного предиката dirfiles: include "iodecl.con" domains
Гпава 25. Создание программы с графическим интерфейсом 691 stamp = s(integer Hour,integer Min,integer Sec,integer Year, integer Month,integer Day,long Size); dir predicates nondeterm get_files(string Path,string FileName,stamp) clauses get_files(PATH,SUBDIR,dir) concat(PATH,PP), dirfiles (PP, fa_subdir, SUBDIR,, SUBDIR X get_files(PATH,FName,s(Hour,Min,Sec,Year,Month,Day,Size)):- concat (PATH, PP) , dirfiles(PP,fa_normal,FName,_,Hour,Min,Sec,Year,Month,Day,Size). Затем мы создадим предикат, принимающий на вход идентификатор списка и путь (Path), который добавит имена файлов и подкаталогов в список. В этом предикате мы будем использовать два предиката VPI, которые работают со списками: предикат lbox_clear — удаляет все содержимое из списка и предикат lbox_add — добавляет строку на данную позицию в списке. Если позиция равна — 1, то элемент будет добавлен в конец списка. predicates fill_lbox(window, string path) fill_lbox(window, string FileName, stamp) clauses fill_lbox(Win,Path) lbox_Clear(Win), get_files(Path,FName, STAMP) , fill_lbox(Win,FName,STAMP), fail. fill_Ibox(_Win,_Path). fill—Ibox(Win,SubDir,dir) format(Str,"%\t%”,SubDir,"<dir>"), lbox_Add(Win, -1, Str). fill_lbox(Win,FName,s(Hour,Min,Sec,Year,Month, Day, Size) ) : - format(Str,"%\t%8\t%2:%2:%2\t%4/%2/%2", FName,Size,Hour,Min,Sec,Year,Month,Day), lbox_Add(Win, -1, Str) . Ну и если мы хотим иметь возможность изменить каталог (при щелчке на нем в списке), то понадобится еще один вспомогательный предикат. Для этой цели мы выделим каталог из строки этого элемента. predicates get—path(string Item,string Path,string Rest) clauses get—path(Item,Path,Rest):- searchchar(Item,'\t' , Pos) ,
692 Часть V. Разработка графического интерфейса пользователя Len = Pos -1, frontstr(Len,Item,Path, Rest). Теперь событие, обрабатывающее вызов предиката на создание списка (win_CreateControl), автоматически вставляется экспертом кода, который генериру- ет выбранный список значений флажка. Завершим начатое. Вызовем предикат lbox_SetTabstops для того, чтобы устано- вить позиции табуляции. Позиции табуляции определяются в базовых единицах Dialog Base Units. Далее изменим шрифт для списка (это делать необязательно). Если мы не хо- тим делать сложное форматирование, проще выбрать непропорциональный шрифт ff_fixed. Затем установим заголовок окна, показывающий путь к отображаемым файлам и подкаталогам (win_SetText), и заполним содержание списка (fill lbox). Добавим код к предложению e Create. Код должен быть добавлен после комментария %end DIRECTORY, ToolbarCreate: LBWIN = win_GetCtlHandle(_Win, idc_listbox), lbox_SetTabStops(LBWIN, [0,75,125,175,225]), Font=font_Create(ff—Fixed, [], 10), win_SetFont(LBWIN,Font), win_SetText(_Win,"C:\\”), fi11—Ibox(LBWIN,"C:\\") Добьемся теперь, чтобы список заполнял все окно. Это легко выполняется в собы- тии е__Size для родительского окна. Просто изменяем размеры элемента управления на такие же, что и у родительского окна, вызывая предикат win_Move: win_directory_eh(_Win,e_Size(_Width,_Height),0):-!, LBWin = win~GetCtlHandle(_Win, idc_listbox), win_Move(LBWin,ret(0,0,—Width,—Height)) На данном этапе осталось реализовать изменение каталога в списке щелчком мыши. Но оставим это читателю в качестве упражнения. (Проверить решение можно, срав- нив его с кодом в файле DirList.pro.) Создание диалогового окна До этого момента мы создавали только обычные окна. Однако большинство прило- жений содержит больше диалоговых окон, чем обычных окон. Диалоговые окна ис- пользуются как формы или как совокупности элементов управления. Диалоговые окна — это специализированные окна, в которых программе не нужно что-либо ри- совать или обновлять их содержимое. Элементы управления используются в качестве пользовательского интерфейса для ввода, вывода или модификации значений. Существует два вида диалоговых окон: немодальные и модальные. Немодальные диалоговые окна похожи на окна, которые мы создавали ранее. Вы можете откры-
Гпава 25. Создание программы с графическим интерфейсом 693 вать немодальное диалоговое окно и продолжать работать с другими окнами. Но когда вы создаете модальное диалоговое окно, система входит в специальный ре- жим, в котором нельзя работать с другими окнами или диалоговыми окнами (в пре- делах данного приложения) до тех пор, пока вы не закроете модальное диалоговое окно. Код для обработки диалогового окна может быть очень похож на код для обработки окна. Существует предикат для создания диалогового окна и предикат обработчика событий, который получает все события для этого диалогового окна. По событию е_Create вы можете инициализировать все элементы управления, а когда нажата кнопка ОК, вы можете получить все значения. Однако, в качестве одной из опций проекта, имеется также диалоговый пакет, кото- рый может автоматизировать большую часть тривиальной работы, связанной с диа- логовыми окнами. Используя этот пакет, можно инициализировать элементы управ- ления, возвращать значения после завершения и делать проверки правильности дан- ных. Диалоговый пакет поддерживается экспертом окон и диалоговых окон, поэтому для каждого элемента управления можно определить тип содержимого, требования проверки правильности и Пролог-переменные (для возвращаемых значений). Для того чтобы реализовать пример использования диалоговых окон, выполните следующие действия: 1. Создайте новый исходный модуль Dialogs. 2. Создайте дополнительный пункт Dialog в меню Task подменю Test. 3. Создайте диалоговое окно Person. 4. Вставьте следующие элементы управления в диалоговое окно: • поле редактирования: idc_name; • поле редактирования: idc_age; • зависимый переключатель: idc_male; • зависимый переключатель: idc_female; • независимый переключатель: idc_married; • список элементов: idc_skills — множественная и расширенная выборка (Multiselect+Extended). Здесь надо использовать визуальные компоненты: статический текст (static text) для написания заголовков перед элементами редактирования и списка элементов, рамка, которая окружает кнопки Male и Female. Скомпонуйте ваше диалоговое окно следующим образом (рис. 25.61). Затем вызовите эксперта кода для диалогового окна, выберите исходный модуль dialogs.pro и выберите стиль кода dialog_Create перед активизацией кнопки Default Code (рис. 25.62). Чтобы увидеть, как работает сгенерированный по умолчанию код, вам нужно доба- вить дополнительное предложение для события пункта меню Task окна Test | Dialog, вставить вызов для создания диалогового окна и запустить приложение.
694 Часть V. Разработка графического интерфейса пользователя Рис. 25.61. Компоновка диалогового окна $ Dialog and Window Expert 'jf ::::•_ j-*- йМйммммма IK Dialoqs pro Window e CloseReque^t 1 "..н.л.и. . vu- k |pj: <Йutomatfc; $ ouce Update л t. IГ- i*w«i W Event Handling t Type 4 E veritorl tern ii illlili |||»иЙ|ЦЙц4«Ц«><«>у«М** Pi£O\Sc^rceCodfe:.»T' t?Jji i WW""""" Module; <•.•. 4W.- • - I ^ Dialog or Window Selection ~~- Diaksg Cancel Scrollbar Control Mouse OwnDraw Misc e_Create e_ Destroy e_EndApplication e_EraseBackGround e_M ove e_N alive e_Updale n' '.^-l :J--rlSXo--,& •Sh'-lrW.jvCs 1 «• :':::-H: >ММШМ1Ч^^ :y' =^.a: -/:•, !• j ^b»ldgE®fe'O: ': у.-.: •нн- iHjS'*-. / J i ' i,' J1 Й. .!>••- * - vc'_ 0 • •••••• !''. ^rr- J^efaultCpdel *м««м<мтм^Мм^ '^;- ? Рис. 25.62. Добавление кода для диалогового окна Эксперт диалогового пакета Чтобы наше новое диалоговое окно нормально работало, снабдим его входными значениями и "попросим” возвращать набор значений после того, как оно будет за- крыто. Кроме того, для некоторых элементов управления можно устанавливать осо- бые параметры или выполнять необходимые правила верификации. Для того чтобы упростить задачу, вы можете использовать эксперт диалогового па- кета — инструмент, который отражает все опции, доступные в диалоговом пакете. В дополнение к определению различных опций этого пакета, вы можете также ис- пользовать его для того, чтобы давать имена Пролог-переменным, которые будут содержать входные значения и получать выходные значения.
Гпава 25. Создание программы с графическим интерфейсом 695 Самый простой способ вызвать эксперт диалогового пакета — выбрать имя диалого- вого окна в окне проекта, выбрать эксперт кода и затем нажать кнопку Dialog Pack. Экран эксперта диалогового пакета разделен на две части (рис. 25.63). Левая область окна всегда содержит список доступных элементов управления. Некоторые из них снабжены префиксом + (плюс): двойной щелчок на + отображает более детальную информацию. Правая сторона используется для отображения и изменения различ- ных настроек выбранного элемента управления. Рис. 25.53. Эксперт диалогового пакета. Поле имени Выберите элемент управления, который должен содержать имя человека idc_name — тип переменной должен быть строковый (string). Нажмите на значок + и, когда он изменится на —, выберите из раскрывшегося списка значение init value, щелкните на кнопке списка Setting method, затем выберите значение variable, чтобы указать, что начальное значение задается из переменной. В поле Variable (init) вы должны ввести имя переменной — Name. Ваше диалоговое окно теперь должно выглядеть так же, как на рис. 25.63. Точно так же для поля возраста Age вы определите, что (рис. 25.64): □ тип переменной — целый (integer); □ оно будет установлено посредством Variable; □ имя переменной — Age; □ метод настройки параметров — User specified; □ минимальное значение — 0; □ максимальное значение — 70. Элемент управления idc_married — это независимый переключатель; мы определя- ем, установлен ли он, по значению переменной Married (рис. 25.65).
696 Часть V. Разработка графического интерфейса пользователя Dialog Package Expert: <Petson> a Setting method %: ♦ idc_cancel ♦ idc_help ♦ idct_name ♦ idct_age idc_name ♦ Init value ♦ Properties ♦ Error Message ““ idc_age ♦ Init value ♦ Properties ♦ Error Message ♦ ide sex User specified Г* mandatory Г* default : !:: - < . - -fu.--?• ,ч. ... .. . .. , F/ mln max 70 := Ж йфмймшшм ; ?i.y ’ ?<' " «ймйшймймйиммышмйм Cancel Help •3' (AwiiMw* : 4 Рис. 25.64. Эксперт диалогового пакета. Поле возраста Рис. 25.65. Эксперт диалогового пакета. Поле семейного положения Следующий элемент управления — список навыков (рис. 25.66). Это список с воз- можностью множественного выбора, т. к. человек может обладать множеством раз- нообразных навыков. Пункты списка в окне списка всегда постоянны, а навыки человека выбраны (высвечены). В диалоговом окне Alternatives вы можете ввести список статических элементов в виде строк, разделенных запятыми. В диалоговом окне Init value вы можете опреде- лить, что переменная Skills будет возвращать список целых чисел (индексы выбран- ных навыков). Также в эксперте диалогового пакета нужно: □ определить, каким группам принадлежат те или иные зависимые переключатели (radio buttons);
Гпава 25. Создание программы с графическим интерфейсом 697 Dialog Package Expert: <Person> ♦ Error НеззадЛ ♦ idct_age •"“ide age ► Properties k Error HessaQ ide sex ide married ide skills ; Alternatives Init value RB groups С о ntro Ity р е; wcJJ3 ох Item Hst: 1W1' iroloi pascal database word excel . z* -3- УГП- Л i d c t к n о w 1 e de - '<4 Г ' • ' ' 4 Z. J 4zil * • 'z-M ‘r . ?'! v ,! ' 'zfli’r’S '' * <” -S 7 дмIЛ.J .. . .... ......«йшаафйМммашАфайямЫЙммМНМ1Й1!а;|ффнМ«Ш*!*Й^ Item number: |8К1ГГ2®Г1,ЭД1 ймш YK Рис. 25.66. Эксперт диалогового пакета. Список навыков Рис. 25.67. Эксперт диалогового пакета. Группа зависимых переключателей П дать имя переменной для каждой группы так, чтобы можно было произвести инициализацию и возвратить заданные значения. Новые группы переключателей создаются при нажатии кнопки New; кнопки Add и Del используются, чтобы добавлять (удалять) зависимые переключатели в группы (рис. 25.67). Сгенерированный код Теперь посмотрим на сгенерированный код и разберемся, как он работает. dlg_person_Create(Parent) NAME -
698 Часть V. Разработка графического интерфейса пользователя AGE = void, MARRIED = b_false, SKILLS = [1,3,5], SEX = idc_male, %MARK Person, new variables dialog_CreateModal (Parent, [resdlg(dlg_person_ResID,wd_Modal) ], %BEGIN Person, ControlList, 16:10:55-23.11.2000, Auto updated! df(idc_name,editstr(NAME,[]),nopr), df(idc_age,editint(AGE,[range(0,70)]),nopr), df(idc_married,checkbox(MARRIED),nopr) , df(idc_skills,listbox(["prolog”,"c","c++","pascal”, "database","word","excel","basic"],SKILLS),nopr), df(SEX,radiobuttongroup([idc_male,idc_female]),nopr) %END Person, ControlList ], dlg_person_eh, 0, VALLIST, ANSWER) , dlg_person_handle_answer(ANSWER,VALLIST) * dlg_person_handle_answer(idc_ok,VALLIST):-!, dlg_person_update(VALLIST). dlg_person_handle_answer(idc_cancel, _):- !. %Handle Esc and Cancel here dlg_person_handle_answer (_,__) :~ errorexit () . dlg_person_update(_VALLIST):- %BEGIN Person, Update controls, 09:07:56-20.6.1999, Auto updated! _NAME = dialog_VLGetstr(idc_name,_VALLIST), _AGE = dialog_VLGetint(idC—age,_VALLIST), —MARRIED - dialog_VLGetCheck(idc_married,—VALLIST), dialog_VLGetListBox(idc_skills,_VALLIST, —IDC—LISTBOX—ITEMLIST,—SKILLS), —SEX - dialog_VLGetRadiobutton(idc_male,—VALLIST), %END Person, Update controls true. %MARK Person, new events dlg_person_eh(_,_,_):- I, fail. %END_DLG Person Вам следует знать, что: □ когда эксперт диалогового пакета добавит новые переменные, он добавит и на- стройки по умолчанию перед точкой ввода %mark. Обратите внимание: эти ини- циализационные переменные позже не будут изменяться. Это означает, что вы столкнетесь с ошибками компилятора, если измените тип элементов управления в эксперте диалогового пакета или удалите их, и т. д. Если вы делаете изменения,
Гпава 25. Создание программы с графическим интерфейсом 699 то именно вы должны обеспечить переменной такое связывание, чтобы она мог- ла получить надлежащее значение; □ после закрытия диалогового окна будет вызван предикат dlg_person_update, ко- торый возвращает результирующие значения из списка. Вам нужно изменить этот предикат, чтобы обращаться только к тем значениям, которые вам фактиче- ски необходимы. Итак, мы изменим сгенерированный предикат dlg_person_Create, чтобы он прини- мал специфические входные параметры для диалогового окна и возвращал специфи- ческие параметры вывода. Теперь изменим наш источник для того, чтобы поставлять входные значения диало- говому окну, получать измененные выходные значения и хранить новые значения. С этой целью объявим факт employee, который может хранить информацию о чело- веке. В этом случае мы не изменяем глобальный предикат dlg_person_Create, а соз- даем локальный предикат my_person_create, который обладает необходимой функ- циональностью с входными и выходными значениями для диалогового окна. И последнее: изменим предикат dlg_person_update так, чтобы возвращались изме- ненные значения: domains person = person(string Name,dialog_int Age,boolean Married, ilist Skills,dialog_control~id Sex) facts — employes employee(person) clauses % Initial value employee(person("",void,b_false,[], idc_male)). predicates dlg_person_eh : EHANDLER dlg_person_update (dialog__val_list, ctlid, person) my_person_Create(window Parent,person In, person Out) clauses dlg_person_Create(Parent) retract(employe(Person) ) , i - / my_person_Create(Parent,Person,NewPerson), assert(employe(NewPerson)). my_person_Create(Parent,person(Name,Age,Married,Skills,Sex), NewPerson):- %MARK Person, new variables dialog_Create(Parent,[resdlg(dlg_person_ResID,wd_Modal)], %BEGIN Person, ControlList, 09:07:56-20.6.2000, Auto updated! df(idc_name,edit st r(NAME, []),nopr), df(idc_age,editint(AGE,[range(0,70)]),nopr), df(idc_married,checkbox(MARRIED),nopr),
700 Часть V. Разработка графического интерфейса пользователя df(idc_skills,listbox(["prolog","с","c++","pascal", "database","word","excel","basic"],SKILLS),nopr), df (SEX, radiobuttongroup ( [idC—male, idC—female] ) , nopr) %END Person, ControlList ], dlg_person—eh,0,_VALLIST,—ANSWER) , dlg__person_update (_VALLIST,_ANSWER, NewPerson) . dlg_person_update(_VALLIST,idc_ok, person(—Name,—Age,—Married,—Skills,—Sex)) %BEGIN Person, Update controls, 09:07:56-20.6.1995, Auto updated) —NAME = dialog_VLGetstr(idC—name,—VALLIST), —AGE = dialog^VLGetint(idC—age,_VALLIST), —MARRIED = dialog_VLGetCheck(idC—married,_VALLIST), dialog_VLGetListBox(idC—skills,—V7VLLIST,_ITEMLIST,—SKILLS) , —SEX ~ dialog_VLGetRadiobutton(idC—male,_VALLIST), %END Person, Update controls true. %MARK Person, new events Несмотря на то, что мы изменяли параметры и структуру сгенерированного кода, сохраняя комментарии %begin/%end вокруг автоматически модифицированного кода, мы можем продолжать модифицировать этот код, используя эксперт диалогового пакета. Динамический обмен данными Visual Prolog поддерживает динамический обмен данными (DDE, Dynamic Data Exchange), поэтому ваши программы могут легко связываться с другими приложе- ниями. Прежде чем использовать предикаты DDE, система DDE должна быть ини- циализирована. Это лучше всего выполнять в событии e_Create для окна Task: task_win_eh(_Win,e_Create(_),0):-!, dde_Init([ddef_clientonly]), Небольшой пример использования DDE — послать команду в MS Word, чтобы за- грузить новый файл. Перед тем как посылать команду в MS Word, мы должны уста- новить соединение DDE. Это делается вызовом предиката dde Connect, который возвращает идентификатор соединения. Предикат dde_Execute затем может послать текстовую команду Command этому соединению, после чего мы снова прерываем со- единение. task—win_eh (_Win, e_Menu(id—Test—dde—test,_ShiftCtlAlt) ,0) , trap(Conv = dde_Connect("WinWord","System"), write("WinWord not running\n")), filenamepath(FullName,\\MYPROJ.PRO"), format(Command,"[FileOpen .Name=\"%\"FullName),
Гпава 25. Создание программы с графическим интерфейсом 701 write("DDE Command="tCommand)fnl, dde_Execute(Conv,Command,0), dde_DisConnect(Conv). В этом случае MS Word посылается команда: [FileOpen .Name =* "C:\TEMP\TST\MYPROJ.PRO"]. При использовании DDE в каждом случае вы должны посмотреть документацию для программы-сервера, чтобы знать, какие команды может принимать программа. Здесь нужно упомянуть, что: □ DDE не переносим на отличные от Windows платформы; '□ DDE на платформе Windows имеет широкие возможности и может работать в сети.
ГЛАВА 26 Средства создания графического интерфейса Визуальный интерфейс программирования VPI (Visual Programming Interface) — вы- сокоуровневый программный интерфейс, разработанный для облегчения создания программ на Visual Prolog, способный поддерживать сложные интерфейсы, исполь- зующие графические возможности современных операционных систем. Окна, меню, диалоговые окна, элементы управления, перья, кисти, курсоры, рисунки и т. д. ~ все эти ресурсы и инструментальные средства входят в состав Visual Prolog как про- стые структуры. VPI делает возможным создание переносимого исходного кода, который может быть откомпилирован, чтобы работать в 16-разрядной конфигурации под MS Windows 3.1х, 32-разрядной — под Windows 95/98/МЕ, Windows NT/2000, под администратором представлений OS/2. Управляемые событиями приложения В большинстве приложений текстового режима MS-DOS последовательность дейст- вий определяется разработчиком во время выполнения программы. Такие DOS- приложения, имея очень небольшое взаимодействие с другими приложениями и отсутствие вмешательства операционной системы, часто основаны на статических принципах проектирования. Современные же GUI-системы (Graphical User Interface, графический интерфейс пользователя) организованы намного динамичнее. Операционная система, кроме обеспечения уровня сервиса, активно вовлечена в процесс выполнения всех прило- жений. Приложение взаимодействует с пользователем косвенно через операционную систему. Когда пользователь делает что-либо (нажимает клавишу, перемещает мышь, щелкает на пиктограмме и т. д.), интерфейс аппаратного обеспечения генерирует событие, операционная система переводит это событие в стандартизированный формат сообщения и посылает уведомительное сообщение вызвавшему его элементу. Обычно это окно, которое является активным. События могут достигать приложения асинхронно, поэтому приложение должно быть написано так, чтобы обеспечить целостность всех данных и процессов незави- симо от последовательности событий. Приложения, основанные на графическом
Гпава 26. Средства создания графического интерфейса 703 интерфейсе пользователя, должны реагировать на свое окружение и не могут имити- ровать стандартное поведение приложений DOS. Приложения позади неактивных окон могут оставаться активными, могут писать или рисовать в своих неактивных окнах, и все это будет отображаться, если окно не закрыто или не перекрыто дру- гим. Приложение может в любое время активизировать одно из своих окон, чтобы запросить входные данные. Активное на этот момент окно становится неактивным, но его приложение продолжает работать. Вообще, во время работы могут возникать самые разные ситуации, например, поль- зователь может получить срочный телефонный звонок. На него можно ответить, выбрав системную кнопку окна Task или меню файла и закрыв приложение. Опера- ционная система сообщает приложению о событии, посылая сообщение обработчи- ку событий окна Task. В большинстве многозадачных системах операционная сис- тема может приостановить ваше приложение в любое время. Приложение никогда не будет знать, запрашивал ли пользователь закрытие или это операционная система обнаружила сбой питания. Или если приложение хочет закрыть какое-то окно, то оно посылает этому окну запрос на закрытие closeRequest точно так же, как поль- зователь, когда он выбирает команду Close в меню окна Task. Конечно, вы можете уменьшить количество возможных событий и сообщений, бло- кируя пункты меню и кнопки управления. Часть обработки сообщений GUI- приложений может быть очень сложной, но это — цена за большую гибкость. Про- лог и VPI существенно упрощают работу программиста по связыванию приложения с GUI операционной системы. Эксперт кода и браузер (The Code Expert and the Browser) облегчают поиск и обработку кода, который соответствует определенному событию, отвечая на определенное сообщение, и вы редко будете иметь дело с запу- танными деталями основной логики передачи сообщений. Запуск VPI Запуск VPI-приложения начинается с выполнения секции GOAL (цель) Пролог- программ ы. Основная функция GOAL — вызвать предикат vpi_init, который запус- тит систему управления окнами и создаст одно главное окно приложения, которое мы будем называть Task-окном. Единственный код, который может предшествовать вызову предиката vpi_init, — это определение параметров предиката vpi init. Эти же параметры затем могут быть переустановлены при помощи предиката vpi_SetAttrVal. Эксперт приложений создаст раздел GOAL со всеми атрибутами и флагами, уста- новленными вами в диалоговом окне Application Expert. Любые другие действия по инициализации приложения должны быть выполнены в обработчике события e_Create, посылаемого окну Task. Все действия по завершению приложения должны быть выполнены в обработчике события e^Destroy. goal vpi Init(WSFlags, EventHandler, Menu, AppName, TaskWinTitle). Обратите внимание, что предикат vpi_init закончит свою работу только после того, как пользователь закроет созданное окно Task.
704 Часть V. Разработка графического интерфейса пользователя wsriags — это список флагов стиля для окна Task. Они определяют, должно ли окно быть свернуто, развернуто и т. д. Menu принадлежит домену menu (res_ menu(resource_id) или no_menu). EventHandler— это предикат домена EHANDLER, который будет получать и обрабатывать события для окна Task. AppName использует- ся для идентификации приложения. И наконец, TaskWinTitle будет являться заго- ловком окна Task. Эти флаги более подробно описаны далее (см. разд. "Создание окон" данной главы). Окна Окно представляет собой прямоугольное пространство на экране. Окно используется приложением для вывода и ввода данных. Все пространство экрана рассматривается как специальное "экранное окно". Каждое окно разделяет экран с другими окнами, включая окна иных приложений, а иногда и операционной системы. Эти окна могут перекрывать друг друга. Независимо от того, сколько приложений одновременно запущено, в любой момент времени активизировано лишь одно окно, т. е. только это окно может получать входные данные от пользователя. Активное окно всегда находится поверх остальных окон. Для взаимодействия с этим окном (а следовательно, и с основным приложе- нием) пользователь может использовать мышь, клавиатуру или другие устройства ввода данных. Окна — это единственный инструмент приложения для взаимодейст- вия с пользователем, поэтому одними из первых функций каждого приложения яв- ляются функции создания и отображения окна. Первое созданное окно каждого приложения называется окном Task и представляет приложение в целом, являясь приемником сообщений, предназначенных для всего приложения. Это окно может быть скрытым (невидимым). Все предикаты динамического создания окон формируют структуру в памяти, затем они отображают окно согласно этой управляющей структуре и присваивают окну дескриптор, который является уникальным номером. Дескрипторы присваиваются всем окнам, как видимым, так и скрытым. Почти все остальные VPI-предикаты в качестве входного параметра требуют дескриптор окна для доступа к нему. Загру- жаемые и готовые для использования ресурсы всех видов также имеют идентифика- торы или дескрипторы, с помощью которых к ним можно обращаться. Каждое окно или диалоговое окно в приложении представлены не только структу- рой памяти, но также и предикатом обработчика событий окна, которому VPI посы- лает сообщения при возникновении пользовательских или системных событий, свя- занных с окном. Причем эти сообщения не обязательно синхронизированы с при- ложением. Приложение также может посылать события любому из своих обработчиков событий. В VPI названия событий из домена событий всегда имеют префикс е__. Типы окон Окна классифицируются по типам. Типы окон должны принадлежать домену windowtype и иметь префиксы w_ (для окон), wc_ (для элементов управления) или wd_ (для диалоговых окон). Тип окна может быть получен по дескриптору окна вы- зовом предиката win_GetType. Возможные типы окон перечислены в табл. 26.1.
Глава 26. Средства создания графического интерфейса 705 Таблица 26. /. Типы окон Тип окна Значение w_TopLevel Обычное окно документа w_Chiid Дочернее окно w Task Окно Task w_Screen Окно экрана Screen wd__Modal Модальное диалоговое окно wd ModeLess Немодальное диалоговое окно wc_PushButton Командная кнопка wc_RadioButton Переключатель wc CheckBox Флажок wc__HScroll wc_VScroll wc_Edit wc_Text wc LBox WC—LBoxButton wc_LBoxEdit wc_GroupBox wc_Icon wc_Custom w_Print w_Picture w_Metafile Горизонтальная полоса прокрутки Вертикальная полоса прокрутки Поле редактирования Статический текст Список Раскрывающийся список Поле редактирования с раскрывающимся списком Групповой блок Пиктограмма Специальные (определяемые пользователем) элементы управления Внутреннее окно, используемое во время печати Внутреннее окно, используемое во время создания изображений Внутреннее окно, используемое во время создания метафайлов Стили окон При создании окна или диалогового окна можно установить различные флаги для определения его внешнего вида, границ, существования полос прокрутки, возмож- ности разворачивать окно на весь экран и т. д. Для создания окон с определенными свойствами можно использовать редактор окон и диалоговых окон (Window and Dia- log editor). Все флаги имеют префикс wsf_. После того как вы создадите окно, флаги стиля окна могут быть получены вызовом предиката win_GetState. Некоторые флаги могут быть изменены вызовом предиката win_SetState. В табл. 26.2 перечислены все возможные значения флагов стилей.
706 Часть V. Разработка графического интepфeйcaJ1Oльзoвaтeля Таблица 26.2. Стили окон Флаг стиля Значение wsf_Border Окно получает тонкую границу wsf_SizeBorder Окно получает двойную границу, которая используется для из- менения размеров окна wsf_DlgBorder Окно получает границу, как у диалогового окна. Размер окна не может быть изменен wsf_Close wsf_Maximize wsf—Minimize wsf_HScroll wsf_VScroll wsf—TitleBar wsf—Visible wsf—Invisible wsf—Enabled Окно получает системное меню, которое обычно используется для закрытия окна Окно получает кнопку максимизации. Этот флажок не устанав- ливается, если размер окна не должен быть изменен Окно получает кнопку минимизации Окно получает горизонтальную полосу прокрутки Окно получает вертикальную полосу прокрутки Окно получает строку заголовка Окно изначально является видимым (значение по умолчанию) Окно должно быть создано невидимым Окно должно быть создано активизированным (задано по умол- чанию) wsf—Disabled wsf—Maximized wsf—Minimized wsf_Restored wsf—Clipchildren Окно должно быть создано неактивизированным Окно должно быть создано развернутым на весь экран Окно должно быть создано свернутым в пиктограмму Этот флажок используется для восстановления окна из развер- нутого или свернутого состояния Изображение за пределами дочерних окон должно быть отсече- но. Этот флажок нужно установить при создании родительского окна wsf—Clipsiblings Отсечение должно быть выполнено при перекрытии дочерних окон. Этот флажок может использоваться только для дочерних окон wsf—NoClipSiblings Сбрасывает флажок wsf—Clipsiblings wsf_Topmost Окно должно быть расположено поверх всех окон ws f_Т ranspa rent Окно должно быть прозрачным
Гпава 26. Средства создания графического интерфейса 707 Клиентская область окна Часть любого окна внутри любого заголовка, меню и полос прокрутки называется клиентской областью и является рабочей областью окна. Она доступна для элемен- тов управления, дочерних окон, расцветки, изображения текста и графики. Прямоугольные области всегда определяются RCT-структурой: rct(Left, Top, Right, Bottom). Внимание! VPI использует значения Right (Правый) и Bottom (Дно), но не Width (Ширина) и Height (Высота). Для большинства окон координаты выражены в пикселах. Но при работе с диалого- выми окнами и элементами управления размеры должны зависеть от размера шриф- та и типа используемого системного шрифта. При изменении системного шрифта изменится и размер текстового элемента управления, а диалоговое окно будет соот- ветственно масштабировано. Поэтому размер диалоговых окон измеряется в диало- говых базовых единицах (Dialog Base Units), чьи метрики зависят от системного шрифта. Окно экрана (Screen) Окно экрана (Screen Window) — абстракция, которая представляет весь экран. Окно Screen всегда является родителем окна Task. Окна верхнего уровня также могут иметь своим родителем окно Screen, которое позволяет перемещать их за пределами окна Task. Окно задачи (Task) Окно задачи (Task Window) — абстракция, которая представляет приложение. На платформах Windows и OS/2 оно может использоваться во многом подобно обычно- му окну. Окно Task — основное окно для приложения и создается с типом окна w_Task вызовом предиката vpi init. Вызов этого предиката генерируется экспертом кода как заключительный вызов в цели (goal) приложения. Инициализация и завершение приложения всегда связываются с окном Task. Ини- циализация должна выполняться в ответ на событие e_Create окна Task. Это собы- тие инициируется автоматически вызовом предиката vpi init. Очистка при завер- шении должна выполняться в ответ на событие е Destroy. Приложение завершается в результате вызова предиката win_Destroy, примененного к окну Task. Одним из способов инициализации является выбор пользователем команды File | Exit. Это действие инициирует событие, приводящее к вызову предиката win Destroy. Окно Task получает некоторые специальные события, которые не посылаются дру- гим окнам. Событие e_EndSession может быть получено от системы управления ок- нами для уведомления об ее закрытии, различные события динамического обмена данными (DDE, Dynamic Data Exchange) используются для связи с другими прило- жениями и также посылаются обработчику событий окна Task.
708 Часть V. Разработка графического интерфейса пользователя В режиме интерфейса с множеством документов (MDI, Multiple Document Interface) окно Task является контейнером для всех окон верхнего уровня, созданных прило- жением. Меню окна Task несет как свое собственное меню, так и меню всех актив- ных MDI-окон. Если приложение запущено в режиме MDI, то в окне Task рисовать невозможно. Окна верхнего уровня (Top-Level) Окно верхнего уровня (Top-Level) — это окно, у которого родительским окном явля- ется либо окно Screen, либо окно Task. Окна верхнего уровня создаются с типом окна w TopLevel. Эти окна часто содержат меню и обычно могут быть свернуты в пиктограмму или развернуты во весь экран. Они могут использоваться как кон- тейнерные окна и содержать дочерние окна или элементы управления. В режиме MD1 под Windows меню окон верхнего уровня будет отображаться в окне Task. Однако в режиме интерфейса с одним документом (SDI, Single Document Interface) любое окно верхнего уровня может объявить родительским окно Screen, что позволяет каждому окну верхнего уровня в приложении содержать свое собст- венное меню. Дочерние окна Дочерние окна (Child) всегда должны находиться внутри области их родительского окна. Дочерние окна создаются с типом окна w chiid. При закрытии, сворачивании, деактивизации, скрывании родительского окна подобные операции сначала приме- няются ко всем дочерним окнам и элементам управления и только затем к самому окну. Элементы управления Термин ’’элементы управления” применяется к окнам меньшего размера, имеющим специализированный вид и определенные функции, предоставляемые операционной системой. Они располагаются внутри других окон, главным образом как компонен- ты диалоговых окон. Элементы управления обеспечивают пользователя средствами для набора текста, установки свойств или вызова команд. Стандартные типы эле- ментов управления поддерживаются VP1, их названия начинаются с префикса wc~. При возникновении событий от элементов управления обработчику событий роди- тельского окна посылаются уведомительные сообщения. Диалоговые окна Диалоговые окна — это специальный вид окон. Их функциональные возможности ограничены по сравнению с обыкновенными окнами. Диалоговые окна обычно со- держат элементы управления, через которые происходит взаимодействие приложе- ния с пользователем (отображение выходной информации, ввод входных данных, выбор из множества свойств или редактирование). Операционная система позволяет перемещаться между элементами управления при помощи клавиши <ТаЬ> и комби-
Гпава 26. Средства создания графического интерфейса 709 нации клавиш <Shift>+<Tab>, передавая фокус от одного элемента управления к другому и обратно. Обработчики событий С каждым окном и диалоговым окном связан предикат, который обрабатывает все события для этого окна. Он объявляется принадлежащим домену ehandler и прини- мает два входных параметра: дескриптор окна и событие. Предикат возвращает па- раметр long, значение которого используется только в нескольких случаях. Типовой обработчик событий может выглядеть так: predicates mywin_event_handler : ehandler clauses mywin_event_handler(Window, e_Create(CreationData) , 0) Предикат обработчика событий для каждого окна должен быть объявлен и создан экспертом окон и диалоговых окон. Для событий, которые явно должны обрабаты- ваться вашим приложением, нужно самостоятельно написать код обработчика. Если предикат обработчика событий для этого события завершится неуспешно, то VP1 выполнит действия по умолчанию для соответствующего события. Если предикат выполнится успешно, VPI не будет производить никаких дополнительных действий. Например, событие e_CloseRequest будет послано после того, как пользователь два- жды щелкнет мышью по кнопке закрытия окна. Если никакое предложение не об- рабатывает это событие, то стандартный обработчик по умолчанию закроет окно. Если существует предложение, обрабатывающее это событие e_cioseRequest, то программист может решить оставить окно открытым. Для этого он должен обеспе- чить успешное завершение этого предложения предиката обработчика ошибок, или закрыть окно в результате его неуспешного завершения, или при помощи вызова предиката win_Destroy. Внимание! Если обработчик не обрабатывает событие или предложение обработчика для этого события завершается неуспешно, то VPI выполнит действие по умолчанию для со- ответствующего события. Если предложение обработчика выполнится успешно, VPI не будет производить никаких дополнительных действий. Возвращаемые значения обработчика событий Если значение возвращаемого параметра предиката обработчика событий не важно, оно должно быть установлено в нуль. Так обстоит дело для большинства событий. Однако существуют случаи, в которых оно имеет специальное значение, а именно: □ события e_Native, передающие возвращаемое значение к GUI операционной системы; □ чтобы передать возвращаемое значение в ответ на событие e_User, приходящее в результате вызова предиката win_SendEvent;
710 Часть V. Разработка графического интерфейса пользователя □ события, возвращающие размер в ответ на событие e_OwnerMeasureitem или воз- вращающие данные другому приложению в DDE-сессии. Создание окон В приложениях Visual Prolog создание окна обычно производится в коде, сгенериро- ванном экспертом окон и диалоговых окон. Для создания окон эксперт кода будет использовать вид окна и параметры, указанные в диалоговом окне Attribute. Создание окна, диалогового окна или элемента управления выполняется одним из предикатов VPI, имена этих предикатов начинаются с win_Create. Все они возвра- щают дескриптор созданного окна. Обратите внимание, что если созданное окно или диалоговое окно были модальными, то они уже будут закрыты, когда мы вер- немся из вызванного предиката, так что возвращенный дескриптор окна в этих слу- чаях не имеет смысла. При создании окна пользователь должен определить необходимую клиентскую об- ласть окна. Это координаты Тор (верх), Left (левый), Right (правый) и Bottom (низ) относительно клиентской области родительского окна. Вызов ни одного из VPI-предикатов для создания окон и диалоговых окон не за- вершится прежде, чем будет выполнено предложение для события e_Create, задан- ного по умолчанию обработчика. Код, который вызывает предикат win_Create, мо- жет применить этот механизм для косвенной передачи данных в обработчик собы- тий, используя данные непосредственно как данные для создания окна (Creation Data), если они являются числовыми значениями типа long. Если нет, то более сложные данные можно передать, используя приведение типов, чтобы сохранить указатель на данные, и передавая указатель как значение типа long. Если в обработчике нет предложения для события е Create или оно выполнится неуспешно, то будет вызван обработчик события e_Create по умолчанию — и в лю- бом случае окно будет создано и отображено. Если инициализация выполнилась неуспешно, то на такой случай ваша программа должна иметь второе предложение события e_Create, которое отследит эту ситуацию и сможет принять соответствую- щие меры. Эксперт кода не может генерировать двойные предложения событий — они должны быть скопированы и вставлены вручную. ( Замечания j • Не забудьте удалить ранее сгенерированное экспертом кода отсечение (!) из всех предложений события e_Create, кроме последнего. • Если окно уничтожается во время события e_Create вызовом предиката win_Destroy, предикат win_Create сгенерирует ошибку 6306. Создание обычного окна Предикат win_Create используется для создания как окон верхнего уровня, так и дочерних окон: Window = win_Create(Windowtype, Rot, Title, Menu, Parentwindow, WSFlags, EventHandler, CreationData)
Гпава 26, Средства создания графического интерфейса 711 Параметр Windowtype определяет вид окна. Параметр Ret определяет размер нового клиентского окна. Значение параметра Title будет отображено в строке заголовка окна (если окно имеет строку заголовка). Для окон верхнего уровня параметр Menu может использоваться для определения, какое меню должно иметь окно. Parentwindow содержит дескриптор родительского окна, которое для окон верхнего уровня должно быть окном Screen или окном Task, а для дочерних окон — окном верхнего уровня или другим дочерним окном, wsFlags — это список значений флагов, которые опре- деляют внешний вид и поведение окна. EventHandler определяет предикат, который должен быть вызван, когда приходит событие для окна. CreationData — значение long, которое будет передано обработчику событий как часть события e_Create. Создание диалогового окна Предикат win_CreateResDialog создаст модальное или немодальное диалоговое окно в зависимости от параметра Windowtype: Window == win_CreateResDialog (Parentwindow, Windowtype, Resld, Eventhandler, CreationData) Существует два вида диалоговых окон: немодальные и модальные. Модальное диало- говое окно должно быть закрыто прежде, чем пользователь сможет активизировать любое другое окно, меню или элемент управления в приложении. С другой стороны, любое число немодальных диалоговых окон может одновременно быть отображено на экране. Каждое из этих окон в любой момент может быть активизировано поль- зователем. Создание элемента управления Предикат win_CreateControl применяется для добавления нового элемента управле- ния к любому окну. На некоторых платформах невозможно добавить элемент управ- ления в диалоговое окно после его создания: Window = win_CreateControl(Windowtype, Ret, Title, Parentwindow, WinStyleFlags, CtrlId) windowtype используется для задания типа создаваемого элемента управления, win- styleFiags — это список параметров для определения поведения элемента управле- ния. ctrlld — константа целого типа, используемая для идентификации элемента управления внутри окна или диалогового окна. Динамическое создание окон Существует возможность определить окно или диалоговое окно вместе с элементами управления в отдельной структуре языка Пролог из домена windef list. Здесь мож- но указать информацию об элементах управления и шрифтах или определить, что вся информация должна быть получена из сегмента ресурсов. Дополнительно вы можете задать все координаты в диалоговых базовых единицах и сделать диалоговые окна независимыми от шрифта (листинг 26.1).
712 Часть V. Разработка графического интерфейса пользователя Листинг 26.1 global domains windef_list = windef* windef == resdlg(resid, Windowtype); dig(wdef, wsflags); dlg_font(wdef, string fontname, integer fontsize, wsflags); topwin(wdef, menu, wsflags); childwin(wdef, ctlid, wsflags); icon(wdef, ctlid, resid, wsflags); ctl(wdef, ctlid, wsflags); customctl(wdef, classname, ctlid, wsflags) wdef = wdef(Windowtype, ret, string, unit_type) unit_type = integer unit_type constants u_Pixels =0 % выбор масштаба в пикселах u_DlgPlatform =1 % масштаб в диалоговых базовых единицах платформы u_DlgBase =2 % масштаб в диалоговых базовых единицах Windows Окна и диалоговые окна могут быть построены динамически во время работы при- ложения из списка элементов управления. Этот способ предпочтительней, чем ис- пользование предварительно подготовленных ресурсов из сегмента ресурсов прило- жения. Первый элемент в списке должен определить окно, а остальные элементы — дочерние окна или элементы управления. Дескриптор созданного окна возвращается в любом случае. Чтобы построить и создать окно, используется такой предикат: Window = win_CreateDyn(Windef_list, Parentwindow, Ehandler, CreationData) Чтобы построить и создать диалоговое окно, используется следующий предикат: Window = win_CreateDynDialog(Parentwindow, Windef_list, Ehandler, CreationData) Чтобы создать множество элементов управления (которые могут или пока не могут быть отображены) в созданном окне или диалоговом окне, используется нижеприве- денный предикат: Window = win_CreateDynControl(Windef_list, Parentwindow) Этот предикат возвращает дескриптор первого из созданных элементов управления. Уничтожение окон Окно, диалоговое окно или элемент управления могут быть уничтожены вызовом предиката win_Destroy. После вызова этого предиката для конкретного окна обра- ботчик событий окна получит событие e Destroy. В обработчике этого события можно удалить связанные с окном факты, закрыть файлы и т. д.
Гпава 26. Средства создания графического интерфейса 713 При уничтожении окна автоматически уничтожаются его дочерние окна. Все они получат событие e_Destroy перед тем, как это же событие получит родительское окно. Пример кода для уничтожения окна показан в листинге 26.2. Листинг 26.2 «fri.i............................. ........ % Кнопка ОК закроет окно mywin__event_handler (Window, e_Control (idc_ok, CtlWindow, Ctllnfo), 0) !, win_Destroy(Window). % Выполнение очистки в событии e_Destroy mywin_event_handler(Window, e_Destroy, 0):- !, retractall(mywin_data(Window, _)). Доступ к клиентской области Функция win GetClientRect возвращает координаты клиентской области окна. win_GetClientRect заполняет структуру RCT координатами левого верхнего и нижне- го правого углов клиентской области, но координаты относятся к самой клиентской области (клиентские координаты). Это означает, что координаты левого верхнего угла клиентской области — всегда (0, 0), а координаты нижнего правого угла — ши- рина и высота клиентской области. ClientRCT = win_GetClientRect(Window) Область отсечения По умолчанию рисование происходит в пределах прямоугольной области, и все, что попадает за ее границы, отсекается (не выводится на экран), но существует возмож- ность явно установить область отсечения вызовом предиката win SetClip: my_event_handler(Window, e_Update(_Rct), 0):- !, win_SetClip(Window, ret(60, 60, 100, 100)), draw_Text(Window, 70, 70, "Hello, World"). Весь будущий вывод в данное окно будет усекаться к указанной прямоугольной об- ласти. Чтобы не допустить отсечение, устанавливают усечение к области, по крайней мере, такого же размера, как клиентская область. Текущая область отсечения для любого окна может быть получена при помощи сле- дующего предиката: ClipRct = win_GetClip (Window) В этом случае координаты области являются координатами клиентской области окна.
714 Часть V. Разработка графического интерфейса пользователя Доступ к внешней границе области Приложение может получить координаты внешней границы окна следующим об- разом: OuterRect = win_GetOuterRect (Window) Структура OuterRect заполняется координатами верхнего левого и нижнего правого углов окна. Для диалоговых окон и для окна Task возвращенные координаты явля- ются экранными, а для других окон — координатами относительно родительского окна. Вычисление клиентской области по внешней границе окна Если окно должно быть расположено на основе положения его внешней границы (например, при перемещении или изменении размеров), можно использовать преди- кат rect_GetClient, который преобразует необходимую внешнюю границу окна в клиентскую область. Стили окна должны быть входными данными. ClientRCT = rect_GetClient(WSFlags, boolean HasMenu, OuterRct) В ClientRCT структура RCT возвращает координаты клиентской области окна отно- сительно координат родительского окна. Для диалоговых окон и окна Task возвра- щаемая структура RCT связана с координатами экрана. Перемещение и изменение размеров окон После того как окно создано, приложение может изменять размер окна и его пози- цию вызовом предиката win_Move. В листинге 26.3 показано, как переместить окно на 10 пикселов вправо и вниз щелчком мыши. 1 Листинг 26.3 ♦ 7.Г...... .... .... . . • • • • : < •.-... .••.••••.- . • • • ' * ’ •••••• - • . • - • •• . в „.....................в....................к».........».. win_event_handler(Window, e_MouseUp(PNT, ShiftCtlAlt, Button), 0):- !, OuterRCT ~ win GetOuterRect (Window), WsFlags = win_GetState(Window), ClientRCT = rect_GetClient (WsFlags, const_HasMenu, OuterRCT), NewClient = rect_Offset(ClientRCT, 10, 10), win_Move(Window, NewClient). Обратите внимание, что нельзя просто использовать rect_Offset для прямоугольни- ка, возвращенного win GetclientRect, потому что этот прямоугольник имеет начало координат (0, 0). Изменение состояния окна При создании окно получает набор флагов стиля. Эти флаги определяют начальное состояние окна, а именно: является ли окно видимым, заблокированным или свер- нутым в пиктограмму. Возможные стили окна приведены ранее в табл. 26.2.
Гпава 26. Средства создания графического интерфейса 715 Состояние окна можно изменить вызовом предиката win_Setstate, а текущее со- стояние окна получить вызовом предиката win_Getstate. win_SetState(Window,wsflags WinStateList) Wsflags - win_GetState (Window) Блокировка и активизация окон Заблокированное окно не получает данных с клавиатуры или мыши, но оно может получать сообщения от других окон, от иных приложений и от операционной сис- темы Windows. Приложение обычно отключает такое окно, чтобы не допустить его использования пользователем. Например, приложение может отключить кнопку ОК в диалоговом окне, чтобы пользователь не мог нажать на нее, пока не будут введены требуемые данные в другом элементе управления. Приложение может активизиро- вать заблокированное окно в любое время (обычно активизируется при создании). Для создания заблокированного окна можно определить стиль wsf_Disabled. Когда дочернее окно заблокировано, Windows передает сообщения мыши от дочер- него окна родительскому, как будто нет никакого дочернего окна, покрывающего данную область. Для блокировки и активизации окон используются следующие предикаты: win_SetState (Window, [wsf__Disabled] ) win__SetState (Window, [wsf_Enabled] ) Скрывание и показывание окон Окно или элемент управления могут быть сделаны полностью невидимыми. В этом случае пользователю они будут недоступны, однако программа может работать с ни- ми как с видимыми. Диалоговые окна могут содержать невидимые элементы управ- ления, которые будут отображены после выполнения каких-либо действий. Чтобы скрыть или сделать видимыми окна, используйте следующие предикаты: win_SetState (Window,[wsf_Invisible]) win_SetState(Window,[wsf_Visible]) Сворачивание, разворачивание и восстановление окон Окно может быть свернуто, развернуто или восстановлено к его предыдущему раз- меру из первых двух состояний. Эти действия может выполнять пользователь, если окно создано с надлежащими флагами стиля, или их можно выполнить программно вызовом предиката win SetState. Чтобы свернуть, развернуть или восстановить окна, используйте следующие преди- каты: win_S etState (Window, [wsf__Minimized] ) win_SetState(Window,[wsf_Maximized]) win Setstate(Window,[wsf Restored])
716 Часть V. Разработка графического интерфейса пользователя Изменение пиктограммы, ассоциированной с окном Чтобы связать с окном пиктограмму, которая отображается в строке заголовка окна и в свернутом состоянии окна, используйте предикат: Win_SetIcon (Window, IconResourceldentifier) Изменение текста окна или заголовка окна Предикат win_SetText используется для изменения текста, отображаемого в тексто- вом поле или содержащегося в окне редактирования: win_SetText (Window, Title) Для окон, имеющих строку заголовка, этот предикат также используется для изме- нения текста заголовка окна. Изменение обработчика событий VPI позволяет динамически изменять предикат обработчика событий для окна. Это делается с использованием предиката: win_SetHandler (Window, Other_eve nt—handler) Все события для определенного окна впоследствии будут направляться предикату, переданному через переменную Other_event_handler. Это свойство используется в качестве примера в инструментальном пакете Tree, когда в уже созданное окно должно быть помещено дерево. Связь данных с окном Данные напрямую могут быть связаны с окном. Это свойство используется не часто, но оно позволяет получать и изменять значение типа long, относящееся к внутрен- ней управляющей структуре окна. Значение типа long в любое время может быть получено и изменено вызовом двух предикатов: Data = win_GetData (Window), win_SetData (Window, NewData) Доступ к окну Task Каждая выполняющаяся программа имеет окно Task. Дескриптор окна Task может быть получен вызовом предиката vpi_GetTaskWin. TaskWinHandle = vpi__GetTaskWin ()
Гпава 26. Средства создания графического интерфейса ... 7/7 Доступ к родительскому окну Каждое окно, кроме окна Screen, имеет родителя. Дескриптор родительского окна для данного окна может быть получен вызовом win_GetParent. Parentwin =• win_GetParent (Childwindow) Доступ к активному окну Активное (приоритетное) окно является окном верхнего уровня или окном Task, которое или имеет фокус, или содержит элемент управления, или дочернее окно, которое имеет фокус. Дескриптор активного окна может быть получен при вызове следующего предиката: Window = win_GetActiveWindow () Установка фокуса В любой момент времени только один элемент управления или окно имеет фокус ввода. Изменить фокус пользователь может, щелкнув по другому элементу управле- ния. Изменить фокус может и приложение вызовом предиката: win_SetFocus (NewWindow) Доступ к окну, имеющему фокус Вызов следующего предиката позволяет получить дескриптор окна, имеющего фокус ввода: Window = winjSetFocus () Этот предикат также используется для определения того, какой элемент управления имеет фокус в данный момент. Упорядочивание окон Иногда нужно расположить окно поверх остальных окон без изменения фокуса. Это может быть выполнено при помощи следующего предиката: win_BringToTop (Window) Обновление окон Приложение всегда должно быть готово перерисовать заданную область окна в ответ на событие e_Update(Ret). Если окно было изменено, восстановлено, частично или полностью объявлено недостоверным, а также, если перекрывающее окно было полностью или частично удалено, произойдет событие e_Update (Ret). Для простоты приложение может перерисовывать все окно, а система управления окнами автома- тически отсечет рисунок до той области, где необходимо обновление.
718 Часть V. Разработка графического интерфейса пользователя Так как приложение должно восстанавливать содержимое окна на событие ejjpdate, очень важно организовать приложение так, чтобы все перерисовки окна выполня- лись именно здесь. Это локализует все перерисовки окна в одном месте в коде. Рассмотрим несколько предикатов, которые используются для управления обновле- нием окна. Предикаты также могут применяться и для оптимизации изображения, чтобы минимизировать объем перерисовываемой области. Недостоверное окно winJCnvalidate (Window) Предикат win_Invalidate — стандартный способ для объявления окна недостовер- ным и требующим перерисовки. В этом случае обработчику событий окна будет по- слано событие ejjpdate. winjnvalidate (Window, Ret) Этот предикат может использоваться для определения того, какая часть окна нужда- ется в перерисовке. Сделать достоверной часть окна win_ValidateRect (Window, Ret) Этот предикат используется для того, чтобы сообщить системе управления окнами, что указанной части окна не требуется перерисовка. Определение необходимости перерисовки части окна win_NeedsUpdate (Window, Rot) Предикат win_Nee ds Update иногда используется для ускорения перерисовки окна или печати. Перед отображением области, которая требует длительных вычислений, просмотра базы данных или сложного изображения, можно использовать этот пре- дикат, чтобы проверить, должна ли обозначенная область действительно быть пере- рисована. В то время как событие ejjpdate определит одну область (соответствую- щую, скажем, области, только что открытой другим окном), коду обновления может потребоваться сегментировать изображение областями, заданными основными структурами данных. Принудительная перерисовка окна winJJpdate (Window) Предикат winJJpdate инициирует любые события обновления, которые могут нахо- диться в очереди событий, и гарантирует, что окно будет полностью перерисовано. Например, это может быть необходимо перед прокруткой части окна.
Гпава 26. Средства создания графического интерфейса 719 События События, происходящие в системе VPI, вызывают формирование структуры данных из домена event в соответствии с типом события. Эти структуры посылаются обра- ботчику событий для окна и будут являться вторым аргументом в обработчике собы- тий. Далее будут представлены описания структур домена событий и рекомендуемые действия обработчика события для каждого из них. В тех случаях, когда нет предло- жения для обработки события, выполняется обработка по умолчанию. Для изучения событий, посылаемых в различных ситуациях, нужно запустить при- мер в подкаталоге <CDROM>.\RUN\VPI\EXAMPLES\EVENTS. Создание окон е_Create(long CreationData) Это первое событие, которое получает любое окно или диалоговое окно. Оно прихо- дит перед отображением, но после внутреннего создания окна. Здесь необходимо выполнять все действия по инициализации (инициализация баз данных, активиза- ция, блокировка, инициализация элементов управления). Вы увидите, что эксперты кода в VPI размещают код для создания элементов управления, дочерних окон, па- нелей инструментов и справки так, чтобы все было создано прежде, чем окно станет видимым. Предикат, который создает окно, не будет завершен до тех пор, пока не будет завершено предложение события e Create. Часто бывает необходимо передать специфическую для отдельного экземпляра ин- формацию от предиката создания к созданному экземпляру окна. Если диалоговое окно используется более чем в одном месте приложения, полезно было бы приме- нить новый или изменяемый заголовок. Это можно сделать только в обработчике события e_Create, т. к. окно и его дескриптор еще не существуют. Окно пока не отображено, и изменения могут быть произведены незаметно. Эти действия можно выполнить, передав значение long параметра CreationData предикату создания окна; затем это значение передается в событие e Create. При- мер кода приведен в листинге 26.4. » * ’ »< *•’ • »>. u»м*»*7 е»* I Листинг 26.4 dlg_criterion_Create(Taskwindow, Menuitem, Caption) CreationVal ~ criterion—context(Taskwindow, Menuitem, Caption), LongVal - cast(long, CreationVal), win_CreateResDialog(Windowtype, Resource_id, event—handler, LongVal). event_handler(Window, e_Create(LongVal), 0):- CreationVal = cast(criterion—context, LongVal), CreationVal = criterion—context(TaskW, Menuitem, Caption)), Обратите внимание на то, что в обработчике события e Create для окна Task вы- полняется инициализация приложения в целом.
720 Часть V. Разработка графического интерфейса пользователя Удаление окна e_Destroy() Это событие позволяет выполнить заключительное удаление всех данных, опреде- ленных для окна. Событие e_Destroy является последним событием, которое обра- батывает окно. Ваш код имеет доступ ко всем элементам управления и дочерним окнам (и их содержанию), даже если они не отображены на экране. Окно всегда будет получать событие e_Destroy, независимо от того, было ли оно уничтожено вызовом предиката win Destroy или было уничтожено его родительское окно. Закрытие окна пользователем encloseRequest() Это событие сообщает, что пользователь дважды нажал кнопку системного меню окна или выбрал команду Close из системного меню. Если вы не обрабатываете это событие, то по умолчанию окно будет уничтожено без выполнения каких-либо за- вершающих операций приложения. Если предикат обработчика событий завершится успешно для этого события, то будет выполнено автоматическое закрытие окна. Примерами обработки события е_С1 oseRequest могут служить: выполнение заклю- чительных действий и/или требование подтверждения перед вызовом предиката win_Destroy(). Закрытие графического пользовательского интерфейса e_EndSession (AbortPossible) При получении данного события оболочка графического пользовательского интер- фейса должна быть закрыта. Это событие может быть вызвано пользователем или получено каким-либо иным способом, например, автоматически при сбое питания. В этом случае операционная система должна иметь некоторое время для завершения работы приложения благодаря системе бесперебойного питания (UPS, Uninter- ruptible Power Supply); это сообщение порождается операционной системой и посы- лается всем выполняющимся приложениям. Событие посылается обработчику события окна Task. Предложение, обрабатываю- щее событие e_EndSession, в предикате обработчика должно завершиться неуспеш- но, если вы удовлетворите запрос на закрытие, или успешно — в противном случае. Обычное действие, выполняемое для события e_EndSession, должно сохранять лю- бые изменяемые данные. Возвращаемый параметр, установленный в ЬТгие, в приведенном ниже примере является бессмысленным.
Гпава 26. Средства создания графического интерфейса 721 my_window_event_handleг(Window, e_EndSession(b_True), b_True)!, % Если этот код успешно выполнится, то система не закроется. % Этот предикат должен выполниться неуспешно, чтобы запрос % на закрытие системы был удовлетворен. Активизация меню e_InitMenu () Событие e_lnitMenu посылается тогда, когда меню должно быть активизировано. Это происходит, когда пользователь щелкает кнопкой мыши на пункте меню или нажимает кнопку меню, что позволяет приложению изменять выпадающее меню до того, как оно будет отображено на экране. Выбор пункта меню e_Menu (Menu_tag, integer ShiftCtrlAlt) Когда пользователь выбирает какой-либо пункт меню, приложению посылается со- бытие e Menu. Параметр Menu tag — это идентификатор константы, которая опреде- ляет выбранный пункт меню. Параметр ShiftControlAlt содержит состояние кла- виш <Shift> и <Ctrl>, которые, возможно, были нажаты во время активизации пунк- та меню. task_win_event_handler(Window, e_Menu(id_file_exit, ShiftCtrlAlt), 0):-!, win_Destroy(Window) Событие e Menu посылается только окнам, содержащим меню. Если окно создано без меню, оно не будет получать событие e_Menu. На некоторых платформах имеют- ся различные ограничения относительно меню для диалоговых или дочерних окон. Командные кнопки панели инструментов также посылают события e Menu обработ- чику событий окна. Часто окна должны передавать неизвестные события окну Task для обработки по умолчанию обработчиком Task окна, например при работе с файлами, справочной системой и т. д. Код для этих действий автоматически вставляется экспертом кода для обработки событий e_Menu по умолчанию: my_event_handler(W, e_Menu(ID, ShiftCtrlAlt), 0):- !, PW = win_GetParent(w), win_SendEvent(PW, e__Menu(ID, ShiftCtrlAlt)). События от мыши e MouseDown (Pnt, ShiftCtrlAlt, Button) Это событие генерируется при нажатии кнопки мыши в клиентской области окна.
722 Часть V. Разработка графического интерфейса пользователя Параметр Button идентифицирует нажатую кнопку. В файле VPI.CON определены следующие константы: mouse_button_left, mouse_button_right, mouse_button_ middle. Внимание! ^^MMIMIMI^HpJ VPI использует термины "левая кнопка" и "правая кнопка" для простоты. Если мышь настроена для левой руки, то кнопки зеркально отображены. Это учитывается на уровне операционной системы, поэтому левая кнопка является первичной кнопкой мыши, правая — вторичной. Параметр ShiftctrlAlt содержит состояние клавиш <Shift>, <Ctrl> и <Alt>. В файле VPI.CON определены следующие константы: clothing, c_Shift, c_Control, c_Alt, C_ShiftCtl, C_ShiftAlt, c_CtlAlt, c_ShiftCtlAlt. Структура Pnt содержит позицию в клиентском окне, в которой была нажата кнопка мыши. Обычно в ответ на событие e_MouseDown никакие действия не выполняются. Это событие используется для подготовки операции, которая должна реагировать на со- бытие e_MouseMove до тех пор, пока не произойдет событие e_MouseUp. Чтобы быть уверенным в том, что событие e_MouseUp будет получено, вы должны следить за тем, чтобы мышь не '‘выходила" за пределы окна до отпускания кнопки мыши, т. к. ина- че событие e_MouseUp не будет послано обработчику событий окна. Если щелкнуть мышью на кнопке, меню, границах и т. д., генерируются другие управляющие события, которые не являются событиями мыши. Двойной щелчок кнопкой мыши e_MouseDbl (Pnt, ShiftControlAlt, Button) Это событие генерируется при двойном нажатии кнопки мыши в клиентской облас- ти окна. Событие e_MouseDbl не препятствует посылке составляющих его сообщений e_MouseDown и e_MouseUp. Перемещение мыши в окне e_MouseMove (Pnt, ShiftctrlAlt, ButtonsList) Это событие генерируется всякий раз, когда мышь перемещается внутри окна. Па- раметр Pnt содержит новое положение курсора мыши. Во время перемещения мыши будет генерироваться поток таких событий. Событие e_MouseMove может предназначаться для различных целей. Например, это событие может быть использовано для обработки перетаскивания мышью различных элементов (блок текста в текстовом редакторе Visual Prolog). Другое применение — "резиновая нить" для маркировки области — реализовано в редакторе диалоговых окон Visual Prolog.
Гпава 26. Средства создания графического интерфейса 723 Отпускание кнопки мыши e_MouseUp (Pnt, ShiftCtrlAlt, Button) Это событие генерируется всякий раз при отпускании кнопки мыши в клиентской области окна. Внимание! Событию e_MouseUp не всегда предшествует событие e_MouseDown. Приложение должно уметь игнорировать такие ситуации. Ввод с клавиатуры e_Char (integer Char, integer ShiftCtrlAlt) Если нажать клавишу на клавиатуре, то окну, которое в данный момент имеет фо- кус, будет послано событие e_Char. Существует в основном два различных типа клавиш: символы данных, которые на- ходятся в тексте, и управляющие клавиши, такие как <Enter>, <Del>, функциональ- ные клавиши и клавиши управления курсором. Клавиши данных представлены их значением, в то время как управляющие клавиши определяются кодами виртуаль- ных клавиш, определенными в файле VPI.CON. Нажатие клавиши e_KeyDown (integer Char, integer ShiftCtrlAlt) Это событие посылается при нажатии клавиши. Отпускание клавиши e__KeyUp (integer Char, integer ShiftCtrlAlt) Это событие посылается, если клавиша отпущена. События e KeyDown и е_Кеуир обычно происходят парами, но если пользователь держит клавишу нажатой достаточно долго, чтобы запустить автоматический повтор, система генерирует множество событий e_Char в строке. После того как пользова- тель отпустит клавишу, генерируется одно событие е_Кеуир. Поэтому получается следующая последовательность событий: e_KeyDown, e_Char, e_Char, ..., e_Char, e_KeyUp Событие от горизонтальной полосы прокрутки e_HScroll (ScrollCode, integer Pos) Это событие посылается, когда пользователь активизирует горизонтальную полосу прокрутки окна. Параметр Pos определяет позицию в полосе прокрутки.
724 Часть V. Разработка графического интерфейса пользователя Событие от вертикальной полосы прокрутки e_VScroll (scrollcode, integer Pos) Это событие посылается, когда пользователь активизирует вертикальную полосу прокрутки окна. Параметр Pos определяет позицию в полосе прокрутки. Получение фокуса e_Get Focus() Это событие генерируется прежде, чем окно получает фокус, так чтобы пользователь не мог нажимать на кнопки или использовать любые другие элементы управления, находящиеся в окне. Окно или диалоговое окно получает событие e Control getfocus), если полу- чают фокус его окна редактирования или окна списка. Потеря фокуса e_LdseFocus() Это событие генерируется, когда пользователь выбирает другое окно или приложе- ние. Когда приходит данное событие, окно все еще имеет фокус и сохраняет его до тех пор, пока не будет выполнен ваш код. Окно или диалоговое окно получает событие e_Control (_,_,_, losefocus), если те- ряют фокус его окна редактирования или окна списка. Стирание фона e__EraseBackground () Событие e_EraseBackground посылается, когда окно должно быть перерисовано. Обычно система управления окнами заполняет окно до перерисовки заданным по умолчанию цветом фона. Если предложение предиката обработчика для этого собы- тия выполнится успешно, то стандартное закрашивание фона произведено не будет. Если вы хотите получить прозрачное окно или нарисовать фон как часть обработки события ejjpdate, вам нужно написать предложение, обрабатывающее это событие. Чтобы ускорить процесс перерисовки и избавиться от мерцания изображения, дос- таточно записать: % Не выполнять автоматическое заполнение фона my_window_event_handler(Window, eJSraseBackground(), 0)!. Изменение положения окна ejfove (TopCornerX, TopCornerY) •l Это событие сообщает, что позиция окна была изменена. Верхний левый угол кли- ентской области окна был перемещен в точку TopCornerX, TopCornerY (в координатах экрана). Событие е Move может быть вызвано при помощи предиката win Move.
Гпава 26. Средства создания графического интерфейса 725 Изменение размера окна e_Size (NewWidth, NewHeight) Это событие сообщает, что окно изменило размер. Если сохранить старый размер в базе данных (возможно, вы делали это в событии e_Create), можно определить, было ли окно увеличено или уменьшено. Если окно было уменьшено, то в некото- рых случаях его изменять не нужно. В ответ на событие e_Size необходимо повторно вычислять размер рисунков, дочер- них окон или элементов управления и корректировать их размер и позиции. Напри- мер, чтобы текст всегда располагался в центре окна, нужно перерисовывать все ок- но. Для этого надо объявить окно недостоверным, вызвав предикат win_lnvalidate, и написать обработчик для события перерисовки, который вычисляет новый центр и перерисовывает окно. Обратите внимание, что эти события могут быть вызваны действиями пользователя (изменение размеров, перемещение окна) или при помощи предиката win_Move. Сворачивание и разворачивание окна также вызывают генерацию данных событий. Изменение состояния окна e_State(Stateinfo) Событие e_state посылается сразу после изменения состояния окна Task, окна верхнего уровня, дочернего окна или специальных элементов управления. Возмож- ные значения параметра stateinfo приведены в табл. 26.3. Таблица 26.3. Значения параметра stateinfo Значение Описание font (Font) Вызван предикат win_SetFont text (String) Вызван предикат win_SetText disabled () Окно блокируется вызовом предиката win Setstate enabled () Окно активизируется вызовом предиката win setstate invisible () Окно скрывается вызовом предиката win_SetState visible () Окно отображается на экране вызовом предиката win_SetState События GUI самой ОС e_Native (Message, wParam, IParam) Это событие дает возможность получать все события графического интерфейса пользователя самой ОС (а не только VPI), которые посылаются окну.
726 Часть К Разработка графического интерфейса пользователя Чтобы разрешить такие сообщения e_Native, вы должны вызвать предикат: win_EnableHook (Window, boolean OnOff) с параметром OnOff = b_True. Если обработчик события e Native успешно выполнится, то для этого события не будет произведена обработка VPI по умолчанию. Если обработчик выполнится неус- пешно, то событие (если нужно) будет преобразовано в обычное событие VPI. Системе управления окнами может быть возвращено значение long. "Собственное" рисование ejDwnerDraw(CtlType, Ctlld, ItemID, Action, State, Window, ret Rectltem, long ItemData) Это событие посылается окну, которое имеет элементы управления с флагами стиля wsf_OwnerDraw или wsf_OwnerDrawVariable. При получении этого события приложе- ние должно нарисовать элемент управления. Дополнительную информацию можно найти в примере OWN_DRAW на прилагае- мом CD-диске в каталоге <CDROM>:\RUN\VPI\TOOLEXAM. Возврат размера элемента "собственного" рисования e_OwnerMeasureItem(CtlType, Ctlld, Itemld, Data) Это событие посылается окну, которому принадлежат элементы управления с фла- гом стиля wsf_OwnerDraw, и используется для определения размеров пункта меню, элемента списка и т. д. Размер должен возвращаться как возвращаемое значение предикатом обработчика событий. Перерисовка окна e_Update (Ret) При получении этого события окно должно быть перерисовано. Прямоугольник Ret определяет координаты той части вашего окна, которая должна быть перерисована. Часто проще перерисовать целое окно, чем выяснять, что нужно перерисовать в той части окна, которая нуждается в обновлении. Система управления окнами устанав- ливает область отсечения так, что независимо от того, что вы рисовали, фактически будет обновляться только реально недостоверная область (листинг 26.5). my_wind°w_event_handleг(Window, e_Update(ClipRct), 0):- !, % Получение клиентской области ClientRCT = win GetClientRect(Window),
Гпава 26. Средства создания графического интерфейса 727 ClientRCT = ret(Wl, Wt, Wr, Wb), % Здесь должен быть код для перерисовки Предикат win_Invalidate вызывает появление события ejjpdate. Истечение интервала времени таймера eJTimer (long Timerld) После вызова: Timerld = timer_Set(Window, Interval) событие e Timer будет посылаться обработчику событий для окна Window через ин- тервал времени interval миллисекунд до тех пор, пока не будет вызван предикат timer_Kill (Timerld). С помощью параметра Timerld можно различать несколько таймеров между собой. Дополнительную информацию о таймерах см. в разд. "Таймеры” данной главы и в примере RUNTIMER каталога <CDROM>:\RUN\VPI\EXAMPLES на прилагаемом CD-диске. Возникновение программируемого события eJJser (LONG Id, LONG Ptr) Это событие eJJser позволяет расширить домен событий для приложения. Динамический обмен данными e_DDE (dde_conv Conv, dde_event Event) При выполнении динамического обмена данных (DDE, Dynamic Data Exchange) в операционной системе MS Windows обработчик событий окна Task автоматически получает все события e_DDE. Параметр dde conv Conv — это идентификатор продол- жающегося отдельного сеанса связи, a dde event Event — субструктура, которая в дальнейшем определит событие. Для получения дополнительной информации об обработке DDE см. раздел интерак- тивной справки о DDE и пример DDE в каталоге <CDROM>:\RUN\VPI \EXAMPLES на прилагаемом CD-диске. События уведомления от элементов управления e_Control (Ctlid, CtlType, CtlWindow, Control_info) Элементы управления посылают своему родительскому окну события e_Control, являющиеся событиями уведомления. Эти события включают в себя события полу- чения и потери фокуса ввода при передвижении по полям редактирования с по-
728 Часть V. Разработка графического интерфейса пользователя мощью клавиши <ТаЬ>, события нажатия на кнопку управления, изменения значе- ний или состояния полосы прокрутки и т. д. Здесь: □ ctlid — это целочисленная константа, присвоенная элементу управления во вре- мя редактирования ресурса, которая уникально идентифицирует его в окне или диалоговом окне; □ ctlType — это одна из констант с префиксом wc . определенных для домена Windowtype — тип элемента управления; □ Criwindow — это дескриптор окна элемента управления; □ Control info — это вспомогательное сообщение, которое более подробно объяс- няет причину активизации. Ниже приведены альтернативы домена control info. Домен control_info События для полос прокрутки scroll (ScrlEvent, Pos) Здесь: □ Pos — это новая позиция движка полосы прокрутки (после того, как пользова- тель его переместил); □ ScrlEvent определяет тип события полосы прокрутки (табл. 26.4). Таблица 26.4. Типы событий полосы прокрутки Событие sc_None sC_JLineUp sc_LineDown sc_PageUp sc_PageDown SC-Top sc_Bottom sc_Thumb sc^ThumbTrack Описание Событие должно быть проигнорировано Одна линия вверх Одна линия вниз Предыдущая страница Следующая страница Наверх Вниз Изменение положения движка Динамическое перемещение движка Получение элементом управления фокуса ввода; Getfocus () Потеря элементом управления фокуса ввода: losefocus ()
Гпава 26. Средства создания графического интерфейса 729 Изменение содержимого элемента управления: modified () Это событие генерируется один раз для каждого нажатия клавиши или операции мыши, которые приводят к изменению значения, отображенному в элементе управ- ления. События для списков П selchanged() Это событие посылается каждый раз при изменении текущего элемента списка, раскрывающегося списка и редактируемого списка. П dropdown() Это событие будет послано для редактируемого и раскрывающегося списков, ко- гда пользователь раскрывает список. Оно может быть использовано для времен- ной блокировки "громоздких*' операций события selchanged. П closeup() Событие closeup будет послано для редактируемого и раскрывающегося списков, когда пользователь закрывает список. Активизация элемента управления при помощи мыши или клавиатуры activated () Происходит при щелчках на кнопках и подобных действиях. События уведомления о завершении приложения ь e_EndApplication (Applications) VPI посылает это событие обработчику событий окна ReceiveMsgWin сразу после завершения приложения (с идентификатором приложения Applications), которое было запущено вызовом: Applications = vpi__CreateProcess (ReceiveMsgWindow, Command, ..., . . .) Здесь Command определяет программу, которая будет загружена. Активизация приложения e_Activate () VPI посылает событие e_Activate обработчику событий окна Task сразу после того, как приложение было активизировано (стало приоритетным).
730 Часть V. Разработка графического интерфейса пользователя Деактивизация приложения e_Deactivate() VPI посылает событие e_Deactivate обработчику событий окна Task сразу после того, как приложение было деактивизировано. Элементы управления Элементы управления — это специализированные дочерние окна. Главным образом они используются внутри диалоговых окон для выполнения операций ввода/вывода, но их также можно создать и в обычных окнах. Вы не можете рисовать в элементах управления (если не определен тип ownerdrawing). Элементы управления не имеют собственных обработчиков событий. События от элементов управления вызывают отправление уведомительных сообщений обработчику событий родительского окна. Предопределенные классы элементов управления перечислены в табл. 26.5. Таблица 26.5. Предопределенные классы элементов управления Тип элемента управления Класс окна Командная кнопка Переключатель Флажок Горизонтальная полоса прокрутки Вертикальная полоса прокрутки Поле редактирования Статический текст Список Раскрывающийся список Редактируемый список Элемент группировки Пиктограмма wc_PushButton wc_RadioButton wc__CheckBox wc_Hscroll wc_Vscroll wc_Ed.it wc_Text wc_Lbox wc__LboxBut t on wc_LboxEdit wc_GroupBox wc Icon Работа с элементами управления Поскольку элементы управления являются окнами, приложение может управлять ими, используя функции управления окнами. Оконный дескриптор для элемента управления всегда может быть получен по родительскому окну и идентификатору ресурса элемента управления: CtrlHndl = win GetCtlHandle(Parentwindow, Ctrlld)
Гпава 26. Средства созданий графического интерфейса 731 В табл. 26.6 приведены часто используемые предикаты для работы с элементами управления. Таблица 26.6. Работа с элементами управлений Операция Предикат Создание win__Creat eControl win_CreateDynControl Уничтожение win_Destroy ЛНМ*«<" та«аМНааа||т1аааа*»*аааа***ав"11*"|В||"''"1|вввв"*!|ааы1|1'*в'в»МЧав 1|Н *ajMHfa»MBwaat (йа^а^м ла*л1ааал»л*ааааьл*ааа№|<*ааьа»а1*аь»ь»<ааь||-»|-аа|Л||ааа|»||4а|л»|»|*аал»|а1*ал |аа(|валап ^аваа(^вваа| дра | а »наа " 1а Н|аа 1|НавваькМва»ьЬ»»а1аа | »| Наа » H-а* al||la*a>ll*l-B|№№№|aa|a№alaaa№№№laa^aa*afraaa|**aaa№№ai Размер/положение win_GetOuterRect win_Move win_GetClientRect Заголовок/метка/текст win_GetText win_SetText »**ЧЧ a a >e,aaa*llaaaal*l,a“4ai*aaa|*««aa*fr*«ialaliiia4lii*a|liii^a|lliiiHliiiil*ii»n,llau||F**aa||<l*aaa||l*aaJ M^*"aaa *aa"frraaaaal-a,laaaw*,aaaaw**aaaafraaa-aall*,all4fr*allllaaHI-»e,aaaHla* 8-H a a*« l« a ,a H H*aa •ае,аа*ач H a >i • "4 a a H • • 8-№8a aaa a HP 1 H h H'И IH*aa »ч Н*Г’ааа>‘Г*а1 H I I *►* H а»»>^Ь 14 j+i'aaju | la |a »,a iaa Показать, скрыть, включить, блокировать win_GetState win_SetState Фокус win_Get Focus win_SetFocus Иерархия окон win_GetParent Тип окон win GetType Флаги стиля При создании элемента управления вы можете определить для него различные фла- ги стиля. Некоторые флаги стиля являются переносимыми, в то время как другие существуют только на определенных платформах. Однако на всех платформах можно определить любой флаг — неподдерживаемые флаги будут просто игнорироваться. Таким образом, можно создать переносимые приложения, которые в то же время будут использовать преимущества каждой платформы. Значения флагов приведены в табл. 26.7. Таблица 26.7. Основные флаги стиля элементов управления Идентификатор флага Значение wsf_Disabled Изначально элемент управления будет заблокирован wsf_Checked Изначально флажок будет находиться в отмеченном состоянии wsf_Default Флаг для заданной по умолчанию кнопки в диалоговом окне wsf Invisible Создать скрытый элемент управления
732 Часть V. Разработка графического интерфейса пользователя Таблица 26.7 (продолжение) Идентификатор флага Значение wsf—Group wsf_TabStop wsf_Readonly wsf—MultiLine Wsf—MultiSelect wsf_MultiColumn wsf_AlignLeft wsf—AlignCenter wsf—AlignRight wsf—LeftText wsf—OwnerDraw wsf—VScroll wsf—HScroll wsf—NoBorder wsf_Simple wsf—NoWrap Флаг для начала создания новой группы элементов управления Позволить переход по табуляции к элементу управления Поле редактирования только для чтения Позволить многострочные поля редактирования Позволить списки множественного выбора Определить несколько колонок в списке Выравнивание текста по левому краю Выравнивание текста по центру Выравнивание текста по правому краю Определяет, что флажок или переключатель должен иметь текст с левой стороны Определяет "собственное рисование" (ownerdrawing) для элемента управления, в этом случае программист должен выполнять все операции рисования для элемента управления Определяет, что многострочное поле редактирования или окно списка при необходимости будет иметь вертикальную полосу прокрутки Определяет, что многострочное поле редактирования или окно списка при необходимости будет иметь горизонтальную полосу прокрутки Определяет, что элемент управления не должен иметь гра- ницу Определяет простой прямоугольник и отображает одиночную текстовую строку, выравненную по левой границе в прямо- угольнике. Эта строка не может быть сокращена или изменена Определяет простой прямоугольник и отображает данный текст, выравненный по левой границе в прямоугольнике. По- зиции табуляции расширены, но слова не переносятся авто- матически на другую строку. Текст, который выходит за пре- делы прямоугольника, отсекается wsf NoPrefix Предотвращает интерпретацию символов & в тексте элемен- та управления как акселераторов, обычно префиксный сим- вол & используется для обозначения того, что следующий за ним символ является акселератором (такой префиксный символ & не отображается (удаляется), а последующий за ним символ отображается подчеркнутым и может быть ис- пользован как акселератор). Этот стиль статического эле- мента управления может быть применен к любым из опреде- ленных статических элементов управления. Часто использу- ется, когда имена файлов или другие строки, которые могут содержать символ &, необходимо отобразить в статическом элементе управления в диалоговом окне
Гпава 26. Средства создания графического интерфейса 733 Таблица 26.7 (продолжение) Идентификатор флага Значение wsf_UpperCase wsf_LowerCase ws f_Ра s sword wsf AutoVScroll Преобразовывает все символы к верхнему регистру по мере того, как они появляются в поле редактирования Преобразовывает все символы к нижнему регистру по мере того, как они появляются в поле редактирования Отображает все символы звездочками (*) по мере того, как они появляются в поле редактирования Автоматически прокручивает текст вверх, когда пользователь нажимает клавишу <Enter> в конце последней строки wsf_AutoHScroll Автоматически прокручивает текст вправо на 10 символов, когда пользователь печатает символ в конце строки. Если нажать клавишу <Enter>, элемент управления прокручивает весь текст назад, в нулевую позицию wsf NoHideSel ws f_0EMConve rt wsf_WantReturn wsf_3State wsf_Auto wsf Auto3State I™ wsf_NoNotify wsf_Sort Не разрешает поведение по умолчанию для поля редактиро- вания. По умолчанию выбранный подтекст не подсвечивает- ся, когда элемент управления теряет фокус ввода, и подсве- чивается, когда элемент управления получает фокус ввода Преобразовывает текст, введенный в поле редактирования, из набора символов Windows в набор символов OEM и обратно. Этот флаг гарантирует необходимое символьное преобразование, когда приложение вызывает функцию AnsiToOem для преобразования строки Windows в символы OEM. Этот стиль более всего полезен для элементов редак- тирования, которые содержат имена файлов Определяет, что при нажатии клавиши <Enter> в многостроч- ном поле редактирования в текст будет вставлен символ возврата каретки. Если этот стиль не определен, то нажатие клавиши <Enter> вызовет те же действия, что и нажатие за- данной по умолчанию командной кнопки диалогового окна. Этот стиль никак не влияет на поведение однострочных по- лей редактирования Создает кнопку, которая, по сути, является флажком, за ис- ключением того, что она может быть как заблокирована, так и отмечена. Заблокированное состояние используется, чтобы показать, что состояние флажка метки не определено Группы автоматических переключателей или автоматических флажков Автоматический флажок с тремя состояниями Отказ от уведомления родительского окна входным сообще- нием Сортирует строки в списке в алфавитном порядке
734 Часть V. Разработка графического интерфейса пользователя Таблица26.7 (продолжение) Идентификатор флага Значение wsf—NoReDraw wsf_OwnerDrawvariable wsf—HasStrings wsf—UseTabStops wsf—NoIntegralHeight wsf—WantKeyboardInput ws f_Ext endedSe1 wsf_DisableNoScroll wsf—BlackRect wsf_GrayRect Определяет, что вид списка не обновляется при сделанных изменениях. Этот стиль можно изменить в любое время, ис- пользуя константы 1Ьох_Suspend и lbox_Resume Определяет, что владелец списка ответствен за изображе- ние его содержимого и что элементы в списке различают- ся по высоте. Окно владельца получает сообщение е_OwnerMeasureltem для каждого элемента при создании списка, и сообщение e_OwneгDrawitem всякий раз, когда изменяется внешний вид списка Определяет, что список содержит элементы, состоящие из строк. По умолчанию, все окна списков, кроме списков со стилем wsf OwnerDraw, имеют этот стиль. Приложение мо- жет создать wsf OwnerDraw-список с этим стилем или без него Позволяет окну списка распознавать и расширять символы табуляции при отображении его строк. Заданные по умолча- нию позиции табуляции — это 32 базовые диалоговые еди- ницы Определяет, что размер окна списка — тот размер, который указан приложением при создании списка. Обычно Windows изменяет размеры списка так, чтобы элементы отображались целиком Определяет, что родительское окно списка получает сооб- щения WM-VKEYTOITEM или wm_chartoitem всякий раз, когда пользователь нажимает клавишу и список имеет фокус вво- да, что позволяет приложению выполнять специальную обра- ботку ввода с клавиатуры. Если список имеет стиль LBS-HAS strings, то он может получать сообщения wm_vkeytoitem, но не WM—chartoitem. Если список не имеет стиля LBS-HASstrings, он может получать сообщения WM-CHARTOITEM, HO не WM-VKEYTOITEM Разрешает множественный выбор элементов при помощи клавиши <Shift> и мыши или специальных комбинаций кла- виш Отображает заблокированную вертикальную полосу прокрут- ки для списка, когда список содержит недостаточное для прокрутки количество элементов. Если этот стиль не опреде- лен, то полоса прокрутки будет скрыта, когда список содер- жит недостаточное для прокрутки количество элементов Устанавливает заполнение wc_Text цветом, используемым для изображения рамки окна. В заданной по умолчанию схеме цветов Windows — это черный цвет Устанавливает заполнение wc_Text цветом, используемым для изображения фона экрана. В заданной по умолчанию схеме цветов Windows — это серый цвет
Гпава 26. Средства создания графического интерфейса 735 Таблица 26.7 (окончание) Идентификатор флага Значение wsf_WhiteRect Устанавливает заполнение wcJText цветом, используемым для изображения фона окна. В заданной по умолчанию схе- ме цветов Windows — это белый цвет wsf_BlackFrame Определяет wc_Text с рамкой, нарисованной тем же цветом, что и рамки окна. В заданной по умолчанию схеме цветов Windows — это черный цвет wsf_GrayFrame Определяет wc_Text с рамкой, нарисованной тем же цветом, что и фон экрана (рабочий стол). В заданной по умолчанию схеме цветов Windows — это серый цвет wsf_WhiteFrame Определяет поле с рамкой, нарисованной тем же цветом, что и фон окна. В заданной по умолчанию схеме цветов Windows — это белый цвет В табл. 26.8 показано, какие флаги wsf_ являются значимыми для каждого класса элементов управления в операционной системе Windows и в администраторе пред- ставлений операционной системы OS/2. Таблица 26.8. Флаги элементов управления в операционных системах Windows и OS/2 Элемент управления Возможные флаги в ОС Windows Возможные флаги в администраторе представлений Командная кнопка wsf_Disabled, wsf_Default, .wsf—Invisible, wsf_Group, wsf_TabStop, wsf_OwnerDraw iaaa*i444444a4a'< j in in i|H W In наш in |4I III Ia44iain ie + *e + >ll 4bl nl aibia|a|| ii| 141 Ibi bibv4i ibi ii| |*t*+**biaaaaaaaa4|a*a4*44l44al in ii| ||| |i| |ll llbin r4a ||| | wsf—Disabled, wsf—Default, wsf_Invisible, wsf_Group, wsf—TabStop, wsf_NoBorder ll+llllltbllka4bataaaaa4"4 + 4+** + *l+**a*aaa" + """** + * 1Ь**4^*444ач,*ииии4и14и4ии4 + 444 е44,и<иЧ-Чии14"41 l-M l-M T* * • * + + + ♦♦♦♦♦a|aaa*aa* Флажок wsf_Disabled, wsf_Invisible, wsf—Group, wsf_TabStop, wsf_OwnerDraw, wsf_LeftText, wsf_Auto, wsf_3State, wsf_Auto3State wsf—Disabled, wsf_Invisible, wsf—Group, wsf_TabStop, wsf—Auto, wsf_3State Переключатель wsf_Disabled, wsf_Invisible, wsf_Group, wsf_TabStop, wsf_LeftText, wsf—OwnerDraw, ws f_Aut о raaal + ba ia) aa*-g e44 bbbbbbbbbaa ам +a e + "4> l>b r + aba a4aaaaaZ44" *444WI bbl b*i aLai b4bbba4b41 wsf_Disabled,wsf_Invisible, wsf—Group, wsf—TabStop, ws f_Aut о Mb* b a b> >4 bV 4 4 b Т-'ЬЛ.'Ь'а'а'а'ач-уЧ1 P V a 4*4 *4 *4 ***4 *** *4 >4 T 4 44 *4 4*4 ***%1 L ♦ ♦ L ♦4444 4 b e4 ***««4 * a 4* Ь Ь 4 a a 4 Статический текст wsf_Disabled, wsf_Invisible, wsf—AlignLeft, wsf_AlignCenter, wsf_AlignRight, wsf—BlackRect, wsf_GrayRect, wsf_WhiteRect, wsf—BlackFrame, wsf_GrayFrame, wsf_Whit eFrame; wsf_Simple, ws f_Lef tNoWordWrap, wsf_NoPrefix wsf_Disabled, wsf_Invisible, wsf—AlignLeft, wsf_AlignRight, ws f _Al ignCent er, wsf_BlackRect, wsf_GrayRect, wsf_WhiteRect, wsf_BlackFrame, wsf_Gray Frame, wsf_Whit eFrame, ws f_Le ftNoWordWrap
736 Часть V. Разработка графического интерфейса пользователя Таблица 26.8 (продолжение) Элемент Возможные флаги Возможные флаги управления в ОС Windows в администраторе представлений Элемент группирования wsf_Disabled, wsf_Invisible wsf—Disabled, wsf—Invisible н- <"*► Поле редак- wsf_Disabled, wsf_Invisible, wsf_Disabled, тирования wsf_Group, wsf_TabStop, wsf_Invisible, wsf_Group, wsf_AlignLeft, wsf_AlignCenter, wsf_TabStop, wsf_AlignLeft, wsf_AlignRight, wsf_MultiLine, ws f_AlignCent e r, wsf-AutoVScroll, wsf_AlignRight, wsf_AutoHScroll, wsf_VScroll, wsf—Mult iLine, wsf__HScroll, wsf_Password, wsf_AutoVScroll, wsf_NoHideSel, wsf_OEMConvert, wsf_AutoHScroll, wsf_WantReturn, wsf___NoBorder, wsf_NoBorder (для много- wsf_UpperCase, wsf—Lowercase, строчного ввода), wsf_Readonly wsf_Readonly, wsf—Password Полоса wsf_Disabled, wsf—Invisible, wsf—Disabled, прокрутки wsf_Group, wsf_TabStop wsf—Invisible, wsf_Group, wsf_TabStop чвавааавваааава+аваая4ЯяаяававГ4""В"+4"В"""В""""""""""4*""444""4'44+444"44+а4++44^+4а<++ ^4В""""4В*ВВ4№Г4*444№Г444444*444а4аааааа*аЯаЯа*Яа44444аа44*44444444Н44*И*аЧ^4*****^^аЧМ***"""""""В"""ВВ"В"""""""В"""ВВв***В*^*^^*^^**^**"^*"**""В""""* Список wsf_Disabled, wsf_Invisible, wsf_Disabled, wsf—Invisible, wsf_Group, wsf_TabStop, wsf_Group, wsf_TabStop, wsf_MultiSelect, wsf_MultiSelect, wsf_ExtendedSel, wsf_ExtendedSel, wsf_OwnerDraw, wsf—Owner Draw-Variable, wsf_HasStrings, wsf—NoBorder, wsf_Sort, wsf_NoNotify, wsf—MultiColumn, wsf_HScroll, wsf_VScroil, wsf_NoReDraw, wsf_UseTabStops, wsf_WantKeyboardlnput, wsf_DisableNoScroll, wsf_NoIntegralHeight wsf_OwnerDraw, wsf_HScroll la*a*aaaaa|aaaaa|a|i+*l + *i a<l +H. + + H.4 + + <M + 4a a + , a + a +<M 4 + + + + b.aaB*ar«-. 4 14 • 14 *4 -1 -1 + 41 *-»!+41 4* *4 *4*"** ****** *♦♦♦♦♦♦*♦♦♦♦•♦*»•••••♦*«•*•♦• •• •••••**4**4444 ♦ B ( |L |L ( () _L ITL--L 1 l-LL I--I -*~ —ь ** *-ьж в вв в в ““ П и., и . .а, »»»*****»»*»*»»*>«** Редактируемый wsf_Disabled, wsf_Invisible, wsf_Disabled, wsf_Invisible, список wsf_Group, wsf_TabStop, wsf_Group, wsf_TabStop, wsf_OwnerDraw, ws f_Owne r DrawVa r i ab1e, wsf—HasStrings, wsf—Sort, wsf_VScroll, wsf_NoIntegralHeight, wsf_AutoHScroll, wsf_DisableNoScroll, wsf_OEMConvert wsf_HScroll
Гпава 26. Средства создания графического интерфейса 737 Таблица 26.8 (окончание) Элемент управления Возможные флаги в ОС Windows Возможные флаги в администраторе представлений Раскрывающийся wsf__Disabled, wsf_Invisible, wsf^Disabled, wsf_Invisible, список wsf_Group, wsf_TabStop, wsf_Group, wsfJTabStop, wsf_OwnerDraw, wsf_HScroll wsf_OwnerDrawVariable, wsf_HasStrings, wsf_Sort, wsf—VScroll, wsf_NoIntegralHeight, wsf—AutoHScroll, wsf_DisableNoScroll + + ' *Ж*4*Ь1Ь*+*таа И b b b 8b b + + b • 8b b b W < W + + 8+ b b b-Г» * * •• * *ь ь •••• 8""8"b b • b 8Ь • 8b 8b b 8b b b b b b b b b О. * + Ь +b b * *ar*ba а* , + •> а 888+8+' 88888++"88+++++88++88++Ь а а* »+♦ a b Ff a b а а b а а а Ь + а а |а i ia ia baa i 11 ba 11 i b ♦ia*|B|a*|a ЬцИ |Н* It* b* ' I'‘ lit* И li b* Hi i i ia t ‘‘ ‘ 'М t* Пиктограмма ,,,,, lu |а|аааааааа|аааааааа^ l|f**^***l + + a+^1,+ ,+,ц ,,1 ||а |а| аа,ааааа+, г|ааа рааа||аь^ава ыа|В1 ь|а ip НЬ*ЬЬ bbb ь ************ а**"^"11*** bb* * b11 b*+b+'+O + 'l* |*1****'* ******'**** к|< к|ьааааа*а1^||||*|****ь * + + *** ** + + ** ***************'**** '*'||аа|аа" lta|aaaa|**11*1 * Специальный wsf_Disabled, wsf_Invisible, wsf_Disabled, wsf_Invisible, элемент wsf_Group, wsf_TabStop, wsf__Group, wsf_TabStop управления wsf_Topmost, wsf_Transparent, wsf_HScroll, wsf_Vscroll События от элементов управления Хотя элементы управления — это окна, обработчики событий от них не являются частью приложения. Стандартные элементы управления, как правило, реализуются системой управления окнами или расширениями этой системы в случае специаль- ных элементов управления. Когда создается элемент управления, он должен быть порожден как потомок другого окна. Это очень важно, т. к. элемент управления по- сылает уведомительные сообщения своему родительскому окну при возникновении событий, например вводе данных пользователем. Приложение использует уведоми- тельные сообщения для выполнения соответствующих действий. Эти сообщения появляются как сообщения e Control родительского окна, которые включают в себя идентификатор ресурса элемента управления, тип элемента управления, идентифи- катор окна элемента управления и зарезервированные поля для дополнительной ин- формации, описывающей событие: e_Control(integer Id, Windowtype, window Ctrl, Control_info); Дополнительная информация о событии принадлежит домену controi_info (лис- тинг 26.6). Листинг 26.6 C.W- control_info = activated(); % Нажатие на кнопку и т. д. scroll(scrollCode, integer); % Тип прокрутки + новая позиция getfocus(); % Элемент управления получит фокус ввода
738 Часть И Разработка графического интерфейса пользователя losefocus(); modified(); % Элемент управления потеряет фокус ввода % Содержимое было изменено dropdown(); % Раскрытие списка closeup(); selchanged; % Закрытие раскрывающегося списка % Изменение текущего элемента в списке Внимание! События e_Controi не посылаются для полос прокрутки, которые созданы согласно флагам стиля окна вдоль его границ. В этих случаях посылаются специальные со- общения e_VScrollи e_HScroll. Создание элементов управления Создание элементов управления может быть автоматическим — как часть создания диалогового окна по его описанию, определенному в сегменте ресурсов приложения, или динамическим — во время работы приложения. Диалоговые окна разрабатыва- ются и размещаются в файлах ресурсов в редакторе диалоговых окон VDE, а затем они создаются (показываются) во время исполнения вызовом предиката win_CreateResDialog, а диалоговые окна, не размещенные в файлах ресурсов, могут быть созданы динамически во время исполнения вызовом предиката win_ CreateDynDialog, но все параметры должны быть определены во время создания. Для создания в окне нового элемента управления используют предикат winjCre at eControl. Для создания в окне нескольких элементов управления, опреде- ленных в структуре Windef_list, — предикат win_CreateDynControl. Для того чтобы добавить дополнительные элементы управления к обычному окну или диалоговому окну, создают невидимый элемент-управления, который затем делается видимым. Изучив параметры предиката win_CreateControl, вы сможете создавать элементы управления (листинг 26.7) с различными возможностями. [Листинг 26.7 ***«««***«««*«***•*«« $ CtrlWin = win_CreateControl( Windowtype, % Тип элемента управления Ret, % Размер и позиция string Title, % Заголовок window Parent, % Родительское окно элемента управления Wsflags, % Флаги стилей для элемента управления integer Ctrlld) % Значение, передаваемое событию e_Create CtrlWin = win_CreateDynControl( Windef_list, % Определение структуры window Parent) % Значение, передаваемое событию e_Create
Гпава 26. Средства создания графического интерфейса 739 Различные элементы управления Рассмотрим элементы управления, используемые VPI. Элемент группировки Элемент группировки — это прямоугольник в диалоговом окне. Он выглядит как вы- емка на поверхности диалогового окна. Выровненный влево заголовок необходимой длины размещен на верхней линии блока. Элемент группировки визуально опреде- ляет, что набор элементов управления внутри него обладает общим значением. Эле- мент группировки не имеет функциональности — он не влияет на поведение эле- ментов управления, заключенных внутри него (вы можете использовать стиль wsf_Group для элементов управления внутри элемента группировки), более того, он похож на статический текст — не может быть выделен и не посылает событий. Внутри элемента группировки нет отношения предок-потомок среди элементов управления. Пиктограммы Этот элемент управления позволяет отображать в окне или диалоговом окне опреде- ленную в ресурсах пиктограмму. Это всегда небольшое битовое изображение, разде- ленное на пикселы. Видимый размер зависит от родительского окна и может поэто- му изменяться вместе с разрешением экрана. Пиктограммы не посылают уведом- лений. Для динамического создания пиктограммы вызовите предикат win_CreateDynControl с вариантом домена icon. icon(Wdef, Ctlld, Resld, WsFlags); Статический текст Этот элемент управления содержит небольшой по размеру текст, обычно используе- мый в качестве меток для других элементов управления, который может быть раз- мещен в определенном месте окна или диалогового окна. Статический текст не ге- нерирует никаких событий. Чтобы изменить текст во время работы приложения, вызовите предикат win_SetText. Для того чтобы скрыть или показать элемент управ- ления, вызовите предикат win_SetState. Поля редактирования Поля редактирования позволяют пользователю вводить данные. Обычно этот эле- мент управления представляет собой отдельную строку, которая содержит имя или число и т. д. Поле редактирования может быть многострочным. Для установки и получения содержимого поля редактирования нужно вызывать сле- дующие предикаты: String - win_GetText(Window) win_SetText (Window, string Text)
740 Часть V. Разработка графического интерфейса пользователя где Window — это оконный дескриптор элемента управления, который может быть получен вызовом предиката: CtrlHndl win_GetCtlHandle(Parentwindow, Ctrlld) Сообщения от полей редактирования Окна редактирования посылают три уведомительных сообщения: ehandler(Window, e_Control(CtllD, CtrlType, CtrlWindow, getfocus), 0):- ehandler(Window, e_Control(CtllD, CtrlType, CtrlWindow, losefocus), 0):- ehandler(Window, e_Control(CtllD, CtrlType, CtrlWindow, modified), 0):- Каждый раз, когда пользователь вносит какие-либо изменения в данные, будет послано уведомление об изменении. Очевидно, что если вводится новый или редак- тируется старый текст, то уведомление об изменении будет посылаться неодно- кратно. Командные кнопки Командные кнопки позволяют пользователю выполнять различные действия: вызы- вать закрытие окна или диалогового окна, открытие нового окна, начало печати и т. д. Командные кнопки — это такие элементы управления, которые "вдавливаются” при нажатии на них мышью и возвращаются в первоначальное положение после того, как кнопка мыши отпущена. В последнем случае генерируется событие e_Control с ресурсным идентификатором кнопки, который посылается обработчику событий родительского окна. Если переместить мышь за пределы командной кнопки и при этом держать кнопку мыши нажатой, то командная кнопка вернется в первоначаль- ное положение и событие e_Control сгенерировано не будет. Для перемещения между командными кнопками используйте клавишу <ТаЬ> и ком- бинацию клавиш <Shift>+<Tab>, а при достижении нужной кнопки нажмите пробел. Если ресурсным идентификатором элемента управления является ide hello, то для создания маленького сообщения может быть использовано следующее предложение: ehandler (Window, e_Control (idc_hello, __) , 0) : - !, dlg__Note ("Hello There!"). Командная кнопка по умолчанию В диалоговом окне может быть задана командная кнопка по умолчанию. Это значит, что нажатие клавиши <Enter> в любом месте диалогового окна (кроме области поля редактирования с установленным свойством wsf_WantReturn) эквивалентно нажатию командной кнопки, заданной по умолчанию. Кнопка, заданная по умолчанию, в начальном положении изображается с окружаю- щим ее прямоугольником, который обозначает, что кнопка является активным эле- ментом управления. Заданная по умолчанию кнопка создается с использованием
Гпава 26. Средства создания графического интерфейса 741 флага стиля wsf_De fault. Это можно сделать в редакторе окон и диалоговых окон VDE, отметив соответствующее свойство для кнопки по умолчанию. Сообщения от командных кнопок Командные кнопки генерируют только один вид событий: ehandler(Window, е_Contго1(Ctlld, CtlType, CtlWindow, activated), 0) Получение и потеря кнопкой фокуса не вызывают генерацию событий get focus или losefocus. Флажки Флажки нужны для определения логических состояний. Например, флажки в диало- говом окне настройки свойств компилятора Visual Prolog позволяют определить, нужно ли производить проверку стека, должна ли генерироваться информация о номерах строк и т. д. В отличие от переключателя каждый флажок, как правило, не зависит от других флажков. Когда пользователь щелкает мышью по флажку, предикату обработчика окна посы- лается событие e_Control. Если вы не используете пакет диалоговых окон, то для изменения состояния флажка вам придется написать код самостоятельно. Это мож- но сделать вызовом предиката: win_Check (Window, integer OnOff). Предложение для обработки изменения состояния флажка может быть таким (лис- тинг 26.8). j Листинг 26.8 mywin__event__handler (Window, e_Control (idc_check, CtrlType, CtrlWindow, },0):- !, % Проверка возможности изменения состояния State = win_IsChecked(CtrlWindow), toggle(State, NewState), win_Check(CtrlWindow, NewState). Если вы используете пакет диалоговых окон, вам не придется заботиться об измене- нии состояния флажков. В этом случае начальная установка должна быть выполнена при создании диалогового окна, а возвращение результирующего значения — при уничтожении. Внимание! В операционной системе MS Windows можно создать флажок, который будет авто- матически изменять его состояние, если указать стиль wsf_Auto.
742 Часть V. Разработка графического интерфейса пользователя Флажок посылает уведомление только об одном виде событий: ehandler(Window, e_Control(Ctlid, CtlType, CtlWindow, activated), 0):- При получении и потере фокуса уведомительные сообщения не посылаются. Переключатели Переключатели подобны флажкам за исключением того, что в группе может быть отмечен только один переключатель. В операционной системе MS Windows для переключателя можно установить флаг wsf_Auto, который означает, что если пользователь нажмет на некоторый переклю- чатель в группе, то все остальные переключатели в этой группе автоматически будут сброшены. Для поддержки переносимости существует предикат: win__CheckRadi ©Button (Window, winlist RadioWinList) при помощи которого можно включить переключатель в группу. При использовании пакета диалоговых окон этот предикат автоматически управляет переключателями. События уведомления от переключателей Переключатели генерируют только одно событие: ehandler(Window, e_Control(Ctlid, CtlType, CtlWindow, activated), 0) При получении и потере фокуса уведомление не посылается. Списки Список — это элемент управления, содержащий набор пунктов, которые может вы- брать пользователь. Если список содержит больше элементов, чем может быть ото- бражено в окне, и установлен флаг wsf vscroll, то автоматически появится верти- кальная полоса прокрутки. Пользователь может перемещаться по списку вверх и вниз для изменения текущего значения или дважды щелкнуть на выбранном эле- менте списка. Существуют специальные виды списков, такие как раскрывающиеся списки и ре- дактируемые списки. Установкой соответствующих флагов вы можете позволить множественный выбор в списке или создать многоколоночный список (создание нескольких колонок в списке реализовано не на всех платформах). Списки обычно содержат строки, но, используя стиль wsf_OwnerDraw, можно созда- вать списки с произвольным типом данных. Предикаты для работы со списками В VPI есть несколько предикатов для работы со списками. Все они имеют префикс 1Ьох_. Эти предикаты требуют идентификатор списка, который может быть получен вызовом предиката win_GetctlHandle, дескриптор родительского окна и ресурсный идентификатор списка. Индекс 0 определяет первый элемент списка, 1 — второй и т. д. Значение индекса —1 используется для добавления элемента в конец списка.
Гпава 26. Средства создания графического интерфейса 743 В примере <CDROM>:\RUN\VPI\EXAMPLES\Listbox на прилагаемом CD-диске показано, как использовать предикаты для работы со списками: □ lbox_Add(Window, integer Index, string Str) Этот предикат добавляет новою строку в список и отображает ее, если она нахо- дится внутри списка. Новый элемент будет размещен в строке, следующей за строкой с индексом index. Если index равен ~1, то строка будет добавлена в ко- нец списка, а текущая строка останется выделенной. • lbox_Add(Window, integer Index, slist StringList) Этот вариант lbox__Add добавляет все строки в список StringList в задан- ную позицию в существующем списке. Если index равен —1, то строки бу- дут добавлены в конец списка. • lbox__Add (Window, string Str) Этот вариант должен применяться, если для списка установлен атрибут сортировки. Для списка с неустановленным атрибутом сортировки элемен- ты будут добавляться в конец списка. • lbox_Add(Window, slist StringList) Этот вариант должен применяться для добавления набора строк в список с установленным флагом сортировки. Если этот флаг не установлен, строки будут добавлены в конец списка. □ lbox_Clear(Window) При помощи этого предиката можно очистить окно списка и удалить все эле- менты. П lbox_Delete(Window, integer Index) Этот предикат может быть использован для удаления конкретного элемента из списка. Если значение параметра index равно 0, то будет удален первый элемент, если 1 — второй и т. д. Значение — 1 будет проигнорировано. П Slist “ lbox_GetAll(Window) Возвращает все элементы списка в виде набора строк. □ Integer = lbox_CountAl1(Window) Возвращает количество строк в списке. □ lbox_GetItem(Window, integer Index) Этот предикат может быть использован для получения конкретного элемента в списке. Если значение параметра index равно 0, то будет возвращен первый элемент, если 1 — второй и т. д. Значение — 1 будет проигнорировано. □ lbox_GetSel(Window, Slist, Ilist) Этот предикат возвращает список выбранных строк и список их индексов в од- ном и том же порядке. Хотя этот предикат предназначен для множественного выбора из списка, он работает и для обычного списка с одним выбранным эле- ментом. Если никакой элемент не выбран, то предикат возвратит два пустых списка.
744 Часть V. Разработка графического интерфейса пользователя □ Integer = Ibox GetSellndex(Window) Этот предикат возвращает индекс выбранного элемента. Предикат завершится неуспешно, если никакой элемент в списке не будет выбран. □ lbox_IsSel(Window, integer Index) Этот предикат завершится успешно или неуспешно в зависимости от того, явля- ется ли текущим заданный элемент в списке. □ IboX—SetSel(Window, integer Index, boolean Select) Этот предикат используется для выбора или отмены выбора элемента в списке. При возможности множественного выбора из списка этот предикат будет влиять только на один элемент. При одиночном выборе из списка выбор одного элемен- та автоматически будет отменять выбор других элементов в списке. □ lbox_Suspend(Window) lbox__Resume (Window) Эти два предиката могут использоваться для временного приостановления пере- рисовки экрана и затем продолжения перерисовки. Такой подход предупреждает эффект мерцания во время процесса перерисовки экрана. Например, при за- полнении списка элементами из внешнего источника, включая ряд операций ibox Add, более правильным будет в начале операции вызвать предикат lbox_Suspend, а в конце операции — предикат lbox_Resume. □ lbox_SetColumnWidth(Window, integer width) Этот предикат может быть использован для установки ширины колонок в много- колоночном списке. Изображенное на рис. 26.1 диалоговое окно Choose Your Favorite Language было создано с использованием стиля многоколоночного списка. Для инициализации был добавлен такой код (листинг 26.9). Рис. 26.1. Диалоговое окно Choose Your Favorite Language ) Листинг 26,9 в. :..:G jO'.• • .5: ...... г: dlg_choose_your_favorite_ language_ _eh (_Window, e_Create (_) , 0) : - ’, LB = win_getCtlHandle(_Window, idc_choose_your_favorite^language) , lbox_setColumnWidth(LB, 56), ! V Предикат lbox_SetTabStops (WINDOW, ILIST TabList) вы можете использовать для создания списка с флагом wsf_UseTabStops, чтобы установить позиции табуляции
Гпава 26. Средства создания графического интерфейса 745 для определения "видимых колонок". Текстовая информация, представленная в каж- дой строке списка, — это один элемент. Заметьте, что это не противоречит многоко- лоночным спискам. Если списки имеют символы табуляции, то символы, следую- щие за ними, будут выровнены в соответствии с установленным списком позиций табуляции. Позиции табуляции даны в диалоговых базовых единицах. Если шрифт диалогового окна пропорциональный, этот метод применяется для вы- равнивания колонок, как показано на рис. 26.2. Заметьте, что каждая строка остается одной сущностью. Рис. 26.2. Диалоговое окно со списком с выровненными колонками В качестве примера вызова предиката показана инициализация позиций табуляции (листинг 26.10). ; Листинг 26.10;' ' ' . ' ::; -ШВ »« ( k« « «'*«'« «*«**'« *««««««««* *««*«««« **************** * (tikkfii'»H*»*'ii„ ««« * *>>'** ********* V* ** •* V« ****** • *•<* **•**•• ' •«>•« *"* »•» dlg_select_employee_eh(_Window, e_Create(_), 0):-!, List - win_getCtlHandle(_Window, idc__select_employee_l) , lbox_setTabstops(List, [36,128,238]), t « * Действительные значения позиций табуляции получены вычитанием значения диа- логовых базовых единиц левой координаты окна списка из левой координаты каж- дого заголовка. События уведомления от списков: % При выборе элемента в списке ehandler(Window, е Control(CtrllD, CtrlType, CtrlWindow, selchanged), 0):-
746 Часть V. Разработка графического интерфейса пользователя % При двойном щелчке в окне списка ehandler(Window, e_Control(CtrlTD, CtrlType, CtrlWindow, activated), 0) % При получении списком фокуса ввода ehandler(Window, e_Control(CtrllD, CtrlType, CtrlWindow, getfocus), 0) % При потере списком фокуса ввода ehandler(Window, e_Control(CtrlTD, CtrlType, CtrlWindow, losefocus), 0) Предикат: IboX—SetTopIndex(window Window, integer Index) прокручивает окно списка так, чтобы элемент с индексом index появился в верхней строке окна списка или была достигнута максимальная позиция полосы прокрутки. Index — это номер позиции в списке, начиная с нуля. Раскрывающиеся списки Раскрывающиеся списки — это комбинация командной кнопки и списка. В исход- ном состоянии раскрывающиеся списки представляют собой одиночную строку, но при нажатии на маленькую кнопку, находящуюся с правой стороны строки, в выпа- дающем окне или выше элемента управления появляется содержимое списка. Выбор может быть сделан из списка альтернатив. Повторное нажатие на кнопку свернет список в одиночную строку, содержащую текущий элемент. Как одиночная строка, так и сам список предназначены только для чтения и не могут быть изменены поль- зователем. Раскрывающиеся списки имеют те же стили, события и операции, как и обычные списки. Однако существуют некоторые исключения: □ при создании раскрывающегося списка данный прямоугольник будет иметь раз- мер элемента управления с раскрытым списком; □ раскрывающийся список не имеет флага множественного выбора, не под- держивает позиций табуляции и многоколоночного стиля, поэтому флаги табуля- ции и многоколоночного стиля, а также предикаты lbox_SetTabstops и lbox_setColumnWidth для него неприменимы. События уведомления от раскрывающихся списков: % При изменении текущего элемента списка ehandler(Window, e_Control(CtrllD, CtrlType, CtrlWindow, selchanged, 0) % При раскрытии списка ehandler(Window, e_Control(CtrllD, CtrlType, CtrlWindow, dropdown), 0) % При сворачивании списка ehandler(Window, e_Control(CtrllD, CtrlType, CtrlWindow, closeup), 0):- % При получении списком фокуса ввода (только для Windows) ehandler(Window, e_Control(CtrllD, CtrlType, CtrlWindow, getfocus), 0):- % При потере списком фокуса ввода (только для Windows) ehandler(Window, e_Control(CtrllD, CtrlType, CtrlWindow, losefocus), 0):-
Гпава 26. Средства создания графического интерфейса 747 Редактируемые списки Редактируемые списки — это комбинация поля редактирования и списка. Их пове- дение аналогично полям редактирования с возможностью отображения элементов списка. Редактировать можно текущий элемент списка, который отображается в по- ле редактирования. Редактирование текущего элемента, удаление отдельной строки и ввод нового значения может производиться при отображении всего списка. Выпа- дающий список не может редактироваться пользователем (хотя вы можете добавить это свойство самостоятельно). События уведомления от редактируемых списков: % При изменении текущего элемента списка ehandler(Window, e_Control(CtrlTD, CtrlType, CtrlWindow, selchanged, 0):- % При раскрытии списка ehandler(Window, e_Control(CtrlTD, CtrlType, CtrlWindow, dropdown), 0) % При сворачивании списка ehandler(Window, e_Control(CtrlTD, CtrlType, CtrlWindow, closeup), 0) % Перед получением списком фокуса ввода ehandler(Window, e_Control(CtllD, CtrlType, CtrlWindow, getfocus), 0) % Перед потерей списком фокуса ввода ehandler(Window, e_Control(CtllD, CtrlType, CtrlWindow, losefocus), 0) % При изменении списка ehandler(Window, e_Control(CtllD, CtrlType, CtrlWindow, modified), 0) Полосы прокрутки Установив типы окна wc_HScroll и wc_yscroll, вы можете создать горизонтальную и вертикальную полосы прокрутки в любом месте окна. Полосы прокрутки могут быть определены как часть поля редактирования и списка. В этих случаях операция прокрутки выполняется элементом управления и уведомле- ния родительскому диалоговому окну не посылаются. События уведомления от полос прокрутки могут быть трех типов: полосы прокрутки как элементы управления посылают события e Control, в то время как полосы про- крутки окна посылают события e HScroll и e VScroll. Предложения обработчика событий для этих трех типов выглядят следующим обра- зом (листинг 26.11): ~ у- i - - - < . - . \ - - - -‘. Л - J/:. - \ : Листинг ...... •••••.« «»».« «Bt ...................................... ehandler (Window, e_Control (Resld, CtrlType, CtrlWindow, scroll (ScrCode, Pos) ) , 0) : - ehandler(Window,e_VScroll(ScrCode,Pos),0):- % Активизирована вертикальная полоса прокрутки ehandler(Window,e HScroll(ScrCode,Pos),0):- % Активизирована горизонтальная полоса прокрутки
748 Часть И Разработка графического интерфейса пользователя ScrCode принадлежит домену scrollcode и определяет действие, произведенное с по- лосой прокрутки (смещение на строку вверх, на страницу вниз, в конец и т. д.). Значения этого параметра перечислены в табл. 26.9. Одиночное нажатие кнопки мыши на полосе прокрутки выше или ниже движка сге- нерирует событие sc_PageUp или sc_PageDown соответственно. Задачей программиста является определение того, что эти события должны обозначать в контексте прило- жения, и обеспечение выполнения всех изменений в окне. Поле Pos является важным в случае перемещения пользователем движка полосы прокрутки. Это значение определяет позицию движка относительно полосы про- крутки. Во время перемещения движка приложение будет посылать события scjrhumbTrack, и после того как пользователь отпустит движок, пошлет событие s с_Thumb. Таблица 26.9. Коды полосы прокрутки Код sc_LineUp sc_LineDown sc_PageUp sc_PageDown sc_Top sc_Bottom SC—Thumb sc_ThumbTrack Значение Строка вверх Строка вниз Предыдущая страница Следующая страница В начало В конец Изменение положения движка Перемещение движка Обращение к полосе прокрутки Для работы с полосами прокрутки окна и полосами прокрутки, принадлежащими элементам управления, используется похожий набор предикатов. Так как нет от- дельного идентификатора для полос прокрутки окна, для обращения к полосам про- крутки используется дескриптор окна. Специальный параметр scroll type исполь- зуется для определения того, к какой полосе прокрутки идет обращение (лис- тинг 26.12). Листинг 26.12 global domains scroll—type = integer constants sb_Horz - 0 sb—Vert = 1 sb Ctl = 2 % Горизонтальная полоса прокрутки окна % Вертикальная полоса прокрутки окна % Полоса прокрутки как элемент управления в окне
Гпава 26. Средства создания графического интерфейса 749 Диапазон полосы прокрутки Чтобы работать с реальной позицией движка, сначала нужно установить диапазон полосы прокрутки. Это можно сделать вызовом предиката: win_SetScrollRange(Window, Scroll_type, integer Min, integer Max) Предикат win_GetScrollRange возвращает диапазон полосы прокрутки. win_GetScrollRange(Window, Scroll_type, integer Min, integer Max) Если вы явно не установили диапазон, то будет установлен диапазон по умолчанию. Для полос прокрутки окна это значение находится в пределах от 0 до 100. Для полос прокрутки как элементов управления диапазона по умолчанию нет. На 16-разрядной платформе Windows разность между Min и Мах должна быть не больше 32 767. Позиционирование полосы прокрутки Реальное положение движка полосы прокрутки должно контролироваться приложе- нием, т. к. оно не изменяется автоматически после действий пользователя. Положе- ние движка должно быть внутри диапазона полосы прокрутки. Приложение устанав- ливает позицию при помощи предиката: win_SetScrollPos(Window, Scroll_type, integer Pos) Текущая позиция может быть прочитана при помощи предиката: Integer = win_GetScrollPos(Window,- Scroll_type) Соразмерность полосы прокрутки На некоторых платформах можно устанавливать размер движка в зависимости от размера полосы прокрутки, что позволяет визуально определять общий объем дан- ных. Значение Proportion должно быть между нулем и длиной интервала (Мах — Min) для полосы прокрутки: win_SetScrollProportion(Window, Scroll_type, unsigned Proportion) Текущее значение этого соотношения можно получить при помощи предиката: Unsigned = win_GetScrollProportion(Window, Scroll_type) "Собственное" рисование для элементов управления Определив атрибут wsf OwnerDraw или wsf_OwnerDrawVariable, вы можете создать элемент управления, который ведет себя иначе, чем обычный элемент управления, т. к. он сам отвечает за свое рисование. Определение этого атрибута означает, что обработчик событий родительского окна элемента управления (для элементов управ- ления диалогового окна — обработчик событий диалогового окна) должен уметь об- рабатывать следующие два события:
750 Часть V, Разработка графического интерфейса пользователя e_OwnerMeasure!tem(CtlType, Ctlid, Itemld, long data); e_OwnerDraw(unsigned CtlType, unsigned Ctlid, unsigned ItemID, od_itemaction ItemAction, od_itemstate ItemState, window Window, rot Rectltem, long ItemData); Первое событие используется для измерения размеров элементов в списке. Обработ- чик должен возвратить значение, которое является размером элемента управления. Второе событие заставляет приложение отобразить содержимое элемента управ- ления. Для получения дополнительных сведений можно обратиться к примеру <CDROM>: \RUN\VPI\TOOLEXAM\OWN_DRAW на прилагаемом CD-диске. Специальные элементы управления Помимо переносимых встроенных элементов управления, на большинстве платформ можно создавать и использовать непереносимые специальные элементы управления. Специальные элементы управления обычно распространяются как библиотеки ди- намической компоновки (DLL, Dynamic Link Libraries). В этом случае вы должны иметь файл с расширением lib для специального элемента управления, скомпилиро- вать его в свой исполняемый модуль (файл с расширением ехе) и знать функцию для инициализации DLL. Если вы создаете окно специального элемента управления программно, вам нужно использовать предикат win CreateDynControl (листинг 26.13). CustomCtl = win_CreateDynControl( [customctl (wdef (wc_Custoin, RCT, Title, u_Pixels) , Controlclass, 101, Flags)], Window, 0), Имя класса Controlclass — это строка, которая определяет класс специального окна для элемента управления. Если вы хотите разместить специальный элемент управления в диалоговом окне, используя редактор окон и диалоговых окон VDE, выберите команду Controls | Custom. В свойствах элемента управления нужно написать имя необходимого класса. Для специальных элементов управления список Flags должен быть отформатирован в соответствии со специальным соглашением. По соглашению список значений сти- лей Flags обрабатывается как список констант стилей VP1 до тех пор, пока не встре- тится нулевой элемент списка, после которого все последующие значения стилей объединяются вместе побитовым ИЛИ. Например, предыдущая переменная Flags, если используется один или более популярных управляющих элементов "сетки”, которые должны быть созданы невидимыми, могла быть определена следующим образом:
Глава 26. Средства создания графического интерфейса 751 FLAGS=[ wsf _ТаЬstop, wsf_Invisible, % Флаги vpi 0, % Переключающий 0 HGRID__ColumnSelect, % Определены в заголовочных файлах HGRID_AllowInsert], % Передаются с элементами управления Можно создавать динамические диалоговые окна с использованием специальных элементов управления, как это сделано в следующем примере (листинг 26.14), кото- рый использует популярные расширения элементов управления компании Borland. ' - - Л-- -r,: -:r f jJ ' " 'T ? С::?..:.?: :i;i: 1Йистинг26..14жЖЖ<,: ........ । Z > ЧФ . i4. У'!;., . . •• >• S ,, • j.-.-. . . j . Г ! ’ •.•?< .. , "• • • • * «ГГ >««>«>««»«> «*.>.>>>><>> kv.«.c««t« «4>:> 1 < « >41 *>>>«>>>> <, «.«>«>* T ¥/««««: Wlist = [dlg_font(wdef(wd_Modal, ret(10, 10, 210, 210), "Dialog", u_Pixels), "Helv", 8, [wsf_Close, wsf_TitleBar, wsf_Minimize, wsf_Maximize]), customctl(wdef(wc_Custom, ret(5, 5, 65, 30), "OK", u_Pixels), "BorBtn", 1, [wsf—Default, wsf_TabStop]), customctl(wdef(wc_Custom, ret(5, 35, 65, 50), "Check box", u_Pixels), "BorCheck", 102, [wsf_TabStop]), customctl(wdef(wc_Custom, ret(5, 55, 65, 70), "Radio", u_Pixels), "BorRadio", 103, [wsf_TabStop]), customctl(wdef(wc_Custom, ret(5, 32, 65, 34), "", u_Pixels), "BorShade", 104, [wsf-TabStop]), ctl(wdef(wc_PushButton, ret(5, 80, 65, 95), "NotCustom", u_Pixels), 105, [wsf_TabStop]), customctl(wdef(wc_Custom, ret(-4, -4, 204, 204), "", u_Pixels), "BorShade", 110, [wsf_TabStop])], win_CreateDynDialog(Wlist, dialog_event_handler, b_False). Обратите внимание, что специальные элементы управления не полностью исполь- зуются в структуре VPI. Таким образом, если они посылают нестандартные сообще- ния обработчику событий диалогового окна, то эти события придут как e_Native, которые должны быть декодированы согласно специфическим константам или дру- гой информации, предоставляемой поставщиком для использования вместе с этими программными продуктами. Вообще, вы должны убедиться, что классы специальных элементов управления должным образом инициализированы перед их использованием в диалоговом окне. Обработчики событий для этих элементов управления обычно реализованы в файлах с расширением dll. Часто бывает необходимо вызвать одну или больше функций инициализации в DLL-файлах, чтобы проинициализировать классы элементов управления. Некоторые DLL выполняют это в скрытой форме, которая зависит от порядка связывания импортных библиотек LIB для DLL. Если вы используете биб- лиотеку элементов управления третьей стороны и сталкиваетесь с ошибкой при по- пытке использовать диалоговое окно, вам следует просмотреть документацию на этот элемент управления, чтобы убедиться, что вы правильно вызываете функцию инициализации; проверить ваши сценарии построения и компоновки (Build scripts), чтобы гарантировать, что импортные библиотеки для DLL (обычно это файлы с та- ким же именем и расширением lib) существуют и, возможно, они должны быть скомпонованы ранее остальных библиотек.
752 Часть V. Разработка графического интерфейса пользователя Элементы управления VBX — более сложное расширение таких же концепций, где классы элементов управления не только должны быть инициализированы, но и ба- зовая среда (в данном случае ваша программа Visual Prolog) должна быть средой вы- полнения Visual Basic с точки зрения протоколов правильного порядка сообщений, ожидаемых элементами управления VBX. Поэтому использование элемента управле- ния VBX требует создания связи с библиотекой эмуляции (такие библиотеки постав- ляются вместе с Visual С, Borland С или могут быть приобретены отдельно у третьей стороны). Обратите внимание, что кроме специальных требований инициализации, VPI-программирование одинаково и для этих и для более простых специальных эле- ментов управления. Кроме того, специальные элементы управления могут быть созданы в Visual Prolog вызовом предиката class_Create (подробнее см. разд. "Классы окон" данной главы). Диалоговые окна Диалоговые окна — это специальный вид окон. Диалоговые окна обычно заполнены элементами управления, которые могут взаимодействовать с пользователем посред- ством отображения выходных данных и приема входных данных. VPI обеспечивает набор "стандартных диалоговых окон" или удобных предопреде- ленных диалоговых окон и набор обработчиков с простым интерфейсом вызовов функций для часто встречающихся ситуаций. Все предикаты VPI в этой группе на- чинаются с префикса dlg_ и могут быть легко вставлены в исходный код при помо- щи меню правой кнопки мыши. Если нет подходящего стандартного диалогового окна, то можно спроектировать свое диалоговое окно в редакторе окон и диалоговых окон VDE. Это диалоговое окно можно сохранить в файле ресурсов (RES-файл). Диалоговое окно можно соз- дать и динамическим вызовом нескольких предикатов, или вызовом предиката win_CreateControl для создания элементов управления из windef„list. Какой бы метод не использовался, эксперт диалоговых окон VDE может сгенерировать струк- туру кода. Установка, получение или изменение содержимого элементов управления в диалого- вом окне могут быть выполнены как поэлементно с использованием предикатов VPI, так и на основе отдельного диалогового окна с помощью пакета диалоговых окон VPI. Пакет диалоговых окон описан в приложении 2. В диалоговых окнах, в отличие от обычных окон, существует возможность переме- щаться между элементами управления при помощи клавиши <ТаЬ> и комбинации клавиш <Shift>+<Tab>. Напомним, что существует два вида диалоговых окон: немодальные и модальные. Одновременно на экране может находиться любое число немодальных диалоговых окон и каждое из них в любой момент может быть активизировано пользователем. Модальное же диалоговое окно должно быть закрыто прежде, чем пользователь сможет активизировать любое другое окно, меню или элемент управления в прило- жении. В операционной системе MS Windows диалоговые окна всегда будут появляться и оставаться перед их родительскими окнами и не будут соблюдать их границы.
Гпава 26. Средства создания графического интерфейса 753 Стандартные диалоговые окна Стандартные диалоговые окна ускоряют и облегчают разработку приложений. Стан- дартное диалоговое окно может быть создано приложением при помощи простого вызова одного предиката. Обычные же диалоговые окна требуют обработчика собы- тий и описания ресурсов, содержащих вид диалогового окна. Вызовы стандартных диалоговых окон могут быть вставлены в приложение при помощи команды Edit | Insert | Predicate call | Window, Dialog or Toolbar. Получение ответа пользователя на вопрос Yes/No и Cancel Integer = dlg_Ask(PromptString, ButtonTitleList) Integer = dlg_Ask(Titlestring, Promptstring, ButtonTitleList) Integer = dlg_Ask(Parentwindow, Titlestring, Promptstring, ButtonTitleList) Предикат dlg Ask отображает небольшое диалоговое окно, которое имеет три кноп- ки. Названия кнопок задаются в списке ButtonTitleList. Обычно это кнопки Yes, No и Cancel. Возможные ответы соответствуют порядку в списке ButtonTitleList и определяют, какая кнопка была нажата. resp_default = О resp_2 = 1 resp__3 = 2 Например, следующий вызов: Answer - dlg_Ask ("MyTitle”, "MyPrompt", ["MyButtonl", "MyButton2”, "MyButton3"]) отобразит диалоговое окно, показанное на рис. 26.3. Рис. 26.3. Стандартное диалоговое окно dlg_Ask Выбор файла для открытия/сохранения string /*FirstFileName=*/ dlg_GetFileName ( string InitMask, slist Types, Title, ilist Flags, StartPath, slist SelectedFileNames) Предикат dlg GetFileName используется для подготовки диалогового окна, где поль- зователь может выбрать файл для открытия/сохранения.
754 Часть И Разработка графического интерфейса пользователя Здесь initMask — заданная по умолчанию маска файла. Types ~ список альтерна- тивных масок файлов, которые пользователь может выбрать. Title — определяет заголовок диалогового окна. Flags — список флагов, определяющих свойства диало- гового окна. startPath — определяет начальный каталог для поиска файлов. Если StartPath = используется текущий каталог. Предикат FirstFileName возвращает полное имя первого выбранного файла. Предикат dlg_GetFileName позволяет пользователю выбирать список файлов, если определен флаг dlgfn_MultiSel в списке Flags. SelectedFileNames — выходной па- раметр, содержащий список всех выбранных файлов. Если определен флаг dlgfnjSave, то будет отображено диалоговое окно Save As. Если выбранный файл уже существует, то диалоговое окно Save As генерирует окно сообщения, в котором пользователь должен подтвердить перезапись файла. Если флаг dlgfnSave не опре- делен, то отображается диалоговое окно Open File (по умолчанию). Например, рассмотрим вызов из листинга 26.15. » i'Н !• i i ! • '! I Flags = [dlgfn_MultiSel], FirstFileName = dlg_GetFi1eName("*.pro", ["Prolog Files (*.pro).pro","Prolog Headers (*.pre,)","*.pre", "All Files (*.*)", ]f "MyTitle",Flags,"",OutFileNames) Он приведет к диалоговому окну, показанному на рис. 26.4. Рис. 26.4. Стандартное диалоговое окно dlg_GetFileName Ввод строки String = dlg_GetStr(Title, Messagestring,InitString) String = dlg_GetStr(Parentwindow, Title, Messagestring, InitString) Предикат dlg_Getstr используется для ввода строки.
Гпава 26. Средства создания графического интерфейса 755 Следующий предикат: _NewString = dlg_GetStr("MyTitle","MyMessage"f "InitStr") приведет к диалоговому окну, показанному на рис. 26.5. Рис. 26.5. Стандартное диалоговое окно dlg_GetStr Выбор из списка строк Boolean = dlg_ListSelect(Title, StringList, PreSelectionindexinteger, SelectedString, Indexinteger) Boolean = dlg_ListSelect(Parentwindow, Title, StringList, PreSelectionindexinteger, SelectedString, Indexinteger) Boolean = dlg_ListSelect(Parentwindow, Title, StringList, PreSelectionindexinteger, SelectedString, Indexinteger, boolean Sort) Предикат dlg ListSelect используется для выбора элемента из списка значений. Например, рассмотрим следующий вызов: Slist=["First string", "Second String", "Third string"], PreSel= 1, dlg_ListSelect ("MyTitle", Slist, PreSei, __StrSel, _lndex) Он приведет к диалоговому окну, показанному на рис. 26.6. Рис. 26.6. Стандартное диалоговое окно dlg_ListSelect
756 ЧастЪ-У. Разработка графического интерфейса пользователя Выбор и настройка принтера dlg_PrintSetup(NoOfCopiesInteger, FirstPagelnteger, LastPagelnteger) Предикат dlg_PrintSetup используется для выбора принтера. Вызов предиката воз- вращает число копий и желаемый диапазон страниц. dl g__Print Set up (_NoOf Copies, _FirstPage, _LastPage) В результате появится диалоговое окно, показанное на рис. 26.7. Рис. 26.7. Стандартное диалоговое окно dlg_PrintSetup Выбор цвета Color - dlg_ChooseColor(OldColor) Color dlg_ChooseColor(Parentwindow, OldColor) Предикат dlg_ChooseColor используется для выбора цвета пользователем. InitColor = color_Red, _NewCOLOR = dlg_ChooseColor(InitCOLOR) В результате будет отображено диалоговое окно, показанное на рис. 26.8. Предикат dlg_ChooseColor/2 используется для определения окна Parentwindow, ко- торое получит фокус после закрытия диалогового окна. Выбор шрифта NewFontBinary = dlg_ChooseFont(OldFontBinary) NewFontBinary = dlg_ChooseFont( font OldFont, string FontName, integer FontSize) Предикат dlg_ChooseFont используется для выбора шрифта пользователем OldFont = font—Create (ff_fixed, [], 9) , _NewFont = dlg_ChooseFont(OldFont) В результате будет отображено диалоговое окно, показанное на рис. 26.9.
Гпава 26. Средства создания графического интерфейса 757 Рис. 26.8. Стандартное диалоговое окно dlg_ChooseColor Eorrt: Font style; реЗЗаГ"1 Courier Ne Courier New Regular Ф Desdemona Fixedsys Д’ Footlight MT Light It Impact Kino MT Д Lucida Console Italic Bold Bold Italic 10 12 16 18 >,4 33: .Sample J*7. « .>7 / >• . ''JJ’ •f Script IWestern OK Cancel Й' 'С 9 г 3 3 3 t.. A ! J i>: ; ' ! и' :!. : :-f 1МММММ Рис. 26.9. Стандартное диалоговое окно dlg_ChooseFont Отображение ошибки dlg_Error(Messagestring) Если программе нужно показать сообщение об ошибке, должен быть вызван преди- кат dlg Error. Например, следующий вызов: dlg_Error("Му Error Message") приведет к диалоговому окну, показанному на рис. 26.10.
758 Часть И Разработка графического интерфейса пользователя Рис. 26.10. Стандартное диалоговое окно dlg_Error Отображение примечания dlg_Note(Messagestring) dlg_ _Note(Titlestring, Messagestring) Предикат dlg_Note удобен для отображения маленького окна с коротким сообщени- ем. Например, следующий вызов: dlg__Note ( "MyTitle”, "Hello There" ) приведет к диалоговому окну, показанному на рис. 26.11. Рис. 26.11. Стандартное диалоговое окно dlg_Note Диалоговые окна, определенные программистом Если нет подходящего стандартного диалогового окна, то программист может спро- ектировать свое диалоговое окно. Идентификаторы элементов управления Каждый элемент управления в диалоговом окне должен иметь идентификатор (целое число), который уникален в пределах диалогового окна. Этот идентификатор назы- вается идентификатором элемента управления (Ctrlid). Целое значение, соответст- вующее идентификатору элемента управления, используется в сообщениях уведом- ления родительских окон. Идентификатор элемента управления определяет, какой элемент породил событие. Если новый элемент управления размещается в диалоговом окне при помощи редак- тора окон и диалоговых окон VDE, он получает именованный идентификатор- константу. Во время компиляции VDE генерирует файл <project>.CON, который связывает целые значения со всеми константами-идентификаторами ресурсов эле-
Гпава 26. Средства создания графического интерфейса 759 ментов управления согласно конкретным правилам. Этот файл включен во все мо- дули, которые используют VPI. Во время построения проекта VDE генерирует файл < projects RES, в котором все элементы управления идентифицируются только этими целыми значениями. Специальные идентификаторы ресурсов элементов управления Следующие идентификаторы ресурсов должны использоваться для всех кнопок ОК, Cancel и Help. Константы предопределены в файле VPI.CON. idc_ok=l idc_cancel=2 idc_help = 3 При использовании пакета диалоговых окон важно, чтобы диалоговое окно имело эти три кнопки с надлежащими идентификаторами, потому что предполагается, что эти кнопки существуют, и используются для закрытия диалогового окна. Создание диалогового окна Если диалоговое окно было сгенерировано при помощи редактора диалоговых окон Visual Prolog и размещено в сегменте ресурсов, то создать диалоговое окно можно при помощи вызова: Window = win_CreateResDialog( Parentwindow, Windowtype, Resld, EventHandler, CreationData) Здесь Parentwindow — это окно, которое получит фокус после закрытия диалогового окна, windowtype должен быть равен wd Modal или wd_ModeLess в зависимости от того, какое нужно диалоговое окно. Resld — целое число, которое определяет диало- говое окно в RES-файле, EventHandler — предикат обратного вызова, который ис- пользуется для обработки событий от диалогового окна, CreationData — значение типа long, которое передается событию е__Сreate. S Также можно создать диалоговое окно из списковой структуры вызовом: Window = win_CreateDynDialog(Parentwindow, Windef_list, EventHandler, CreationData) Другой способ создания диалоговых окон — создание непосредственно диалогового окна при помощи предиката win Create, а затем поочередное создание элементов управления вызовом предиката win_CreateControl. Инициализация диалогового окна При создании диалогового окна обработчику событий диалогового окна посылается событие e_Create. В предложении, обрабатывающем событие e Create, должны быть проинициализированы все элементы управления диалогового окна. Для каждо- го типа элементов управления существуют различные предикаты, которые исполь- зуются для установки значений элементов управления (табл. 26.10).
760 Часть V. Разработка графического интерфейса пользователя Таблица 26.10. Предикаты для работы с элементами управления Тип элемента управления Предикаты Флажки Полосы прокрутки Поля редактирования Статический текст Списки win_Check win_SetScrollPos win_SetText win_SetText Все предикаты с префиксом Ibox Внимание! Все эти предикаты работают с оконными идентификаторами для элементов управ- ления, а не с идентификаторами самих элементов управления. Операции рисования Для изображения текста, пикселов и различных геометрических фигур существует несколько предикатов VPI, которые начинаются с префикса draw_. Операции рисо- вания в окне используют текущие настройки рисования для данного окна, к кото- рым обращаются как к инструментам рисования. Инструментами являются: перо (Реп), кисть (Brush), режим рисования (Drawmode), шрифт (Font), цвет изображения (Foreground Color), цвет фона (Background Color) и режим заполнения фона (Background Filling Mode). Пример в каталоге <CDROM>:\RUN\VPI\EXAMPLES\DRAWING на прилагаемом CD-диске показывает различные комбинации примитивов рисования, свойств инст- рументов и иллюстрирует соответствующие результаты. Инструменты рисования Настройки всех текущих инструментов рисования для окна можно записать в от- дельную переменную и затем использовать ее для восстановления этих свойств при помощи предикатов win_GetDrawTools/l и win_SetDrawTools/2 (листинг 26.16). SaveTools = win_GetDrawTools(Win), % Изменяем настройки рисования и рисуем с различными свойствами win_SetDrawTools(Window, SaveTools), % Восстанавливаем сохраненные настройки Компоненты объекта инструментов рисования определены в домене drawtools: global domains drawtools = draw_tools( pen,
Глава 26. Средства создания графического интерфейса 761 brush, drawmode, font, color foreground, color background, bk__mode) Перья Линии и контуры фигур рисуются при помощи перьев (PEN). Параметры линии, например ширина, стиль и цвет, могут быть изменены установкой атрибутов пера перед его использованием (табл. 26.11). Для перьев существует специальный домен: global domains penWidth = integer pen = pen(penWidth, penStyle, color) Таблица 26.11. Стили пера Стиль ps_Hollow ps_Solid ps_Dash ps_Dot ps_DashDot ps_DashDotDot Значение Прозрачная линия Сплошная линия Пунктирная линия Точечная линия Штрих-пунктирная линия Штрих-пунктирно-пунктирная линия Получение текущего пера Предикат win GetPen возвращает текущее перо. Часто он используется для времен- ного изменения пера и восстановления старого пера после рисования. Pen ~ win_GetPen (Window) Установка пера текущим Предикат win_SetPen/2 изменяет перо для данного окна. Следующий код устанавли- вает перо со сплошной красной линией и шириной 2 пиксела. PenWidth = 2, Pen = pen(PenWidth, ps_Solid, color_Red), win SetPen(Window, Pen)
762 Часть V. Разработка графического интерфейса пользователя Режимы фона Текст отображается текущим цветом изображения, и если домен bkjnode имеет зна- чение bk Opaque, то фон будет залит текущим цветом фона. При использовании зна- чения b’^Transparent цвет фона будет прозрачным (листинг 26.17). • <«••••? J Листинг26.17 global domains bkjnode = integer constants bkJTransparent == 1 bk_Opaque = 2 Для изменения текущего режима фона используется предикат: win_SetBackMode(Window, Bk_Mode) Кисти Для закрашивания фигур используются кисти. Параметрами кисти являются цвет и стиль узора-заполнителя. global domains brush = brush(patStyle, color) Стили узора-заполнителя и их описание приведены в табл. 26.12. Основные цвета перечислены ниже,-в табл. 26.15. Таблица 26.12. Стили узора-заполнителя Идентификатор стиля Значение pat__Hollow pat__Solid pat__Horz pat__ Vert pat__Fdiag pat__Bdiag pat__Cross pat_DiagCros s Нет заполнителя Однотонная кисть определенного цвета Горизонтальная штриховка Вертикальная штриховка Диагональная штриховка снизу вверх (слева направо) Диагональная штриховка сверху вниз (слева направо) Горизонтальная и вертикальная перекрестная штриховка Диагональная штриховка крест-накрест Получение и установка текущей кисти Предикат win_GetBrush возвращает текущую кисть. CurrentBrush - win GetBrush(Window)
Гпава 26. Средства создания графического интерфейса 763 Предикат win_SetBrush/2 изменяет текущую кисть для окна. win_SetBrush(Window, NewBrush) Шрифты Текущий шрифт изменяется вызовом предиката: win_SetFont(Window, FontBinary) Вы можете получить текущий шрифт вызовом предиката: FontBinary - win_GetFont(Window) Для получения сведений о том, как создавать и выбирать шрифты, обратитесь к разд. "Работа со шрифтами " данной главы. Режимы рисования Режим рисования определяет, как пикселы, которые отображаются на экране, со- четаются с уже нарисованными. Свойства режимов рисования перечислены в табл. 26.13. Обычный режим рисования — это dm_CopyPen, который только присваивает пикселам новые значения. Константа dm XorPen или dmjtfot используются для рисования 'ре- зиновых лент", где эффект растяжения создается за счет рисования прямоугольника и последующего затирания его с помощью повторного рисования (см. пример кода в каталоге <CDROM>:\RUN\VPI\EXAMPLES\MAPPING). Получение и установка текущего режима рисования Предикат win_GetDrawMode/l возвращает информацию о текущем режиме рисования. DrawMode = win_GetDrawMode(Window) Предикат win_SetDrawMode/2 устанавливает новый режим рисования. win_SetDrawMode(Window, dm_Copy) Доступные режимы рисования приведены в табл. 26.13. Таблица 26.13. Доступные режимы рисования Идентификатор режима Значение dm__Black dm__N о t Me г g еР еп dm MaskNotPen dm_NotCopyPen dm_MaskPenNot dm_Not dm_XorPen Черный Not (Перо | Пиксел экрана) Not (Пиксел экрана) & Перо Not (Перо) (Not (Перо)) & Пиксел экрана Not (Экран) Перо Хог Пиксел экрана
764 Часть И Разработка графического интерфейса пользователя Идентификатор режима Значение Таблица 26.13 (окончание) dm_NоtMaskPen dm_MaskPen dm_NotXorPen dm_Nop dm_MergeNоtPen dm_CopyPen dm_MergePenNot dm_MergePen dm_White Not (Перо & Пиксел экрана) Перо & Пиксел экрана Not (Перо Хог Пиксел экрана) Экран не изменяется Not (Пиксел экрана) | Перо Перо Not (Перо) | Пиксел экрана Перо | Пиксел экрана Белый Предикаты рисования Рассмотрим подробнее доступные операции рисования. Если вам нужно работать с битовыми массивами или графиками, см. разд. "Изображения" данной главы. Рисование пикселов Предикат draw_Pixel/3 устанавливает заданный цвет пиксела: draw_Pixel {window Window, pnt Position, color Color) Обратная операция выполняется при помощи предиката: Color = Win_GetPixel{Window, pnt{X, Y)) Этот предикат возвращает цвет пиксела с координатами (X, Y) в окне Window. Закрашивание области draw_FloodFill{Window, Pnt, Stopcolor) Предикат draw FloodFill устанавливает цвет пиксела с координатами (X, Y) равным цвету, определенному последней операцией win_SetBrush/2 для окна. Операция за- крашивания рекурсивно применяется ко всем соседним пикселам до тех пор, пока не будут достигнуты пикселы с цветом stopcolor. Если такие пикселы не встретятся, то окно рисования будет закрашено полностью. Изображение пиктограмм draw__Icon {Window, X, Y, Resourceld) Этот предикат получает пиктограмму с заданным идентификатором ресурса из сег- мента ресурсов ЕХЕ-файла активного приложения и отображает ее. Верхний левый угол пиктограммы размещается в точке с логическими координатами (X, Y).
Гпава 26. Средства создания графического интерфейса?65 Изображение контуров фигур draw_Line(Window, Pnt, Pnt) draw_Arc (Window, Ret, integer StartX, integer StartY, integer StopX, integer StopY) draw_Polyline(Window, PntList) Эти предикаты рисуют только контур фигуры с использованием пера и не закраши- вают кистью внутреннюю область фигуры. Изображение закрашенных фигур draw_Ellipse(Window, Ret) draw_Pie(Window, Ret, integer StartX, integer StartY, integer StopX, integer StopY) draw_Polygon(Window, PntList) draw_Rect(Window, Ret) draw_RoundRect(Window, Ret, integer Ellipsewidth, integer EllipseHeight) Контур фигуры рисуется текущим пером, а внутренняя область фигуры автомати- чески закрашивается текущей кистью. Вывод текста При выводе текста на экран существует много настраиваемых свойств. drawJText (Window, X, Y, String) draw_Text (Window, X, Y, String, DrawLength) Предикат draw_Text выводит первые DrawLength символов из строки string в окно Window, где х и Y — логические координаты верхнего левого угла первой буквы в строке. Параметр DrawLength можно не заполнять или установить его равным — 1 для того, чтобы вывести всю строку. drawJTextInRect(Window, Ret, String, DrawLength, ilist DrawFlags) Функция draw_TextlnRect выводит первые DrawLength символов из строки String внутри ограничивающего прямоугольника, используя текущий шрифт. Варианты dtext_Flag показаны в табл. 26.14. Таблица 26.14. Флаги, определяющие параметры вывода текста Идентификатор флага Значение dtext__center Центрирует текст по горизонтали в прямоугольнике dtext left Выравнивает текст по левому краю прямоугольника dtext_right Выравнивает текст по правому краю прямоугольника dtext_vcenter Центрирует текст по вертикали. Работает только в сочетании с константой dtext singleline
766 Часть V. Разработка графического интерфейса пользователя Таблица 26.14 (окончание) Идентификатор флага dtext_bottom dtext_top dtext_singleline dtext_noclip dtext_wordbreak dtext_expandtabs dtext_noprefix dtext_externalleading Значение Выравнивает текст по нижнему краю прямоугольника. Рабо- тает только в сочетании с константой dtext_singleline Выравнивает текст по верхнему краю прямоугольника Определяет одиночную строку. Символы '\п' игнорируются Выводит текст без отсечения по границе прямоугольника Выводит текст без переноса слов Расширяет символы табуляции Отключает обработку префиксных символов & Отключает настройку размера шрифта к высоте строки Размер текста Часто важно заранее знать, каков будет размер текстовой строки. Предикат win_GetTextExtent/5 возвращает высоту и ширину (в логических единицах) прямо- угольника, который мог бы содержать данный текст. Третий параметр содержит чис- ло символов, которые должны быть измерены. Если установить этот параметр рав- ным — 1, то в измерение будет включена вся строка (листинг 26.18). г Листинг 26.18 Text = "This is a test", win_GetTextExtent(Window, Text, -1, Width, Height) Цвет текста Для изменения цвета изображения и фона текста используются два предиката: win_SetBackColor(Window, Color) win_SetForeColor(Window, Color) Список идентификаторов для основных цветов приведен в табл. 26.15. Не существу- ет предикатов для непосредственного получения текущих цветов изображения и фо- на, однако они могут быть извлечены из структуры draw_toois, возвращаемой пре- дикатом win_GetDrawTools для данного окна. Если текущий стиль фона bk_Opaque, то прямоугольник закрашивается цветом, который был установлен последним вызовом предиката win_setBackColor/2. Если стиль фона bkjrransparent, то текст выводится на существующем фоне (лис- тинг 26.19).
Глава 26. Средства создания графического интерфейса 767 j Листинг 26.19 % Выводит текст белым цветом на красном фоне win_SetForeColor(Window, color_White), win_SetBackColor(Window, color_Yellow), win_SetBackMode(Window, bk_Opaque), Text ~ "This is some text" X = 20, Y -- 20, draw_Text(Window, X, Y, Text) Работа co шрифтами У каждого окна есть шрифт, который используется при выводе текста. Этот шрифт может быть изменен вызовом предиката win_SetFont. Пользователь может выбрать шрифт в стандартном диалоговом окне настройки шрифтов. Также шрифт можно создать, задав семейство, стиль и размер шрифта и вызвав предикат font_Create. Шрифт — это бинарный домен VPI. Это значит, что он может быть сохранен в фай- ле или внешней базе данных и использован позже. У нас нет возможности прямо проверить настройки шрифта, но при помощи предиката font_GetAttrs можно по- лучить некоторую информацию. Создание шрифта Font = font_Create(FontFamily, StyleFlags, Size) Этот предикат возвращает структуру шрифта. Возможные семейства шрифтов: □ ff_System,* □ ff_Fixed; □ ff_Times; О ff_Helvetica. Флаги стиля шрифта: □ fs_Bold; □ fs__ltalic; □ fs_UnderLine. Параметр StyleFlags — это список, поэтому можно определить один или больше стилей. Пустой список создаст обычный шрифт. Выбор шрифта Для установки нового шрифта в окне используется предикат win_SetFont/2. win_SetFont(Window, Font)
768 Часть V. Разработка графического интерфейса пользователя Для изменения параметров шрифта используется предикат: font_SetAttrs(Font, Fontstyle, Fontsize) Получение информации о шрифте Размер текущего шрифта можно получить, вызвав предикат win_GetFontMetrics/4. win__Get Font Met ries (Window, Leading/ Ascent, Descent) Здесь Window — это дескриптор окна, Leading — дополнительное расстояние, кото- рое приложение добавляет между строками. Ascent — расстояние между базовой линией и верхним краем символа. Descent — расстояние между нижним краем сим- вола и базовой линией. Стиль текущего шрифта можно получить, вызвав предикат font_GetAttrs/3. FontName = font_GetAttrs(Font, StyleFlags, Fontsize) Возвращаемые параметры: StyleFlags — список, Fontsize — целочисленное значе- ние, FontName — название шрифта. Текущий шрифт окна можно получить, вызвав предикат: Font = win__GetFont (Window) Работа с цветом VPI использует цветовую модель RGB, которая позволяет работать с 16 миллионами цветов. Вы составляете необходимый цвет из трех значений, определяющих компо- ненты красного (Red) (от 0 до 255), зеленого (Green) (от 0 до 255) и голубого (Blue) (от 0 до 255). В противоположность субтрактивному (subtractive) закрашиванию, при котором каж- дый добавляемый цвет перекрывает предыдущий, существует и аддитивное (additive) закрашивание с использованием цвета, получаемого сложением значений интенсив- ности для каждого из основных цветов: красного, зеленого и синего. Например, ес- ли минимальная интенсивность на данном экране была определена как 0, а макси- мальная ~ как 255, то белый пиксел будет задаваться RGB-тройкой (255, 255, 255), черный пиксел — тройкой (0, 0, 0) и голубой пиксел — тройкой (0, 255, 255). Количество воспроизводимых цветов зависит от видеоадаптера и принтера. Черно- белый принтер выполняет печать в черно-белом цвете, а видеокарта, возможно, вос- производит только 16 цветов. Однако программное обеспечение GUI предусматри- вает более сложные цветовые схемы и составы, поэтому если вы попытаетесь нари- совать 2000 прямоугольников разного цвета, то GUI сможет распределять цвета и пикселы в этих прямоугольниках так, чтобы приблизиться к 2000 различных цветов. В черно-белых устройствах для отображения полутонов используются оттенки серо- го цвета. Для обеспечения дополнительной гибкости в цветовой гамме также применяется технология палитры. Цветовые палитры используются устройствами, которые спо- собны порождать множество цветов, но в данный момент времени отображают толь-
Гпава 26. Средства создания графического интерфейса 769 ко часть из них. Палитра — массив цветов, используемых в изображении. Она по- зволяет определить каждый цвет заданием его компонентов. Если видеокарта одно- временно поддерживает, например, 256 цветов, это означает, что она может пра- вильно отображать только 256 цветов, а палитра определяет, какие из 256 цветов могут быть отображены. Система управления окнами имеет одну глобальную сис- темную палитру, а каждое окно может иметь локальную палитру. Система управле- ния окнами старается отображать цвета в активном окне как можно правильнее. Это делает возможным, например, установить 200 из 256 доступных цветов видеокарты в различные оттенки синего, в сочетании с другими 56 цветами, доступными для дру- гих окон на экране. Забота об обработке цвета ложится на аппаратные средства: чем они лучше, тем лучше внешний вид экрана. За исключением отображения многоцветных рисунков, программист может выбрать любой цвет для элементов GUL Однако из-за выше- упомянутого ограничения на количество одновременно используемых цветов, а так- же для обеспечения переносимости на различные компьютеры, будет полезным ог- раничиться использованием цветов, определенных в файле VP1.CON. Эти цвета приведены в табл. 26.15. Таблица 26.15. Идентификаторы стандартных цветов Идентификатор цвета Значение(B-G-R) color_Red color_Green color_Blue color_Cyan color_Magenta color_Yellow color_Black color_DkGray color_Gray color LtGra\ color White Красный Зеленый Синий Г олубой Фиолетовый Желтый Черый Темно-серый Серый Светло-серый Белый OxOOOOFF OxOOFFOO OxFFOOOO OxFFFFOO OxFFOOFF OxOOFFFF 0x000000 0x404040 0x808080 OxCOCOCO OxFFFFFF Если вы хотите, чтобы приложение хорошо смотрелось при использовании черно- белого оборудования, используйте цвета color_Black, color_DkGray, color~Gray, color_LtGray и color_white. Для того чтобы проверить, поддерживается ли дан- ный цвет аппаратным обеспечением компьютера, нужно использовать атрибут attr_have_color в предикате vpi GetAttrVal. Кроме того, убедитесь, что цвета име- ют достаточно большой контраст, чтобы соседние цвета не сливались в один.
770 Часть V, Разработка графического интерфейса пользователя Предикат: Color = vpi_ComposeRGB(Red, Green, Blue) может использоваться для создания цвета из основных цветов; красного, зеленого и синего. Яркость каждого основного цвета определена целым числом, но значимыми являются только 8 младших битов, давая диапазон от 0 до 255. Стандартный предикат; Color = dlg_ChooseColor(color OldColor) позволяет пользователю выбрать любой из возможных цветов. В примере <CDROM>:\RUN\VPI\EXAMPLES\DRAWING на прилагаемом CD-диске проиллюстрирована и объяснена работа с цветом для различных предикатов рисования. Пример <CDROM>:\RUN\VPI\EXAMPLES\Colors показывает, как нарисовать множество прямоугольников различного цвета, как составлять и анализировать цве- та, и иллюстрирует, что цвет, полученный вызовом предиката win_GetPixel, может не совпадать с цветом, определенным во время рисования в соответствии с числом доступных цветов видеоадаптера. При выборе цветов для окна и других элементов приложение должно использовать настройки (цветовую схему), выбранные по умолчанию, для GUI-компьютера. По- этому существуют несколько атрибутов, которые могут использоваться для получе- ния этих цветов. Например, атрибут attr_color_menu возвращает цвет для меню, а атрибут attr_color_window — цвет фона окна. Системы координат В заданной по умолчанию логической системе координат, используемой в VPI, ось X направлена слева направо, а ось Y — сверху вниз. По умолчанию, логическими еди- ницами являются элементы экранного изображения (пикселы). Точка (0, 0) — это пиксел, находящийся в верхнем левом углу области представления. Так как коорди- наты отсчитываются от нуля, любая попытка доступа к пикселу с координатами (Width, Height) будет отвергнута, поскольку он может находиться вне области пред- ставления и, фактически, на ограничивающем прямоугольнике. Существует также возможность работать с другими логическими единицами и системами координат — см, разд. "Масштабирование и панорамирование изображений" данной главы. При создании, перемещении или изменении размеров дочернего окна используются координаты относительно родительского окна (Parent Coordinates), а при работе с окнами Task и диалоговыми окнами — всегда экранные координаты (Screen Coordinates). При рисовании или выполнении любых подобных операций в окне используются координаты относительно клиентской области окна (Client Coordinates). Однако иногда нужно преобразовать координаты в одном окне к координатам дру- гого окна. Для этой цели существует предикат win MapPoints, который может преоб- разовывать координаты относительно одного окна в координаты относительно дру- гого окна: NewPntList = win_MapPoints(FromWindow, ToWindow, OldPntLists).
Гпава 26. Средства создания графического интерфейса 771 Например, если ToWin — родительское окно окна FromWindow, тогда win_MapPoints повторно вычисляет координаты точек клиентской области в координатах родитель- ского окна, а если ToWin — весь экран, тогда winjviapPoints преобразовывает коор- динаты клиентской области к экранным координатам (и наоборот). Напомним, что прямоугольники всегда задаются структурой ret(Left, Top, Right, Bottom). Замечание VPI не определяет прямоугольники в виде структуры (X, Y, Width, Height). Обычно координаты выражены в пикселах, которые удобны при работе с редакто- рами, меню, деревьями, панелями инструментов и т. д. Разрешающая способность экрана выражается как размер в пикселах по направлениям X и У (например, 640x480 или 1024x768), несмотря на то, что более правильным было бы указание числа пикселов в сантиметрах или дюймах. Шаг расположения точек измеряется расстоянием от центра одного пиксела до центра соседнего, что, конечно же, явля- ется инверсией к разрешающей способности. Экраны с более высокой разрешающей способностью имеют большее количество пикселов, что позволяет одновременно представлять больше информации на экране. Увеличенная вдвое разрешающая спо- собность позволила бы представить в 4 раза больше информации без потери качест- ва изображения, однако каждый элемент становится в 4 раза меньше, что затрудняет его рассмотрение. Диалоговые базовые единицы Размеры диалоговых окон и элементов управления обычно кратны выбранному для диалогового окна шрифту, являющемуся по умолчанию системным шрифтом, кото- рый может быть изменен пользователем. Это означает, что координаты диалоговых окон и элементов управления измеряются в диалоговых базовых единицах. При из- менении системного шрифта будет изменяться размер текстовых элементов управле- ния, соответственно и все диалоговое окно будет масштабировано. VPI поддерживает два типа диалоговых базовых единиц: Windows и платформо- зависимые. Предикат: vpi_GetBaseUnits(integer Width, integer Height) возвращает размер пиксела по горизонтали (ширина) и по вертикали (высота) в диа- логовых базовых единицах, присущих конкретной платформе. Они вычисляются как средние ширина/4 и высота/8 для символов системного шрифта. Эти значения нужно использовать для размещения и разметки окон и диалоговых окон, зависящих от системного шрифта. Предикаты win_CreateDyn, win_CreateDynControl и win__CreateDynDialog поддержи- вают возможность использовать оба типа диалоговых базовых единиц для вычисле- ния размеров и положений окон, диалоговых окон и элементов управления. Типы единиц должны быть определены в описании объектов (домен windef) флагами
772 Часть V, Разработка графического интерфейса пользователя u_DlgBase, u_DlgPlatform или u_Pixels в компоненте UType подструктуры wdef из домена windef. Использование диалоговых базовых единиц типа платформо-зависимых в этих пре- дикатах в случае многоплатформенных проектов имеет следующий недостаток. Если целевое приложение используется и в операционной системе Windows» и в OS/2, то ширина диалоговых окон и элементов управления под Windows будет приблизитель- но на 30% шире, чем под OS/2. Учитывая это, нужно использовать диалоговые базовые единицы типа Windows. Ширина диалоговых окон под Windows и OS/2 будет одинаковой. Однако в этом случае ширина текстовых строк под Windows будет на 30% шире, чем под OS/2. По- этому, если вы проектируете схему под OS/2, обязательно нужно проверить, как по- ведут себя текстовые строки элементов управления под Windows. Следующее соглашение используется для преобразования координат в пикселах к координатам в диалоговых базовых единицах типа платформо-зависимых: NatDBU_X - (Xpixel * 4) div NatDlgBaseWidth NatDBU_Y = (Ypixel * 8) div NatDlgBaseHeight как под Windows, так и под OS/2. Соотношения между диалоговыми базовыми единицами типов Windows и платфор- мо-зависимых следующие: □ под OS/2: WinDlgBaseWidth = 4/3*NatDlgBaseWidth WinDlgBaseHeight = NatDlgBaseHeight □ под Windows- WinDlgBaseWidth - 4/3*NatDlgBaseWidth WinDlgBaseHeight = NatDlgBaseHeight Масштабирование и панорамирование изображений Панорамирование и изменение масштаба изображения — все эти слова обозначают одну и ту же основную операцию — отображение. Она позволяет приложению изме- нять логические координаты и систему координат, которые преобразуются и мас- штабируются базовой системой GUI к реальным физическим координатам окна, принтера или факса, делая таким образом приложение независимым от используе- мых устройств. Как только для окна установлен режим отображения, приложение должно давать и получать логические координаты вместо физических координат реального устройст- ва. Например, если логическая координата X изменяется от 10 000 до 20 000, а логи- ческая координата Y — от 10 000 до 20 000, то преобразование должно быть таким, чтобы отображать эти координаты в реальные координаты пиксела окна (например, X в пределах от 0 до 349 и Y от 0 до 249).
Гпава 26. Средства создания графического интерфейса 773 * Рассмотрим вызов: win__SetMapMode (Window, MapMode) Предикат win_SetMapMode изменяет режим отображения окна. Возможные режимы отображения перечислены в табл. 26.16. По умолчанию всегда устанавливается режим mm_Text, при котором координаты со- ответствуют реальным пикселам на экране или принтере, mmjrext — это зависимый от устройства режим, в то время как другие режимы независимы от устройств. Режимы отображения Metric, English и Twips позволяют приложению работать с ре- альными физическими размерами, что особенно полезно для печати. Режим mm Arbitrary позволяет преобразовать любой диапазон логических координат к ре- альным координатам окна. Таблица 26.16. Режимы отображения Режим Преобразование Х/Направление Y/Направление mm Text р I mm_Arbitrary mm_HiMetric mm_LoMetric mm_HiEnglish mm_LoEnglish mm_Twips Логические = Физические (по умолчанию) Устанавливается предикатом win_SetMapScale() Логические = 0,01 мм Логические = 0,1 мм Логические = 0,001 дюйм Логические = 0,01 дюйм Логические = 1/20 точки (72 точки = 1 дюйм) Вправо Вправо Вправо Вправо Вправо Вправо Вниз Вверх Вверх Вверх Вверх Вверх Рассмотрим следующие вызовы: win_SetMapScale(Window, pnt DevOrg, pnt DevExt, pnt LogOrg, pnt LogExt) win_GetMapScale (Window, pnt DevOrg, pnt DevExt, pnt LogOrg, pnt LogExt) Предикат win_SetMapScale может быть использован для установки любого масштаба в логических координатах. win_SetMapScale( Window, % Целевое окно pnt DevOrg, % Физическое начало отсчета pnt DevExt, % Физические границы pnt LogOrg, pnt LogExt) % Желаемое логическое начало отсчета % Желаемые логическое границы Если режим — mm Arbitrary, то для координат X и Y может быть выполнено сле- дующее координатное преобразование: DevCoord = DevOrg + (LogCoord — LogOrg) * DevExt / LogExt
774 Часть V. Разработка графического интерфейса пользователя где LogCoord — это координата в логических единицах, a DevCoord — результирую- щее значение в единицах физических координат. Изменяя только параметр DevOrg, можно выполнить панорамирование (движение начала системы координат). Изменяя DevExt и LogExt, можно масштабировать изо- бражение. Оси с одинаковым масштабированием дают изотропную систему, а с разным — ани- зотропную систему. Следующий предикат может быть использован для получения текущего масштаба окна: win__GetMapScale (Window, pnt DevOrg, pnt DevExt, pnt LogOrg, pnt LogExt) Эти два предиката: LogPntList = win_DPtoLP(Window, pntList DevicePoints) DevPntList = win_LPtoDP(Window, pntList Logpoints) используются для преобразования координат устройства (пиксела экрана, точки принтера или факса) в логические координаты и обратно. Это полезно, т. к. значе- ния в событиях от мыши и в событиях перерисовки всегда даются в координатах пиксела. К сожалению, в 16-разрядной операционной системе Windows масштаб логических координат ограничен диапазоном —32 000<Х<+ 32 000 из-за того, что координаты обрабатываются как 16-разрядные целые. Примеры отображения В каталоге <CDROM>:\RUN\VP1\EXAMPLES\MAPPING на прилагаемом CD-диске имеется пример, показывающий, как нарисовать карту Дании, которая поддерживает увеличение и уменьшение масштаба. Имеется фактическая база данных многоуголь- ников, представляющих береговую линию в системе координат от —10 000 до + 10 000. Первоначально эта система координат преобразуется к координатам реаль- ного окна. Печать окна При создании подпрограмм печати окна часто существует такая проблема, что окно может состоять из нескольких дочерних окон, которые должны быть напечатаны отдельно различными подпрограммами. Решение состоит в том, чтобы использовать отображение для правильного расположения каждого дочернего окна на странице. Отображение также может масштабировать дочерние окна снова к естественному размеру из-за более высокой разрешающей способности принтера. Об использова- нии отображения для масштабирования данных при выводе на факс или принтер см. пример <CDROM>:\RUN\VPI\EXAMPLES\PRINT на прилагаемом CD-диске.
Гпава 26. Средства создания графического интерфейса 775 Печать Печать очень похожа на рисование в окне. Во время печати во всех функциях ри- сования используется специальный дескриптор окна принтера. Процесс печати ра- ботает с одной страницей, и рисование может происходить в произвольном месте на странице в любой последовательности. Существует стандартное диалоговое окно VPI dig PrintSetup (вызов диалогового окна Print), которое позволяет выбрать диапазон страниц для печати, разрешающую способность принтера, число копий и необходи- мость сопоставления. В этом же окне — выбор принтера (но не изменение заданно- го по умолчанию принтера), задание ориентации печати на странице (портретная или ландшафтная), размера бумаги, исходного лотка, способа передачи полутонов и т. д. Все это необходимо для вывода задания на печать. Для MS Windows заданный по умолчанию принтер может быть изменен только при- ложениями Windows или через панель управления Windows. Нельзя изменять прин- тер в приложениях VPI, которые уже запущены с использованием заданного по умолчанию принтера. Печать в Visual Prolog может быть выполнена с использовани- ем факсимильного драйвера. Современные системы управления окнами выполняют значительную работу, позво- ляя приложениям работать независимо от устройств. Система управления окнами "знает" все об обработке специальных сообщений принтеру: как установить полу- жирный или курсивный стили шрифта, как выполнять смену страниц и т. д. В рабо- те приложения нет принципиальной разницы между рисованием на экране, печатью на лазерном принтере, графопостроителе, печатающем устройстве типа "ромашка" или печатью на факсимильном устройстве. Основное различие между экраном и принтером состоит в том, что принтер обычно имеет более высокую разрешающую способность и может печатать только цветами из черно-белой гаммы. Q Замечание J Если не выполнить предварительных действий по масштабированию, то на принте- ре изображение может оказаться очень маленьким. Если приложение должно печатать в режиме WYSIWYG (What You See Is What You Get, "Что вижу, то и получаю"), то оно должно масштабировать печать, основанную на разрешающей способности монитора и принтера. Эта информация возвращается предикатом vpi_GetAttrVal, который обеспечивает возможность получать число то- чек на дюйм (DPI, Dots Per Inch) на экране и на принтере. Масштабирование при помощи этих значений делает рисование на бумаге подобным рисованию на экране. Для масштабирования печати, выполненной в координатах пиксела (листинг 26.20) может использоваться следующая структура: = Листинг 26.20 l* р • vp p > Нн p • • »p p p p if* p »»»if p p .«> r*p V ,> p " > p > p p H ll* »•*•» ,.,p, VresPrinter = vpi_GetAttrVal\attr_printer_vres) , HResPrinter = vpi_Get/-\ttrVal (attr_printer_hres), VResScreen = vpi_GetAttrVal(attr_screen_vres), HResScreen = vpi_GetAttrVal(attr_screen_hres), PRINTWIN = print_StartJob("Test printing. . . "),
776 Часть И Разработка графического интерфейса пользователя win_SetMapMode(Printwindow, mm__Arbitгагу) f winJSetMapScale(Printwindow, pnt(0, 0), pnt(VResPrinter, HResPrinter), pnt(0, 0)f pnt(VResScreen, HResScreen)), print_StartPage{Printwindow), draw__xxx (Printwindow), % Фактическое рисование страницы print_EndPage(Printwindow)f print_EndJob(Printwindow). Пример в каталоге <CDROM>:\RUN\VPI\EXAMPLES\PRINT показывает, как можно избежать трудностей с различием изображения в окне (на экране) и на прин- тере; а пример в каталоге <CDROM>:\RUN\VPI\EXAMPLES\PRINTTXT иллюстри- рует возможности печати текстовых файлов. VPI содержит семь предикатов для поддержки печати. Запуск задания на печать WINDOW = print_StartJob(Title) Для запуска нового задания на печать используется print startjob. Этот предикат возвращает дескриптор окна, который может применяться как целевой объект для подпрограмм рисования и других предикатов печати. Конец задания печати print_EndJob(Printwindow) По окончании печати должен быть вызван предикат print_EndJob. Установка ориентации страницы print_SetOrientation(IsOrientationLandscape) Перед началом печати может быть изменена ориентация страницы при помощи пре- диката print setorientation. По умолчанию установлена портретная ориентация. Определив параметр IsOrientationLandscape равным b_true, можно установить ландшафтную ориентацию страницы. Начало новой страницы print_StartPage(window Printwindow) В определенный промежуток времени приложение должно печатать только одну страницу. Перед началом печати страницы должен быть вызван предикат print_StartPage.
Глава 26. Средства создания графического интерфейса 777 Окончание печати страницы print_EndPage(window Printwindow) По окончании печати страницы вызывается предикат print_EndPage. Отмена задания принтеру print_AbortJob(Printwindow) Если приложению по какой-то причине нужно остановить печать, должен быть вы- зван предикат print_AbortJob. Во время печати файла на экране отображается диалоговое окно File printing. Поль- зователь может прервать печать, нажав кнопку Cancel. Диалоговое окно содержит две строки с текстом по умолчанию: Раде и Cancel. Эти строки имеют фиксирован- ные идентификаторы ресурса: vpi__strcon_prn_page и vpi_strcon_prn_cancel соот- ветственно, поэтому их легко редактировать, используя редактор строчных ресурсов. Получение настроек печати Вызвав предикат print_GetConfig: PrintConfig = print_GetConfig() можно получить информацию о пользовательских настройках, выполненных в диа- логовом окне Print. Возвращаемая запись конфигурации принтера PrintConfig со- держит информацию, которую предикат dlg_printsetup использует для инициализа- ции диалогового окна Print. Запись конфигурации PrintConfig предназначена толь- ко для дальнейшего использования в предикате: print_SetConfig(PrintConfig) Для восстановления настроек печати вы можете записать конфигурацию PrintConfig в файл и затем считать обратно, но помните, что содержимое записи конфигурации принтера зависит от используемой ОС и не переносимо между разными платформами. Курсор Курсор — это форма указателя для мыши или устройства с шаровым манипулятором. Каждое окно на экране имеет свой собственный курсор, который может быть изме- нен приложением. Набор курсоров для окна обычно или заранее задан, или опреде- лен в файле ресурсов. Существует 11 предопределенных типов курсоров. Эти константы принадлежат до- мену cursor и перечислены в табл. 26.17. Дополнительные курсоры можно определить в файле ресурсов. Курсорам, определяемым пользователем, нужно присваивать номера (идентификато- ры) ресурса, начиная с 12. VDE генерирует константу автоматически, когда вы реги- стрируете курсор при помощи кнопок Cursor и New в окне проекта. Курсоры в про-
778 Часть V. Разработка графического интерфейса пользователя екте могут быть созданы редактором изображений VDE или любым внешним инст- рументом, который может сохранять данные курсора в отдельном файле с расшире- нием cur. Таблица 26.17. Типы курсора Идентификатор курсора Описание курсора cursor—Arrow cursor_Cross cursor_Ibeam cursor_Icon cursor_Size cursor_SizeNESW cursor_SizeNS cursor_SizeNWSE cursor_SizeWE cursor_Uparrow cursor_Wait cursor_User Стандартный курсор в виде стрелки Курсор в виде перекрестия Текстовый курсор в виде буквы I Пустая пиктограмма Курсор в виде квадрата с меньшим квадратом в его нижнем правом углу Двусторонний курсор с направлением северо-восток/юго-запад Двусторонний курсор с направлением север/юг Двусторонний курсор с направлением северо-запад/юго-восток Двусторонний курсор с направлением восток/запад Курсор в виде вертикальной стрелки Курсор в виде песочных часов Первый идентификатор ресурса для пользовательских курсоров Предикаты для работы с курсорами В VPI есть шесть предикатов для работы с курсорами. □ cursor_Set(Window, CursorlD) Этот предикат заменяет курсор окна window курсором с идентификатором CursorlD. □ cursor_Set(Window, CursorlD, ChangeNow) Предикат cursor_Set (__, b_false) изменяет курсор только после движения мыши в указанном окне или когда мышь перемещается в указанное окно window. С другой стороны, cursor_Set (_, bjtrue) работает так же, как cursor_Set/2. □ cursor_Get(Window) Этот предикат получает текущий курсор. Он полезен, если вы хотите восстано- вить предыдущий курсор после замены его на курсор в виде песочных часов. □ cursor_Hide() Этот предикат делает курсор невидимым до тех пор, пока не будет вызван преди- кат cursor_Set или не будет перемещена мышь.
Гпава 26. Средства создания графического интерфейса 779 О cursor_SetWait() Этот предикат устанавливает текущим курсор в виде песочных часов. Любое движение мыши восстанавливает курсор, заданный по умолчанию. □ CursorPosition = cursor_GetPos(Window) Этот предикат получает позицию текущего курсора (мыши) в окне window. Примеры работы с курсорами приведены в каталоге <CDROM>:\RUN\VP1 \EXAMPLES\Cursor на прилагаемом CD-диске. Работа с мышью Существует несколько различных типов устройств мыши. Большинство устройств мыши имеют две кнопки. Если мышь имеет только одну кнопку (как на мыши Macintosh), то она обычно работает как левая кнопка мыши. Однокнопочные мыши представляются в виде цифровых датчиков и сенсорных экранов. Некоторые мыши имеют три кнопки (левую, центральную и правую). На двухкнопочной мыши цен- тральная кнопка моделируется одновременным нажатием обеих кнопок. Когда пользователь щелкает (или дважды щелкает) мышью в окне, то окно автома- тически получает фокус ввода. Если окно (или элемент управления) имеет фокус ввода, то оно будет получать события, связанные с мышью, происходящие в преде- лах границ этого окна. Захват мыши Когда мышь ’’захвачена'* окном, все события от нее направляются к этому окну; при этом неважно, где находится курсор. В каждый момент времени только одно окно может захватывать мышь. Это свойство может быть использовано, например, в Visual Prolog в редакторе текста для автоматического пролистывания текста, или когда пользователь хочет "перетащить" часть текста в другое место. win_CaptureMouse(Window) Предикат win_CaptureMouse перенаправляет все события, связанные с мышью, окну Window. Все события e MouseMove посылаются окну Window во время захвата. win_ReleaseMouse() Предикат win_ReleaseMouse освобождает мышь после вызова предиката win_CaptureMouse. События от мыши VPI преобразует события, связанные с мышью, в следующие четыре сообщения, не зависящие от операционной системы: e_MousеDown(Pnt, Shift_Ctl_Alt_Status, Button) e_MouseUp(Pnt, Shift__Ctl__Alt_Status, Button) e_MouseDbl(Pnt, Shift_Ctl_Alt_Status, Button) e_MouseMove(Pnt, Shift_Ctl_Alt_Status, ButtonsList)
780 Часть V. Разработка графического интерфейса пользователя Каждое сообщение имеет три параметра: Pnt, shift_Ctl_Alt_Status и Button или ButtonsList. □ Pnt — это структура вида pnt(х, у), где х и у —- целые значения, являющиеся координатами мыши относительно верхнего левого угла окна (0, 0). □ Shift_Ctl_Alt_status — показывает статус клавиш <Shift>, <Ctrl> и <Alt>. □ Button и ButtonsList — это целые, которые содержат информацию о том, какие кнопки были нажаты. Следующие константы определены в файле VP1.CON: mous e_but t on_l eft mouse__button_right mouse button middle w** - II Нажата/освобождена Нажата/освобождена Нажата/освобождена левая кнопка мыши правая кнопка мыши средняя кнопка мыши Прямоугольные области Прямоугольные области широко применяются в VPI. Позиция и размер окна, на- пример, определяются прямоугольником, система управления окнами модифицирует окна в прямоугольных областях и т. д. Поскольку прямоугольники в VPI использу- ются часто, существует несколько специальных предикатов, выполняющих общие функции работы с ними. Пересечение двух прямоугольников Ret = rect_Intersect(Rctl, Rct2) Эта функция вычисляет пересечение двух прямоугольников и возвращает координа- ты прямоугольника пересечения. Если прямоугольники не пересекаются, то возвра- щается пустой прямоугольник ret (0, 0, 0, 0). Увеличение размера прямоугольника Ret = rect_Inflate(Ret, integer Dh, integer Dv) Функция rect lnflate увеличивает или уменьшает ширину и высоту прямоугольни- ка, добавляя Dh div 2 единиц слева и справа и Dv div 2 единиц сверху и снизу. Па- раметры Dh и Dv являются знаковыми значениями; положительные значения увели- чивают ширину и высоту, а отрицательные — уменьшают. Проверка пустого прямоугольника rect_TsEmpty(Ret) Этот предикат проверяет, является ли прямоугольник пустым, т. е. равна ли его площадь нулю. Этот предикат завершается неуспешно, если прямоугольник не пустой.
Гпава 26. Средства создания графического интерфейса 781 Перемещение прямоугольника Ret == rectJDffset(Ret, integer Dh, integer Dv) Функция rectJDffset перемещает заданный прямоугольник в другое место. Смеще- ния Dh и Dv знаковые значения, поэтому прямоугольник может быть перемещен в любом направлении. Проверка, находится ли точка внутри прямоугольника rect_Pntlnside(Ret, Pnt) Предикат rect_Pntinside определяет, лежит ли заданная точка внутри прямоуголь- ника. Точка находится внутри прямоугольника, если она лежит на левой или верх- ней его сторонах или внутри всех четырех сторон. Если точка расположена на пра- вой или нижней сторонах, то считается, что она не принадлежит прямоугольнику. Предикат завершается неуспешно, если точка не лежит внутри прямоугольника. Объединение двух прямоугольников Ret = rect_Union(ret Rctl, ret Rct2) Функция rect union создает объединение двух прямоугольников. Объединение — это самый маленький прямоугольник, который содержит оба прямоугольника. Обработка событий Рассмотрим, как VPI-программы обрабатывают события. События, находящиеся в очереди на обработку vpi_ProcessEvents(SendEventsToThisAppBoolean) Этот предикат обрабатывает события, стоящие в очереди. Он передает управление другим приложениям перед обработкой каждого события из очереди событий при- ложения. MS Windows 3.x не имеет приоритетного планирования. Это означает, что если вы- зван предикат обработчика событий, то никакие другие приложения или окна не получат событий до окончания работы предиката. А значит, нежелательно иметь очень длинные непрерывные операции, такие как компиляция, генерация отчетов баз данных, вывод больших изображений, сложные вычисления и т. д. Очевидна необходимость какого-либо механизма для хранения событий, передаваемых другим обработчикам событий. Таким механизмом может быть регулярно вызываемый пре- дикат vpi_ProcessEvents внутри длинных операций. Например, компилятор Пролога в VDE регулярно вызывает предикат vpi_ ProcessEvents для обеспечения компиляции в фоновом режиме, чтобы выполнять другие приложения во время работы компилятора.
782 Часть V. Разработка графического интерфейса пользователя Параметр логического типа SendEventsToThisAppBoolean определяет, должен ли пре- дикат vpij?rocessEvents вызывать обработку событий из очереди событий приложе- ния (b_True) или только передавать управление другим приложениям (b_False). Во втором случае он вызывает обработку только событий е_т inter и e_Update, находя- щихся в настоящее время в очереди событий приложения; все остальные события, кроме eJTimer и ejjpdate, будут удалены из очереди событий. Вариант предиката vpi_ProcessEvents/0 работает так же, как vpi_ ProcessEvents(b_True). vpi_ProcessEvents(SendEventsToThisAppBoolean, WaitNewEventsBoolean) Причем vpi_ProcessEvents/2 отличается от vpi_ProcessEvents/l только тогда, когда WaitNewEvents = b_true. В этом случае vpi_ProcessEvents/2 приостанавливает при- ложение, если нет событий в очереди, и ждет до тех пор, пока не придет новое со- бытие, а затем вызывает обработку этого события. Предикат vpi_ProcessEvents/2 обычно используется в циклах, где приложение ожи- дает событий от других приложений. Вы должны удостовериться, что сможете завершить длинный процесс, если пользо- ватель выберет команду File | Exit, чтобы закрыть приложение. Перед тем как вызы- вать предикат vpi_ProcessEvents, необходимо вызвать предикат, который проверяет флаг, определяющий, можно завершить приложение или нет. Отправка событий Пока события происходят в системе управления окнами, код приложения может послать событие одному из его собственных обработчиков событий, используя пре- дикат win_SendEvent, эффект будет таким же, как при вызове обработчика для этого окна. Конечно, можно вызвать обработчик событий непосредственно, если он дос- тупен в данной точке, но во многих ситуациях известен только дескриптор окна, поэтому лучше использовать вызов, основанный на дескрипторе окна. Значение long, которое возвращается предикатом win_SendEvent, является возвращаемым зна- чением (заключительным параметром) предложения обработчика событий для этого события. Long = win_SendEvent(Window, Event) Предикат win_SendEvent можно использовать для передачи событий от меню обра- ботчику родительского окна. win__SendEvent (Parentwindow, e_Menu(ID, CtrlAltShift)) Предикат win_SendEvent не завершится до завершения соответствующего предложе- ния обработчика событий. Если это предложение обработчика завершается неус- пешно, то win_SendEvent возвратит нуль. Иногда нужно поставить событие в очередь, чтобы оно было обработано после за- вершения текущего события и остальных ждущих обработки событий. Постановку события в очередь выполняет предикат win_PostEvent: win_PostEvent(Window, Event) Этот предикат завершается сразу после постановки события в очередь.
Гпава 26. Средства создания графического интерфейса 783 Ресурсы Термин ’’ресурсы” часто применяется в компьютерном мире, В системе GUI термин "ресурсы” относится к окнам, диалоговым окнам, изображениям, строковым табли- цам, пиктограммам, курсорам, контекстам устройства, перьям, кистям, распределе- ниям памяти и т. д. для всех активных приложений. Когда мы обращаемся к ресурсам в Visual Prolog, то обычно имеем в виду описания диалоговых окон, меню, панелей инструментов и справки, курсоров, растровых изо- бражений, пиктограмм и строковых таблиц, которые создаются различными редак- торами ресурсов в VDE. Размещение и атрибуты этих ресурсов сохраняются в базе данных проекта (файлы <project>.VPR или <project>.PRJ). Когда вы запускаете про- цесс компоновки, VDE генерирует стандартный двоичный файл ресурсов (<project>.RES) и файл с определениями констант для включения в исходные моду- ли. Генераторы объектного кода VDE для окон (в противоположность диалоговым окнам), панелей инструментов и справки генерируют код Пролога прямо в исход- ном коде приложения, т. к. они не поддерживаются стандартными форматами фай- лов с расширениями гс или res. Вы можете указать необходимость генерации файлов <project>.RC и Н-файлов, вы- полнив команду меню Options | Project | Code Generator. Эти файлы обеспечивают стандартное текстовое описание ресурсов и могут быть преобразованы в RES-файл любым из доступных инструментов (компилятором RC или любым из инструментов для подготовки ресурсов). Имена ресурсов После того как ресурс создан одним из редакторов VDE, ему необходимо присвоить имя, VDE предоставляет стандартное имя, основанное на ряде правил, и пользова- тель может принять, изменить или полностью заменить его. Эти символы, которые позже станут константами Пролога, будут использоваться приложением для обраще- ния к ресурсам, а во время компиляции превратятся в уникальные целочисленные значения. Во время построения проекта генерируются два файла. Один с расширением res, предназначенный для компоновщика, и другой с расширением соп, предназначен- ный для включения в исходные модули проекта, связанные с обработкой ресурсов. В CON-файле присваивается уникальное целочисленное значение каждому ресурсу. Эти числовые значения (а не символьные имена, заданные в приложении) будут использоваться в VPI-предикатах. В файл ресурсов входят следующие элементы: □ диалоговые окна; □ растровые изображения; □ меню; □ курсоры; □ пиктограммы и строки. Связывание ресурсов RES-файл связывается (линкуется) в исполняемый файл (<project>.EXE), в котором он формирует сегмент(ы) ресурсов. Эта операция может быть выполнена компо-
784 Часть У- Разработка графического интерфейса пользователя новщиком PDC. Во время выполнения приложения предикаты VPI получают доступ к диалоговым окнам, меню, пиктограммам, растровым изображениям и строкам из сегмента ресурсов ЕХЕ-файла. Растровые изображения, пиктограммы и курсоры Эти элементы могут быть созданы как в редакторе изображений VDE, так и при помощи других инструментов, предназначенных для создания, сохранения или из- менения графических изображений. В отличие от диалоговых окон, меню и строк, определения пиктограмм, растровых изображений и курсоров будут находиться в отдельных файлах перед тем, как они будут переданы в RES-файл. Диалоговые окна Диалоговые окна обычно создаются в редакторе диалоговых окон VDE. Он упроща- ет работу по размещению и установке размеров элементов управления в диалоговом окне. Если в файле ресурсов есть описание диалогового окна, то оно может быть создано вызовом предиката win_CreateResDialog. Или это описание может быть за- писано в структуру VPI вызовом предиката: win_GetResDialog(ResID) Следующий предикат может быть использован для проверки того, существует ли описание диалогового окна в файле ресурсов. Result = vpiJSheckExistDialog(ResID) Меню Меню обычно проектируются в редакторе меню VDE. Если окно создано вызовом предиката win_Create, то меню для окна передается как параметр. Если вызвать предикат res__lhenu (Resld) то окно автоматически получит ресурсное меню с идентификатором Resld. Строки Строки помещаются в строковые таблицы по нескольким причинам. Наличие их в ресурсах позволит упростить выполнение изменений. Например, перевод на другой язык можно произвести без изменения исходного кода. Приложение может получить строку по идентификатору ресурса вызовом предиката: Str = vpi_GetResStr(ResID) Следующий предикат’может использоваться для проверки того, существует ли стро- ка с заданным идентификатором в файле ресурсов: Result = vpi_CheckExistString(ResID)
Глава 26. Средства создания графического интерфейса 785 Редактор меню VDE Самый простой способ выполнения различных команд состоит в том, чтобы органи- зовать такие команды в виде пунктов меню. Эту задачу выполняет редактор меню VDE. Каждому пункту меню присваивается идентификатор ресурса. Ваше приложе^- ние может быть сделано более понятным, если присваивать каждому идентификато- ру ресурса имя, связанное с выполняемым действием. Меню связываются с окнами во время создания окна. Параметр меню в предикате win_create определяет, что меню расположено в файле ресурсов или что оно дина- мически формируется из структуры языка Пролог. При использовании режима MDI все меню будут появляться в окне Task. Меню автоматически изменяется при модификации активного окна. Для того чтобы каждое окно имело свое собственное меню, нужно сбросить флаг MDI в эксперте приложений. Родительским окном должно быть окно Screen. VPI имеет возможность создавать собственные меню. Это средство не является переносимым на все платформы. Обратите внимание, что редактор меню в VDE поддерживает только текстовые меню. Домен MENU Рассмотрим домены, использующиеся при создании меню: menu = res_menu{resld); dyn_menu (menu_item_list); nojnenu menu_item_list = menu__item* menu_item = txt(menu_tag, string, integer Mnemonic, boolean Enabled, boolean Checked, integer Itemstate, % Целое значение для этого пункта меню % Текст меню % Буква для акселератора % Константы: mis__None, mis_Checked, mis_Help. % Последний размещает пункт меню ’Window' % сразу перед этим пунктом. Обычно это % используется для размещения пункта % меню Help в крайнюю правую позицию menu_item_list) ; ownerdr aw { menu_tag, long Val, boolean Enabled, boolean Checked, menu_item_list); separator; menu_break % 32-разрядное число, передаваемое в e_ownerDraw % Рисует горизонтальную линию в меню % Разрыв колонки в меню menu_tag = unsigned
786 Часть У, Разработка графического интерфейса пользователя Параметр Itemstate определяет состояние пункта меню. Его значения приведены в табл. 26.18. Таблица 26.18, Значения параметра itemstate Параметр Описание mis_None Определяет обычное состояние mis_Checked Устанавливает галочку в пункте меню mis_Help Определяет, что этот пункт меню должен быть размещен сразу после меню Window; обычно это используется для того, чтобы поместить пункт Help в крайнее правое положение в меню (только в режиме MDI) Сочетание ItemState =* mis__Checked + mis_Help, определяет, что пункт меню должен быть размещен сразу после пункта Window и должен иметь галочку. Определение действий для пункта меню Для того чтобы пункт меню выполнял какое-то действие, в обработчике событий окна при помощи эксперта кода нужно сгенерировать предложение, которое обраба- тывает событие от этого пункта меню. Любой код, помещенный в это предложение, будет выполняться при выборе данного пункта меню (если пункт меню заблокиро- ван, то этот код никогда не будет выполнен, кроме случаев, когда существует кноп- ка на панели инструментов, которая не заблокирована и имеет тот же идентифика- тор ресурса). Событие, которое было порождено выбором команды File | Open в окне Task, обра- батывается таким кодом: task_win_event_handler(Window, e_Menu (id_file_open, ShiftCtlAlt), 0):- dlg__note ("Menu", "You chose Open”), i 4 Загрузка меню из файла ресурсов Предикат menu_GetRes используется для получения меню из файла ресурсов. Меню загружается в структуру данных VPI из домена menu. NewMenu = menuJSetRes(irm_my_menu), menu_Set(Window, NewMenu) Изменение текста пункта меню Текст отдельного пункта меню может быть изменен при помощи предиката menu_SetText/3. Следующая строка, вставленная в событие создания окна Task —
Гпава 26. Средства создания графического интерфейса 787 ejCreate — изменит стандартный текст File Open на другой, например, Open DataBase: menu_SetText (Window, id_f ile_open, "Open DataBase") Метки С пунктами меню могут быть связаны метки, которые определяют, доступно неко- торое свойство или нет. Начальное состояние задается в определении меню. Это состояние можно изменить, вызвав предикат menu_check/3. menu_Check(Window, menu_item_resoLirce_identifier, Boolean) Включение/блокировка пунктов меню По умолчанию новые пункты меню в редакторе меню являются включенными (кро- ме тех пунктов, которые автоматически генерируются экспертом приложений без кода). Их можно выключить или блокировать двойным щелчком мыши по имени пункта меню на этапе проектирования или изменением состояния заблокированного пункта, а во время работы приложения — вызовом предиката menu_Enable с послед- ним параметром b False. % Блокировка пункта File|New menu_Enable(Window, id_file_new, b_False), % Включение пункта File I New menu_Enable(Window, id_file_new, b_True). Принудительная перерисовка меню Если вы динамически изменяете текст пунктов меню в событиях, отличных от e_Create, то ваше изменение может быть не видно пользователю на некоторых платформах. Предикат menu_Update/l объявляет область экрана, занимаемую меню, недостоверной, инициируя ее перерисовку (листинг 26.21). i Листинг 26.21 ........ ........................... ........................................................................ ....... „....................................................................... menu_SetText(Window, id_file^open, "Open DB"), % Вызывает перерисовку области меню menu_Update(Window) Работа с подменю в MS Windows Если пункт подменю нужно включить, блокировать, отметить галочкой или изме- нить текст, то в MS Windows необходимо использовать специальные варианты пре- дикатов menu_Enable, menu_check и menu_SetText, которые используют текст пункта меню вместо идентификатора.
786 Часть V. Разработка графического интерфейса пользователя Параметр itemstate определяет состояние пункта меню. Его значения приведены в табл. 26.18. Таблица 26.18. Значения параметра itemstate Параметр Описание mis_None mis Checked Определяет обычное состояние Устанавливает галочку в пункте меню mis_Help Определяет, что этот пункт меню должен быть размещен сразу после меню Window; обычно это используется для того, чтобы поместить пункт Help в крайнее правое положение в меню (только в режиме MDI) Сочетание ItemState = mis_Checked + mis_Help, определяет, что пункт меню должен быть размещен сразу после пункта Window и должен иметь галочку. Определение действий для пункта меню Для того чтобы пункт меню выполнял какое-то действие, в обработчике событий окна при помощи эксперта кода нужно сгенерировать предложение, которое обраба- тывает событие от этого пункта меню. Любой код, помещенный в это предложение, будет выполняться при выборе данного пункта меню (если пункт меню заблокиро- ван, то этот код никогда не будет выполнен, кроме случаев, когда существует кноп- ка на панели инструментов, которая не заблокирована и имеет тот же идентифика- тор ресурса). Событие, которое было порождено выбором команды File | Open в окне Task, обра- батывается таким кодом: task_win_event_handler{Window, e_ Menu {id_f ile_open, ShiftCtlAlt), 0):- dlg_note{"Menu", "You chose Open"), i « Загрузка меню из файла ресурсов Предикат menu GetRes используется для получения меню из файла ресурсов. Меню загружается в структуру данных VPI из домена menu. NewMenu = menu_GetRes {irm__my_menu) , menu_Set(Window, NewMenu) Изменение текста пункта меню Текст отдельного пункта меню может быть изменен при помощи предиката menu SetText/З. Следующая строка, вставленная в событие создания окна Task —
лза 26. Средства создания графического интерфейса 787 : reate — изменит стандартный текст File Open на другой, например, open ; - aBase: 7'-_SetText(Window, id—file_open, "Open DataBase") Метки 2 пунктами меню могут быть связаны метки, которые определяют, доступно неко- ~:рое свойство или нет. Начальное состояние задается в определении меню. Это :остояние можно изменить, вызвав предикат menu_check/3. renu_Check(Window, menu_item_resource_identifier, Boolean) Включение/блокировка пунктов меню По умолчанию новые пункты меню в редакторе меню являются включенными (кро- ме тех пунктов, которые автоматически генерируются экспертом приложений без кода). Их можно выключить или блокировать двойным щелчком мыши по имени пункта меню на этапе проектирования или изменением состояния заблокированного пункта, а во время работы приложения — вызовом предиката menu_Enable с послед- ним параметром b_False. % Блокировка пункта File)New menu_Enable(Window, id_file_new, b_False), % Включение пункта File I New menu_Enable(Window, id_file_new, bJTrue). Принудительная перерисовка меню Если вы динамически изменяете текст пунктов меню в событиях, отличных от e create, то ваше изменение может быть не видно пользователю на некоторых платформах. Предикат menu_update/l объявляет область экрана, занимаемую меню, недостоверной, инициируя ее перерисовку (листинг 26.21), Листинг 26.21 menU—SetText(Window, id_file_open, "Open DB”), % Вызывает перерисовку области меню menU—Update(Window) Работа с подменю в MS Windows Если пункт подменю нужно включить, блокировать, отметить галочкой или изме- нить текст, то в MS Windows необходимо использовать специальные варианты пре- дикатов menu_Enable, menu_Check и menu_SetText, которые используют текст пункта меню вместо идентификатора.
788 Часть V. Разработка графического интерфейса пользователя menu_Enable(Window, String, Boolean) menu^Check(Window, String, Boolean) menu__SetText (Window, String, NewString) Всплывающие меню Предикат menu_PopUp/4 используется для создания всплывающих меню и обычно вызывается в ответ на нажатие кнопки мыши. Следующее предложение: my_event_handler (Window, е_MouseDbl (Pnt, _, __) , 0):- !, menu_PopUp (Window, res__menu (idr_my_pop_up) , Pnt, align__Left) . отобразит меню с идентификатором ресурса idrjnyjpopup в точке pnt в окне Window, когда пользователь дважды щелкнет мышью в этом окне. Последний параметр, который может быть равен align Left, align_Right или align Center, определяет положение всплывающего меню относительно точки Pnt. Когда пользователь щелкает мышью во всплывающем меню для данного окна, VPI посылает обычное событие e_Menu обработчику событий этого окна. Таймеры Таймер — это внутренняя процедура, которая каждый раз по истечении заданного интервала времени посылает событие ejrimer обработчику событий окна, связанно- му с таймером. Поскольку точность таймера зависит от тактовой частоты системы и частоты получения приложением сообщений из очереди сообщений, то значение окончания интервала времени является приблизительным. Новый таймер запускает время сразу после своего создания. Приложение может уничтожить таймер при помощи функции timer Kill. Чтобы эффективно использо- вать ресурсы системы, приложения должны уничтожать ненужные в данный момент таймеры. Каждый таймер имеет уникальный идентификатор, который возвращается предикатом создания таймера. Запуск таймера Новый таймер можно создать вызовом предиката Timerld = timer__Set (Window, unsigned Interval) Интервал времени определяется в миллисекундах. Возвращаемый идентификатор таймера может быть использован для остановки таймера. Остановка таймера Таймер уничтожается вызовом предиката: timer_Kill(long Timeridentifier) где Timeridentifier — значение, возвращаемое предикатом timer_Set.
Гпава 26. Средства создания графического интерфейса 789 Изображения VPI содержит набор предикатов для работы с изображениями. Изображения сохра- няются в файлах с расширением bmp и не ограничены размером 64 Кбайт. Опера- ции над изображением выполняются с использованием его идентификатора, кото- рый должен быть уничтожен вместе с изображением, чтобы освободить занимаемые ресурсы. Существует четыре способа получения изображения. Преобразование изображений в двоичные данные Для сохранения изображений в базах данных, пересылке по сети полезно преобразо- вывать их в стандартные (binary) термы Пролога. Это преобразование выполняют два предиката. Binary = pict_ToBin(Picture) Picture = pict_FromBin(Binary) Обратите внимание, что на 16-разрядных платформах существует ограничение на размер двоичной структуры — 64 Кбайт. Вызов предиката pict ToBin для изобра- жения, размер которого превышает это значение, может привести к ошибке испол- нения. Создание изображения операциями рисования Изображение в памяти можно создать при помощи комбинации различных опера- ций рисования. Для этого требуется выполнить три шага: 1. Вызвать предикат pict_Open для определения начала построения изображения. 2. Выполнить все операции рисования. 3. Вызвать предикат pict_ciose, который возвратит идентификатор созданного изо- бражения (листинг 26.22). £ "77’"’7 7'77’7'*'•' ' 77^'7 77Л7? 7?7'7 7" 7 7’’ ’• 7777 7777’^77- PictWin = pict_Open(Window, Ret) « « % Операции рисования в окне PictWin Picture = pict Close(PictWindow)
790 Часть V. Разработка графического интерфейса пользователя Уничтожение изображения Если работа с изображением окончена, то занимаемая им память должна быть осво- бождена вызовом предиката pict_Destroy. pict_Destroy(Picture) Рисование изображений Существует два способа нарисовать изображение: с масштабированием и без него. Рисование без масштабирования Все изображение может быть нарисовано в заданном месте окна. Верхний левый угол изображения в координатах клиентской области окна должен быть определен в структуре Pnt. pict_Draw (Window, Picture, Pnt, Rop) Рисование с масштабированием В масштабирующей версии предиката pict_Draw изображение может быть масшта- бировано для заполнения области (DestRct). Также можно определить, какая часть изображения может подвергаться масштабированию (pictRct): pict_Draw(Window, Picture, DestRct, PictRct, Rop) Растровые операции Параметр Rop в предикате pict^Draw определяет расширенные возможности комби- нирования: □ существующего содержимого экрана (Dst); □ текущего узора-заполнителя кисти для окна (Pat); □ изображения, которое должно быть нарисовано (src). Комбинации изображений выполнены при помощи поразрядных логических опера- ций. Эта особенность позволяет создавать растровое изображение, которое частично может быть прозрачным, модифицировать только части окна, обходить измененные области изображения и т. д. Результаты растровых операций приведены в табл. 26.19. Таблица 26.19. Растровые операции Операция Результат rop_Blackness Черный rop_Whiteness Белый rop_DstInvert Not Dst
Гпава 26. Средства создания графического интерфейса 791 Таблица 26.19 (окончание) Операция Результат rop_MergeCopy rop_MergePaint гop_NotSгcCору r op_Not S rcE ra s e rop_PatCopy rop_PatInvert rop_PatPaint rop_SrcAnd rop_SrcCopy rop_SrcErase rop_Srdnvert rop_SrcPaint Pat And Src (Not Src) Or Dst Not Src Not (Src Or Dst) Pat Dst Xor Pat (Not Src) Or Pat Or Dst Src And Dst Src (Not Dst) And Src Src Xor Dst Src Or Dst Получение изображения из содержимого окна Существует возможность получить содержимое всей или части клиентской области окна вызовом предиката: Picture = pict_GetFromWin(Window, Ret) где Ret определяет прямоугольную область внутри окна Window. Загрузка изображения из файла Изображения обычно хранятся в файлах с расширением bmp (bitmap). Они могут быть загружены из файлов, которые хранят растровые изображения и могут превы- шать размер 64 Кбайт. Picture = pict_Load(FileName) Загрузка изображения из сегмента ресурсов Битовый массив можно загрузить из сегмента ресурсов ЕХЕ-файла приложения прямо в изображение при помощи предиката: Picture = pict_GetFromRes(Resource~identifier)
792 Часть V. Разработка графического интерфейса пользователя Получение размеров изображения Предикат pict Get Size используется перед процессом рисования для получения вы- соты (Height) и ширины (width) изображения. Этот предикат также может приме- няться для получения размера изображения перед преобразованием его в двоичный формат или перед сохранением его в файле. pict_GetSize(Picture, integer Width, integer Height, long Size) Сохранение изображения в файле Для сохранения изображения в файле нужно вызвать предикат: pict__Save (Picture, FileName) Вращение изображения При помощи предиката pict_Rotate изображения можно вращать. NewPicture=pict__Rotate (Picture, Tingle) Буфер обмена Буфер обмена — это общее место, через которое приложения могут обмениваться данными посредством операций копирования и вставки. Буфер обмена управляется пользователем; приложения не должны помещать что-либо в буфер обмена или очищать его без ведома пользователя. Данные буфера обмена могут находиться в различных форматах. VPI поддерживает три открытых переносимых формата, которые обрабатываются способом, совмести- мым с другими приложениями: □ текст, который соответствует домену string; П изображения, которые хранятся как битовые массивы; □ метафайлы. Также вы можете помещать в буфер обмена данные собственного формата. Эти дан- ные помещаются как двоичные, совместно с названием формата. Так как произ- вольные элементы Пролога могут быть преобразованы в двоичный формат, то лю- бой домен, в принципе, может быть сохранен в буфере обмена. С буфером обмена можно выполнять следующие действия: □ проверять, содержит ли буфер обмена какие-либо данные; □ получать данные из буфера обмена; □ помещать данные в буфер обмена, замещая таким образом его предыдущее со- держимое.
Гпава 26. Средства создания графического интерфейса 793 Откройте пример в каталоге <CDROM>:\RUN\VPI\EXAMPLES\CLIPBRD на при- лагаемом CD-диске и посмотрите, как форматы VPI сочетаются с вашими програм- мами. Получение данных из буфера обмена Для получения данных из буфера обмена используются следующие предикаты. Если выбранный тип данных не существует, то предикат закончится неуспешно. Binary ~ cb_GetBin(string) Picture ~ cbjGetPicture() MetaFileHandle = cb_GetMetafile() StringLength - cb_GetSize() String « cb_GetString() Внимание! - _ — — T- Предикат cb__GetString возвратит строку согласно соглашениям Windows отно- сительно комбинации "возврат каретки — перевод строки" (CR-LF). Предикат str_DOSStr может использоваться для преобразования комбинаций CR-LF в обыч- ную строку Пролога с символами \п. Помещение данных в буфер обмена Следующие четыре предиката используются для помещения данных в буфер обмена: cb_PutString(String) cb_PutPicture(Picture) cb_PutMetafile(MetaFileHandle) cb_PutВin(Formatname, Binary) Предыдущее содержимое буфера обмена будет потеряно. Проверка данных в буфере обмена Существует предикат для проверки каждого типа данных в буфере обмена. Чтобы проверить, доступен ли двоичный формат, на вход предиката нужно дать название формата. cb^StringAvailable() cb_PictureAvailable() cbJMetafileAvailable() cb_BinAvaliable(FormatName) Приложениям бывает необходимо включить или блокировать команду меню Edit | Paste в зависимости от того, есть что-либо в буфере обмена или нет. Существует специальное событие e_lnitMenu, которое посылается, когда меню активизировано. Это означает, что достаточно только изменить меню, когда событие e lnitMenu по- слано (листинг 26.23).
794 Часть V. Разработка графического интерфейса пользователя Листинг 26.23 ehandler(Window> e_InitMenu(), 0):- cb_StringAvailable()f menu_Enable (Window, menu__edit_paste, b_True) , ! . ehandler(Window, e_InitMenu(), 0):- menu_Enable (Window, menu__edit__paste, b_False) . Символы вставки Символ вставки отмечает точку вставки текста в поле редактирования или в окне. Не нужно путать этот символ с курсором, который отмечает текущую позицию мы- ши на экране. Часто приложение размещает символ вставки, проверяя текст в об- ласти нажатия кнопки мыши. Когда мышь перемещается пользователем, символ вставки остается на месте. Символ вставки часто определяется как точка вставки текста. Каждое окно имеет свой собственный символ вставки со своей собственной позицией. Существует четыре предиката для работы с символом вставки. О caret_Set(Window, X, Y) Этот предикат перемещает символ вставки окна window в точку с координатами х и Y (относительно верхнего левого угла окна). О caret_Set(Window, X, Y, Width, Height) Этот предикат показывает символ вставки в определенном окне Window (если он был скрыт), устанавливает размеры символа вставки равными width и Height и помещает его в точку с координатами х и Y. □ caret_Size(Window, Width, Height) Этот предикат определяет символ вставки для использования в окне window, ширина (width) и высота (Height) даются в логических единицах. □ caret Off(Window) Этот предикат делает символ вставки окна window невидимым. Флаги атрибутов VPI Идентификаторы атрибутов определяют множество значений флагов, которые могут быть установлены или прочитаны из VPI. Некоторые атрибуты — глобальные для VPI, другие — локальные для определенного окна. Список идентификаторов атрибу- тов и их описания приведены в табл. 26.20. Значения доступных только для чтения атрибутов не всегда могут быть установлены из VPI, однако получить можно значе- ния всех атрибутов. Те атрибуты, значения которых могут быть установлены, долж- ны быть установлены до вызова предиката vpi_lnit, и установка их в любое другое время не будет иметь никакого эффекта.
~ава 26. Средства создания графического интерфейса 795 Таблица 26.20. Идентификаторы атрибутов Название атрибута Описание Среда ittr_have_color Флаг "только для чтения", который возвращает бу- левское значение, определяющее способность эк- рана отображать цвета attr_have_mouse a 11 r_num_t ime г s attr^CheckPortability Флаг "только для чтения", который возвращает бу- левское значение, определяющее наличие у компь- ютера мыши Флаг "только для чтения", который возвращает мак- симальное число таймеров В случае если этот флаг установлен в b_false (0), VPI не проверяет, поддерживается ли предикат на выбранной платформе. (Соответствующая ошибка 6008.) Значение по умолчанию — b true (1) Атрибуты размеров объектов attr_ctl_button_height attract l_checkbox_jh eight attractl_edit_text_height attractl_horz_sbar_height Флаг "только для чтения", который возвращает ми- нимальную высоту командной кнопки Флаг "только для чтения", который возвращает ми- нимальную высоту флажка Флаг "только для чтения", который возвращает ми- нимальную высоту поля редактирования Флаг "только для чтения", который возвращает вы- соту изображения стрелки на горизонтальной поло- се прокрутки attract l_vert_sbar_width Флаг "только для чтения", который возвращает ши- рину изображения стрелки на вертикальной полосе прокрутки attractl_radiobutton_height Флаг "только для чтения", который возвращает ми- нимальную высоту переключателя attract l_stat ic__t ext_height Флаг "только для чтения", который возвращает ми- нимальную высоту статического текста attr_icon_width attr_icon_height Флаг "только для чтения", который возвращает ши- рину пиктограммы Флаг "только для чтения", который возвращает вы- соту пиктограммы Предопределенные окна I *1 I 1»«| »»lali на ьы *** *»Мааа»1М»аа»ааМаааа|аЧПаааа<|а111ааа|а1МЬааа||1|ааЬаа||1111аааа||аааа attr_screen__window Флаг "только для чтения", который возвращает де- скриптор окна Screen Флаг "только для чтения", который возвращает де- скриптор окна Task attr_task_window
796 Часть V. Разработка графического интерфейса пользователя Таблица 26.20 (продолжение) Название атрибута Описание Атрибуты системных метрик attr_screen_height Флаг "только для чтения", который возвращает вы- соту экрана в строках attr_screen_width attr_screen_hres Флаг "только для чтения", который возвращает ши- рину экрана в пикселах Флаг "только для чтения", который возвращает ко- личество пикселов в логическом дюйме по ширине экрана attr_screen__vres attr_printer_height attr_printer_width attr_printer_hres att r_pr ante r__vres Флаг "только для чтения", который возвращает ко- личество пикселов в логическом дюйме по высоте экрана Флаг "только для чтения", который возвращает вы- соту печатаемой страницы в строках Флаг "только для чтения", который возвращает ши- рину печатаемой страницы в пикселах Флаг "только для чтения", который возвращает ко- личество пикселов печатаемой страницы в логиче- ском дюйме по ширине страницы Флаг "только для чтения", который возвращает ко- личество пикселов печатаемой страницы в логиче- ском дюйме по высоте страницы Атрибуты метрик окна attr_docframe_width attr_docframe_height attr_frame_width attr_frame_height attr_dblframe_width attr_dblframe_height at t r_menu_height attr_title_height Флаг "только для чтения", возвращающий ширину рамки окна, размер которого можно менять Флаг "только для чтения", возвращающий высоту рамки окна, размер которого можно менять Флаг "только для чтения", возвращающий ширину рамки окна, размер которого нельзя менять Флаг "только для чтения", возвращающий высоту рамки окна, размер которого нельзя менять Флаг "только для чтения", который возвращает ши- рину рамки, если окно имеет стиль wsf_DlgBorder Флаг "только для чтения", который возвращает вы- соту рамки, если окно имеет стиль wsf_DlgBorder Флаг "только для чтения", который возвращает высоту отдельной строки панели меню: высота меню минус высота рамки окна, размер которого нельзя менять Флаг "только для чтения", который возвращает вы- соту заголовка окна: высота заголовка плюс высота рамки окна, размер которого нельзя менять
Глава 26. Средства создания графического интерфейса 797 Таблица 26.20 (продолжение) Название атрибута Описание Системные цвета окон р р р^Р р ^р а а q р р р р а q И Ч Р И Р Р чЛ q q f №•-- F №№Р №Р *< F Ж F* Ч ПН Р l-Н №РР№аР""""№РИИаР Mllli tw №•-- Р Р Р №1-ь№* 'НН «*>'*** •"*> *ч>» >»К»Н № F №№№ рхН № г R * р №"*. р г R г R * i«p iiriiiibbpPiiii .ib?'-ii.ii . Ч i attr_color_activeborder Флаг “только для чтения", который возвращает цвет рамки активного окна attr_color_activecaptaon Флаг "только для чтения", который возвращает цвет заголовка активного окна attr_color_appworkspace Флаг "только для чтения", который возвращает цвет фона приложений многодокументного интерфейса (MDI, Multiple Document Interface) attr_color_desktop Флаг "только для чтения", который возвращает цвет рабочего стола attr_color_btnfасе Флаг "только для чтения", который возвращает цвет лицевой грани командных кнопок attr_color_btnshadow attr_color_btntext attr_color_captiontext attr__color_graytext attr_color__highlight- attr_color_highlighttext Флаг "только для чтения", который возвращает цвет границ командных кнопок Флаг “только для чтения", который возвращает цвет текста командных кнопок Флаг "только для чтения", который возвращает цвет текста в заголовках, кнопках размера, кнопках со стрелками на полосах прокрутки Флаг "только для чтения", который возвращает цвет текста на заблокированных элементах Флаг "только для чтения", который возвращает цвет фона выделенного элемента в элементе управления Флаг "только для чтения", который возвращает цвет текста выделенного элемента в элементе управ- ления att г_соIor_inactiveborder Флаг "только для чтения", который возвращает цвет рамки неактивного окна attr_color_inactivecaption Флаг "только для чтения", который возвращает цвет заголовка неактивного окна att r_color_inactivecapt iontext Флаг "только для чтения", который возвращает цвет текста заголовка неактивного окна att г_с о1о r_menu att r_;co 1 о rjnenut ex t attr_color_scrollbar Флаг "только для чтения", который возвращает цвет фона меню Флаг "только для чтения", который возвращает цвет текста в меню Флаг "только для чтения", который возвращает цвет серой области полосы прокрутки
798 Часть К Разработка графического интерфейса пользователя Таблица 26.20 (продолжение) Название атрибута Описание attr_color__windowtext attr_color__window attr_color_windowframe Флаг "только для чтения", который возвращает сис- темный цвет текста в клиентской области окна Флаг "только для чтения", который возвращает сис- темный цвет фона в клиентской области окна Флаг "только для чтения", который возвращает цвет рамки окон attr_color_windowstatictext attr_color_tooltiptext Флаг "только для чтения", который возвращает цвет текста в статическом элементе управления Флаг "только для чтения", который возвращает цвет текста во всплывающих подсказках в Windows. На других платформах возвращает О attr_color_tooltipback Флаг "только для чтения", который возвращает цвет фона всплывающих подсказок в Windows. На других платформах возвращает О Атрибуты окон ^8аааааааа||||а88аааа|аа|.||8а8ааааааааааа"88аа8ааааааааьал88чачаача|||*^чаааа««**^*г**"*'""""*"88^<'"""ьг****>*ргььь ь**8**т i i г • ' ьь||ааРРааааа||Р||ааааааа|Ь|ац|РЧ«, I«,|4*awaairii |k»****-*i гч*1*а***гагвг*а*аа*ь88гаааа*а«« л л л*/ / «а*'*«****8*ч а* а* "888888"1"1""""8"88*8*1"" iwi 88888aaaaadda."8**8a«a«***| р****м« **^*8**а 8**8 1>Ч>»М|«1 8 8 8 attr_native_window Флаг "только для чтения", который возвращает "род- ной" дескриптор окна (Windows-дескриптор) attr_win_instance Флаг "только для чтения", который возвращает де- скриптор приложения attr_win_mdi Под Windows attr_win_mdi используется для уста- новки режима многодокументного интерфейса (MDI) attr_win_mdi_client_hwnd attr__win_tbar attr_win_lbar attr_win_rbar attr_win_sbar attr__win_3dcontrols Флаг "только для чтения". Под Windows он возвра- щает "родной" дескриптор окна Task в режиме MDI. Под OS/2 он всегда возвращает О Флаг, который используется для установки высоты области панели инструментов в верхней части окна Флаг, который используется для установки/полу- чения ширины области панели инструментов с левой стороны окна Флаг, который используется для установки/получе- ния ширины области панели инструментов с правой стороны окна Флаг, который используется для установки высоты области строки состояния в нижней части окна Флаг, который используется для включения/выклю- чения поддержки трехмерных элементов управле- ния. Этот флаг должен быть установлен перед вызо- вом предиката vpi_lnit attr_suppress_update_check Флаг, который используется для отключения отсыл- ки уведомлений modified () для полей редактиро- вания
~лава 26. Средства создания графического интерфейса 799 Таблица 26.20 (окончание) Название атрибута attr_erase_background Описание Если этот флаг установлен в b_False, то не будет автоматического стирания фона для окон Текущее значение любого из этих атрибутов может быть получено посредством пре- диката: Long = vpi_GetAttrVal(long Attribute) Для атрибутов, связанных с окном, может использоваться предикат: Long = win_GetAttrVal(Window, long Attribute) Так как возвращаемое значение имеет тип long, то иногда необходимо использовать предикат cast, чтобы преобразовать значение к нужному домену. Например, чтобы получить дескриптор окна Task, можно использовать следующее предложение: TaskWin = cast(window, vpi_GetAttrVal(attr_task_window)) Атрибуты "не только для чтения" могут быть изменены вызовом предиката: vpi_SetAttrVal(long Attr, long Value) В настоящей версии VPI не существует изменяемых атрибутов, определенных для окна. Классы окон В VPI поддерживается создание классов окон. Класс окна может использоваться для реализации специальных элементов управления, которые затем могут быть вставле- ны в диалоговое окно. Создать класс можно при помощи предиката: class__Create (string ClassName, Ehandler) Класс будет удален вызовом предиката: class_Destroy(string ClassName) Перед тем как использовать диалоговое окно со специальными элементами управле- ния, проверьте, что класс действительно создан. При создании класса с ним ассо- циируется обыкновенный предикат обработчика событий. При создании диалогово- го окна автоматически создается окно для специального элемента управления, и предикат обработчика событий получает событие e_create для класса. Когда предикат class_Create вызывается в библиотеке динамической компоновки (DLL), он создает глобальный класс окна. Операционная система загружает опреде- ленные DLL в контекст запущенного процесса, перед тем как вызвать цель (goal) в этом процессе; DLL регистрирует классы окон во время процедуры инициализации. В листинге 26.24 приведен фрагмент кода, который может использоваться для реа- лизации элемента управления, отображающего рисунок в диалоговом окне.
800 Часть V. Разработка графического интерфейса пользователя |Й1ЙЙЯЙЙГ-2& global predicates bmpctrl—register bmpctrl_SetBMPFile(window, string File, boolean Stretch) — (i,i,i) domains ctrlBmp = file(string FileName); resid(resID) facts — bmpctrl ctrl_bmp(window, ctrlBMP, boolean Stretch) predicates win_bmpctrl_eh : ehandler clauses win_bmpctrl_eh (—Window, e_Update (_UpdateRct), 0) : - Ctrl—bmp(—Window, file(File),b_True), i • F Picture = pict—Load(File), pict_GetSize(Picture, X, Y, _Size), WinRCT = win_GetClientRect(—Window), pict_Draw(—Window, Picture, WinRCT, ret(0,0,X,Y),rop_SrcCopy) , pict—Destroy(Picture) , i * clauses bmpctrl—register :- class—Create("BMPCTRL”, win_bmpctrl_eh). clauses bmpctrl—SetBMPFile(Window, File/ Stretch) :- retractall(ctrl_bmp(Window,—,_)) , assert(Ctrl—bmp(Window, file(File),Stretch)). Обратите внимание, что предопределенные инструменты, такие как дерево или ре- дактор, также могут быть помещены в специальный элемент управления. Для это г: существуют предикаты edit-CreateSubClass и tree_CreateSubClass, которые долж- ны быть вызваны в событии e_Create для подкласса. В примере <CDROM>:\RLA \VPI\EXAMPLES\CLASS на прилагаемом CD-диске показано, как создать подклас- сы дерева и редактора. Для создания любых инструментов, которые могут быть помещены в специальные элемент управления, необходимо использовать такой же механизм, как и в дерезе Хитрость заключается в том, что дерево изменяет обработчик событий для окне, запоминает обработчик событий для подкласса и передает события обратно подклз: су (листинг 26.25). -«f>5.. • . "j-.- 1-.«л•«* -JT '« Л"А ::-л -• •.-•*! ./ • : '>•' "•.> ' -л- = : • 3 =г-''f . *: ' ' : 'ji-?'J.'Z "''У: -t’ ’X?; ‘ ' - :Л, • . ' . ' ' ....... ’ J - ’ ' ’ clauses tree_CreateSubClass (Window,....................) :- OldHandler = win GetHandler(Window),
Гпава 26. Средства создания графического интерфейса 801 assert(tree_class_callback(Window, OldHandler)), win_SetHandler(Window,tree_class_handler). tree_ehandler(Window, e_Create(....)}. tree_class_handler (—Window, Event, Ret) : - tree_class_callback(_Window, USER—CALLBACK) , Ret - USER—CALLBACK(Window, Event),I, tree_after_handling(Window, Event). tree_class—handler(_Window, Event, Ret):- Ret = tree_ehandler(Window, Event). tree_after_handling(Window, e_Destroy()) tree—ehandler(Window, e_Destroy()). tree_after_handling(Window, e^Size()):- tree_ehandler(Window, e_Size()). tree_after_handling(_,_). Подклассы элементов управления Обычно все элементы управления, порожденные от одного класса, совместно ис- пользуют один обработчик событий окна. Подкласс — это один или несколько эле- ментов управления, принадлежащих одному и тому же предопределенному классу элементов управления (стандартные типы элементов управления), чьи события пере- хватываются и обрабатываются обработчиком событий другого окна перед передачей заданному по умолчанию обработчику событий предопределенного класса элементов управления. Для создания подкласса вызовите предикат win_SetSubClassHandler, чтобы связать обработчик событий нового подкласса с элементом управления. Для этого элемента управления каждое событие VPI сначала будет передаваться определенному обработ- чику событий подкласса и затем, если событие не обрабатывается обработчиком событий подкласса или предложение обработчика событий завершилось неуспеш- но, — заданному по умолчанию обработчику событий первичного предопределенно- го класса элементов управления. В VPI можно создать подкласс только для пред- определенных классов элементов управления (не для специальных элементов управ- ления и не для обычных окон или диалоговых окон). Реализация интерактивной справки Для активизации интерактивной справки в VPI используются следующие преди- каты : vpi_ShowHelp(string FileName) vpi—ShowHelpContext(string FileName, long Contextld) vpi—ShowHelpKeyWord(string FileName, string Keyword) vpi—HelpClose()
802 Часть V. Разработка графического интерфейса пользователя Работа с метафайлами MS Windows может сохранять изображения не только в битовых массивах, но и в метафайлах. Метафайл — запись фактических операций рисования, необходимых для создания изображения. В файле имеются структуры, которые содержат доста- точно информации для каждой операции рисования. Изображение может быть соз- дано позже посредством выполнения этих операций рисования в окне. Преимущество метафайлов состоит в том, что они полностью независимы от уст- ройства. Когда битовый массив отображается на принтер, должна быть масштабиро- вана каждая точка. Но для метафайла изображения выполняются в контексте нового устройства. Недостаток состоит в том, что метафайлы непереносимы на другие платформы. В зависимости от конкретных изображений может быть существенное различие по скорости рисования изображения из метафайла и из битового массива. Загрузка метафайла из файла Метафайл должен быть загружен в память при помощи предиката mf_Load перед тем, как он будет отображен в окне. Не забудьте вызвать предикат mf_Destroy для освобождения занимаемой памяти после того, как вы закончите работать с мета- файлом. Metafile == mf_Load(string FileName) Metafile - mf_Load(string FileName, ret RctMf, unsigned Resolution) Отображение метафайла в окне После того как метафайл загружен в память, он может быть отображен в окне или на принтере при помощи предиката mf_Piay/3 или mf__Piay/2. Обратите внимание, что метафайл сохраняет координаты границ изображения в за- головке. Предикат mf_Piay/3 использует границу изображения для того, чтобы раз- местить изображение в прямоугольной области, задаваемой параметром Rect. mf_Play(window Window, metafile Mf, ret Rect) Предикат mf__Play/2 просто отображает метафайл в прямоугольник с координатами границ изображения. mf_Play(window Window, metafile Mf) Поэтому, чтобы отобразить метафайл в желаемой позиции при помощи предиката mf_Play/2, программист должен использовать соответствующую систему масштаби- рования, коррелированную с координатами границ изображения. Предикаты win_SetMapMode и win SetMapScale устанавливают систему масштабирования. Уничтожение метафайла После того как ваше приложение закончило работу с метафайлом, память должна быть освобождена при помощи предиката: mf_Destroy(metafile Mf)
Гпава 26. Средства создания графического интерфейса 803 Запись метафайла В метафайл можно записать несколько операций рисования, выполняя следующие шаги: 1. Вызовите предикат mf_Open, который инициирует запись операций рисования. Window = mf__Open() Предикат mf_Open возвращает дескриптор окна, который должен использоваться во всех операциях рисования в метафайле. 2. Выполните несколько операций рисования в окне метафайла для создания изо- бражения. 3. Вызовите предикат mf ciose для окончания записи операций рисования и полу- чите дескриптор метафайла. Metafile = mf_Close{MetafileWindow) 4. Созданный метафайл может быть сохранен в файле на диске вызовом предиката mf_Save. f_Save(Metafile, Ret, FileName) Значение Ret используется для определения желаемого размера изображения в ме- тафайле, поэтому метафайл может быть масштабирован позже во время его отобра- жения. Обработка ошибок VPI обеспечивает безопасность для приложений, опираясь на механизм обработки ошибок, который обнаруживает неуспешное завершение обработчиков событий, выходы и ошибки во время исполнения в предикатах обработчиков событий (или в предикатах, вызываемых ими). Проверка правильности параметров Чтобы ускорить отладку приложений, VPI выполняет множество проверок правиль- ности параметров в предикатах. Если имеются какие-либо проблемы, появляется сообщение об ошибке на этапе исполнения (сообщается об исходной позиции "пло- хого'* вызова, если такая информация доступна). Эта проверка правильности пара- метров требует времени, поэтому для хорошо протестированных приложений ее можно отключить: vpi_ArgValidation{boolean On/Off) Установка нового обработчика ошибок Если произошла ошибка и она не была перехвачена, то VPI перехватывает попытку выхода из приложения в обработчике событий и выполняет заданную по умолчанию обработку ошибок посредством вызова общего диалогового окна dlg_Error.
804 Часть V. Разработка графического интерфейса пользователя Существует возможность установить определяемый пользователем предикат обра- ботки ошибок, который мог бы, например, регистрировать ошибку в файле и реко- мендовать пользователю послать этот файл разработчику. Предикат обработчика ошибок должен быть объявлен как принадлежащий домену errhandler и принимающий на вход два параметра: дескриптор окна и номер ошибки. Далее приведен типичный обработчик ошибок в приложении (листинг 26.26). ^Листинг 26.26 .... ......... Л..;....... predicates my_eгr_handler : erгhandlег clauses my_err_handler(Window, Err) lasterror(ErrorNo, FileName, includeFileName, Position), format(S,"ErrorNo=%, FileName=<%>\nIncludeFileName=<%>, Position-%", ErrorNo, FileName, IncludeFileName, Position), file_str("error.log", S), dlg_Note( "Internal error\nPlease send the file ERROR.LOG to developer"). Новый обработчик ошибок может быть установлен при помощи предиката: vpi_SetErrorHandler(my_err_handler) Для получения точных позиций ошибок от предиката lasterror необходимо пара- метру компилятора Error level (выбрав команду Options | Project | Compiler | Error- level) присвоить значение medium или maximum. Получившееся в результате увеличен- ное приложение будет работать медленнее, поэтому в хорошо протестированных приложениях можно регистрировать только номер ошибки. Приложение может быть откомпилировано без включения диагностической информации в исполняемый файл. Функции API операционной системы VPI поддерживает наиболее общие свойства систем управления окнами. Однако для специальных приложений бывает необходимо идти ниже уровня VPI. Уровень VP1 — это, по существу, множество функций, которые выполняют вызовы основного API операционной системы. Однако из VPI можно прямо вызывать все основные функ- ции API операционной системы. Если вам нужно воспользоваться преимуществом специальных возможностей "род- ной" операционной системы, вы можете непосредственно вызывать функции основ- ного API операционной системы (Microsoft Windows, РМ и т. д.). Нужно заметить, что это сделает ваше приложение непереносимым, поэтому рекомендуется изолиро- вать такие вызовы (в модуле, который содержит код, зависимый от операционной системы). Эта мера, если позже вы решите перенести приложение на другую опера- ционную систему, позволит переписать только зависимые от операционной системы
Гпава 26. Средства создания графического интерфейса 805 части. Вы можете также использовать автоматически определяемые константы (ws win, ws__pm и т. д.) внутри конструкции if def . .. enddef, чтобы условно компи- лировать разделы исходного кода. Вызов внутренних функций GUI операционной системы В VPI функции API операционной системы могут быть объявлены как глобальные предикаты. Соглашения языка о вызовах функций API зависят от платформы (табл. 26.21). Таблица 26.21. Соглашения языка о вызовах функций API для разных платформ Платформа Соглашения языка Win 16 PASCAL Win32 STDCALL PM SYSCALL Для 16-разрядной MS Windows подкаталог WINBIND содержит объявления боль- шинства вызовов функций API 16-разрядной MS Windows. Включив файлы WINDOWS.CON, WINDOWS.DOM и WINDOWS.PRE из каталога WINBIND \INCLUDE, вы легко можете вызывать непосредственно функции API 16-разрядной MS Windows. Создание окна вне VPI Существует возможность создать окно, обращаясь непосредственно к функциям API операционной системы, и установить предикаты обработчика событий, которые работают на внутренних событиях API операционной системы. Внутренние события API операционной системы для окна VPI Для окна, созданного с использованием VPI, уровень VPI посылает относительно немного событий высокого уровня предикату обработчика событий окна. Однако можно получить все события, которые базовый GUI операционной системы посыла- ет окну, при помощи предиката: win_EnableHook(Window, boolean OnOff) Если предикат вызван с параметром ь_тгие для окна, то окно будет получать собы- тия eNative в виде: my__event_handler (Window, e__Native (Message, WParam, LParam), 0) : —
806 Часть V. Разработка графического интерфейса пользователя где Message — дескриптор события GUI операционной системы, WParam — значение word для 16-разрядной платформы, long — для 32-разрядной платформы и LParam — значение long (см. пример в каталоге <CDROM>:\RUN\VPI\EXAMPLES\HOOK). Получение дескриптора окна GUI операционной системы Параметр VPI Window фактически является дескриптором окна GUI операционной системы. Он может быть просто преобразован предикатом cast к правильному до- мену для дескриптора окна GUI операционной системы. Однако более правильным будет использование предиката win_GetAttrVal: NativeWin = win_GetAttrVal(Window, attr_native_window) Получение контекста устройства GUI операционной системы Контекст устройства (DC, Device Context) GUI операционной системы, ассоцииро- ванный с окном, можно получить вызовом предиката: DC = win_GetNativeGraphicContextWindow,ReleaseFlag) Предикат возвращает контекст устройства, который может быть использован для операций рисования в собственных окнах GUI операционной системы, и флаг, ко- торый должен быть передан предикату win_ReleaseNativeGraphicContext для осво- бождения контекста устройства. win_ReleaseNativeGraphicContext(Window,DC,ReleaseFlag) Запуск внешних приложений Из программ VPI можно запускать любые внешние программы при помощи преди- ката: Applicationld = vpi_CreateProcess(ReceiveMsgWindow, Program, Param, TaskWinlnitFlag) Здесь program ~ это строка, определяющая путь и имя файла приложения, которое будет выполнено. Param — строка, определяющая командную строку для приложе- ния. Переменная TaskWinlnitFlag может быть одним из следующих флагов: wsf_Invisible, wsf_Minimized, wsf_Restored, wsf_Maximized. Если TaskWinlnitFlag = wsf_Invisible, загруженное приложение запускается в фоновом режиме, иначе — в приоритетном режиме. Предикат возвращает иден- тификатор Applicationld загруженного приложения (если ReceiveMsgWin О Null win). Этот идентификатор приложения будет возвращен в событии e_EndApplication (Applicationld), которое будет послано указанному предикату' ReceiveMsgWindow, чтобы уведомить о завершении загруженного приложения.
Гпава 26. Средства создания графического интерфейса 807 Загруженное внешнее приложение с идентификатором Applicationld можно пре- рвать при помощи предиката: vpi_CloseProcess(Applicationld) Модуль, загруженный при помощи предиката vpi_CreateProcess, может быть при- ложением не только основной операционной системы, поэтому вызов vpi_closeProcess не гарантирует завершения указанного приложения, например, если это приложение MS-DOS, загруженное из программы Windows. Поэтому про- граммист должен ждать событие e_EndApplication (Applicationld), чтобы гаранти- ровать завершение приложения Applicationld. Если вы определите ReceiveMsgWin = Nullwin, где Nullwin — это специальный деск- риптор окна, который в вашей программе должен определяться следующим образом: NullWin = cast(window, 0) то: □ никакое окно не будет получать событие e__EndApplication (Applicationld) для уведомления, что загруженное приложение завершено; □ предикат vpi_CreateProcess возвращает Applicationld = -1 вместо реального идентификатора приложения загруженного модуля; □ загруженное приложение не может быть закрыто вызовом предиката vpi_CloseProcess (Applicationld). Обратите внимание, что идентификатор приложения не соответствует идентифика- тору экземпляра приложения, который может быть получен как значение attr_win_instance при помощи предиката vpi_GetAttrVal.
ЧАСТЬ VI Возможности визуальной среды разработки Глава 27. Особенности визуальной среды разработки для опытного пользователя
ГЛАВА 27 Особенности визуальной среды разработки для опытного пользователя В этой части мы последовательно рассмотрим свойства визуальной среды разработки Visual Prolog. Использование VDE В процессе изучения визуальной среды разработки (VDE, Visual Development Environment) вам уже не раз приходилось сталкиваться со множеством различных меню. Они всегда появляются как меню основного окна, которое мы называем ок- ном Task. Обычно в нем доступны меню File, Edit, Project, Options, Help и Window, но при активизации некоторых других окон в меню могут появиться дополнитель- ные пункты. Контекстные меню Большинство окон в VDE имеют контекстные меню, которые могут быть вызваны нажатием правой кнопки мыши или комбинации клавиш <Shift>+<F10>. Команды в контекстном меню в основном отображают наиболее часто используемые функции в текущей ситуации. Ускоряющие клавиши Многие команды меню имеют ассоциированные с ними ускоряющие ("горячие") клавиши (табл. 27.1). Использование "горячих" клавиш позволяет быстрее выбирать команды меню, чем это можно сделать с помощью мыши (см. гл. 25).
812 Часть VL Возможности визуальной среды разработки Таблица 27.1. Торячие" клавиши Клавиша Функция <Ctrl>+<l> Вызвать браузер идентификаторов ресурсов <Ctrl>+<T> <Ctrl>+<B> <F8> <F2> <Alt>+<X> <Shift>+<Ctrl>+<D> <S h ift>+<Ctrl >+< C> <Alt>+<F9> <F9> <Shift>+<F9> Вызвать отображение дерева проекта Открыть браузер кода Выполнить команду меню File | New Выполнить команду меню File | Open Выполнить команду меню File | Save Выполнить команду меню File | Exit Открыть окно редактора, позиционированное на объявление выбранного предиката Открыть окно редактора, позиционированное на предложения выбранного предиката Выполнить команду Project | Build Выполнить команду Project | Run (которая включает в себя выполнение команды Build, если это требуется) Выполнить только компоновку <Ctrl>+<F9> Компилировать модуль <Ctrl>+<Break> Прервать выполнение команды Build <Ctrl>+<Shift>+<F9> Вызвать отладчик <Ctrl>+<G> <Ctrl>+<S> <Ctrl>+<F7> <Ctrl>+<F8> <Ctrl>+<A> Выполнить цель в текущем окне Сохранить проект и все открытые файлы Выполнить команду Project | New Выполнить команду Project | Open Вызвать эксперта приложений <Alt>+<F8> Выполнить построения только ресурсов <Ctrl>+<F1 > Отобразить окно с содержанием интерактивной справки <F1> Отобразить окно с контекстной справкой <Ctrl>+<C Копировать выделенную информацию в буфер обмена <Ctrl>+<V> Выполнить вставку из буфера обмена. Вставляемая информация заменит любую выделенную информацию <Ctrl>+<X> Выполнить вырезание выделенной информации в буфер обмена <Ctrl>+<W> Вызвать эксперта окон и диалоговых окон в том состоянии, в котором он использовался последний раз <Shift>+<Ctrl>+<W> Вставить вызов предиката для создания окна или диалогового окна
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 813 Клавиша Функция Таблица 27.1 (окончание) <Shift>+<Ctrl>+<v> <Shift>+<Ctrl>+<S> <Shift>+<Ctrl>+<!> <S hift>+<C tri >+<K> Вставить вызов предиката VPI Вставить вызовов стандартного предиката Вставить прототип вызова для указанного предиката Вставить ключевое слово <Ctrl>+<Tab> <F11> <Alt>+<F2> Изменить активное окно (только для Windows) Закрыть все окна редактора Переместить курсор в позицию последней зарегистрированной в буфере обмена ошибки исполнения Панель инструментов и строка подсказки Как мы уже знаем, в VDE панель инструментов находится в окне Task. Часто ис- пользуемые команды меню могут быть выполнены и при помощи кнопок на панели инструментов (рис. 27.1). Рис. 27.1. Панель инструментов Каждая из пиктограмм (табл. 27.2) на панели инструментов выполняет ту же функ- цию, что и соответствующая команда меню. Таблица 27.2. Команды меню и кнопки панели инструментов Кнопка панели Команда меню инструментов Кнопка панели Команда меню инструментов File | New File | Open File | Save Edit | Undo Edit | Redo Edit | Cut Edit | Copy Edit | Paste Project | Compile file Project | Build Project | Run Project | Debug
814 Часть VI. Возможности визуальной среды разработки Таблица 27.2 (окончание) Кнопка панели Команда меню инструментов Кнопка панели инструментов Project | Test Goal Project | Browse Project | Tree Команда меню Options | Temporary | Font J Help | Local Help Строка подсказки В нижней части окна Task, как уже говорилось, расположена строка подсказки. Она разделена на две части (рис. 27.2). Рис. 27.2. Строка подсказки Левое поле используется для отображения контекстно-зависимой информации, на- пример, подсказок для командных кнопок на панели инструментов или информации о текущем элементе управления в редакторе диалоговых окон и т. д. Крайнее правое поле используется построителем программ (make facility) для ото- бражения состояний генерации/компиляции/компоновки текущего ресурса. Файл проекта Для создания приложения в Visual Prolog вы должны использовать команду эксперта приложений Project | New, в котором вы должны указать имя проекта и другие па- раметры. Эксперт приложений, если необходимо, создаст указанный подкаталог и файл базы данных проекта <ProjectNanie>.PRJ или <ProjectNanie>.VPR. База данных проекта содержит все настройки компилятора и компоновщика, уста- новки каталогов, имена и расположение всех исходных модулей проекта, характери- стики пользовательского интерфейса и многое другое. В ней сохраняются все свой- ства, настраиваемые при помощи команды меню Options | Project. Имя файла проекта (с расширением prj или vpr) при запуске VDE (файл VIP.EXE) может быть передано файлу VIP.EXE в качестве параметра командной строки, что вызовет запуск среды разработки VDE с открытым указанным проектом. Ассоции- рование файлов с расширениями vpr и prj с VDE позволяет двойным щелчком мы- ши на любом имени файла с такими расширениями автоматически открывать вы- бранный проект в Visual Prolog VDE.
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 815 Утилита VIPCONV.EXE преобразует базу данных <ProjectName>.VPR в текстовый формат и обратно. VIPCONV используется для преобразования проектов между 16- и 32-разрядными платформами, т. к. формат файлов внешней базы данных раз- личен на этих платформах. VIPCONV также применяется для преобразования фай- лов проекта, если нужно использовать файл проекта вместе со старыми версиями VDE (более подробно см. интерактивную справку VDE). Обратите внимание, что при работе над проектом группы программистов Visual Prolog может разделить содержимое VPR-файла между PRJ-файлом (в текстовом формате), который является полным описанием проекта, и небольшими текстовыми файлами, описывающими ресурсы проекта: окна, диалоговые окна, панели инстру- ментов и т. д. Такое разделение имеет три преимущества: □ при использовании систем управления исходным кодом (таких как Visual Source- Safe или PVCS) можно блокировать ресурсы и сохранять различные версии; □ обеспечивается возможность разделять ресурсы проекта между несколькими про- граммистами, поддерживая удобную и безопасную работу группы программистов над одним проектом; □ одни и те же ресурсы можно использовать сразу в нескольких проектах. Окно проекта Это окно автоматически появляется при открытии проекта. Если окно проекта за- крывается, то закрывается и сам проект. Окно проекта содержит список всех ком- понентов приложения Visual Prolog. HCAProgramFilesWIP52\D0C\GUIDT0UR\Mypro... НИЕЗ win_cross_create Д-. win_cross_eh gl'DIALOGS.PRO g DIRLIST.PRO B| EDWIN.PRO Bl MYPROJ.PRO g PICTURE.PRO g| SWEEP.PRO Bl TREE.PRO Й VPITOOLS.PRO dialog.pro t A.AvpMr dialog^checkeditp Л .. ; И Edit . J г Delete j ''H t Attributej j riTtfn | jJkj dialogic reate dialog_createmod ($3 Hialrtri ггаЫлглпг Рис. 27.3. Доступ к исходному модулю через окно проекта На рис. 27.3 показано окно проекта. Нажатие одной из кнопок на панели инстру- ментов с левой стороны изменяет содержимое списка в центре окна. Этот список
816 ЧастьУкВозможности визуальной среды разработки отображает все компоненты проекта, которые соответствуют типу компонента нажа- той кнопки. При нажатии кнопки Module в центральной части окна в дереве проек- та отображаются исходные модули проекта; в остальных случаях центральная часть окна представляет собой список зарегистрированных компонентов. Выбрав компонент в списке/дереве, вы можете нажать кнопку на панели инстру- ментов справа, чтобы активизировать соответствующий инструмент для работы с данным компонентом. Дважды щелкнув на имени компонента, вы откроете его в редакторе данного типа компонентов. На левой стороне окна проекта находятся следующие кнопки: □ Module. Дерево проекта содержит все исходные модули текущего проекта. При выполнении команды Project | Build эти файлы будут откомпилированы. Помимо добавления файлов Пролога, можно регистрировать и другие файлы: исходные файлы языка С (или другого языка), файлы с расширениями lib и obj. Если вы регистрируете С- или СРР-файлы, они будут автоматически откомпилированы (с использованием указанного компилятора С) при построении проекта. Дважды щелкнув на исходном файле в списке, вы откроете его в текстовом редакторе. Исходные модули сохраняются в обычных файлах. Если вы регистрируете OBJ- файлы или LlB-файлы, то они будут включены в сценарий компоновки. Эти файлы недоступны для редактирования. Первоначально (перед компиляцией модулей) отображаются только имена мо- дулей. После компиляции модуля сгенерированная компилятором информация для браузера исходного кода используется для отображения дополнительной инфор- мации о содержимом модуля. Перед названиями таких модулей в дереве проекта располагаются знаки + (плюс) или — (минус). Дважды щелкнув (или нажав клавишу <Enter>) на имени текстового файла в де- реве проекта, вы откроете этот файл в текстовом редакторе для соответствующего типа файла. Щелкнув (или нажав стрелку вправо) на знаке + перед текстовым файлом, вы раскроете более подробный обзор информации о файле, который содержит: • список всех PRO-файлов (со знаками + или -), прямо или косвенно включен- ных в файл. В свою очередь вы можете щелкнуть на знаке + перед этими файлами, чтобы раскрыть их обзор; • список глобальных предикатов, которые определены в файле (т. е. таких, чьи предложения находятся в файле); • список локальных предикатов в алфавитном порядке, которые определены в файле. Если предикат объявлен в классе, то к имени предиката будет при- соединено имя класса. Дважды щелкнув на имени предиката, вы загружаете файл в текстовый редактор (или активизируете окно подходящего текстового редактора, если файл уже от- крыт) и размещаете курсор на первом предложении предиката. Пиктограммы перед именами глобальных предикатов — цветные, а перед имена- ми локальных предикатов — черно-белые.
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 817 Щелкнув (или нажав стрелку влево) на знаке —, вы закрываете обзор файла. Щелкнув на корневом элементе исходных модулей (в дереве проекта), вы закры- ваете обзор для всех раскрытых файлов. Щелкнув правой кнопкой мыши на имени модуля, вы активизируете всплываю- щее меню, в котором находится список всех файлов (с расширениями con, dom, pre, inc и т. д.), включенных в модуль, из этого меню их можно открыть. □ Dialog. В окне отображается список диалоговых окон проекта. Дважды щелкнув на имени диалогового окна, вы откроете редактор диалоговых окон для этого ок- на. Компоновка всех диалоговых окон полностью сохраняется в VPR-файле про- екта или в соответствующем файле описания ресурса при использовании PRJ- файла. Основной целью редактора диалоговых окон является определение и раз- мещение элементов управления в диалоговом окне. □ Window. В окне отображается список окон, сохраненных в файле проекта. Дваж- ды щелкнув на имени окна, вы откроете редактор окон. Здесь можно поместить в окно строку заголовка, полосы прокрутки и элементы управления, присоеди- нить меню и добавить панели инструментов и т. д. Обратите внимание, что в ре- жиме MDI невозможно изменить компоновку окна Task; в этом случае вместо редактора окна в окне Task активизируется диалоговое окно Window Attributes. О Menu. Окно содержит список меню, зарегистрированных в проекте: выпадающих и всплывающих. Дважды щелкнув на названии меню, можно открыть редактор меню. □ Toolbar. Окно содержит список панелей инструментов, определенных в проекте. Дважды щелкнув на названии панели инструментов, можно открыть редактор панелей инструментов. □ String. Окно содержит список групп строковых ресурсов. Дважды щелкнув на элементе списка, вы откроете редактор строковых ресурсов для данной группы строк. □ Icon. Окно содержит список пиктограмм в файлах ресурсов. Перед использова- нием пиктограмм в диалоговых окнах и панелях инструментов они должны быть зарегистрированы в этом списке. В операционной системе Windows пиктограммы сохраняются в файлах с расширением ico. Дважды щелкнув на имени пикто- граммы, вы откроете графический редактор. □ Cursor. Окно содержит список зарегистрированных в проекте курсоров. В опера- ционной системе Windows курсоры сохраняются в файлах с расширением cur. □ Bitmap. Список содержит зарегистрированные растровые изображения (файлы с расширением bmp). Дважды щелкнув на имени изображения, вы открываете гра- фический редактор VDE или (для больших изображений в MS Windows) графи- ческий редактор MS Paint. □ Help Topics. Список содержит названия разделов справки для проекта. Интерак- тивная справка хранится в файле <ProjectName>.HAM. Дважды щелкнув на на- звании раздела, вы откроете окно редактора гипертекста для этого раздела. Описания диалоговых окон, меню, строк, пиктограмм, курсоров и растровых изо- бражений располагаются в базе данных проекта и будут включены в сгенерирован- ные файлы ресурсов (с расширением гс или res).
818 Часть VI. Возможности визуальной среды разработки При создании нового переносимого ресурса (окно, диалоговое окно, меню, панель инструментов или группа строк) пользователь может выбрать — размещать ресурс в отдельном файле описания ресурса или нет. Такие отдельные файлы описания ресурса содержат всю информацию о ресурсах (включая все атрибуты) и настройки для эксперта кода. Одни и те же файлы описаний ресурсов можно использовать в различных проектах. Если VDE настроен для совместной работы нескольких про- граммистов над одним проектом, то все ресурсы сохраняются в отдельных файлах описаний ресурсов. Операции над компонентами проекта Пиктограммы на панели инструментов с правой стороны окна вызывают выполне- ние следующих действий с компонентами проекта. □ New. Добавляет новый компонент. Информация, необходимая для нового ком- понента, зависит от типа выбранного компонента. □ Edit. Открывает редактор для заданного компонента. □ Delete. Удаляет выбранный компонент (или несколько) из проекта. Если компо- нент расположен в базе данных проекта, он будет полностью удален. Если ком- понент содержится в файле, то из базы данных проекта удаляется связь между файлом компонента и проектом. Сам файл физически не удаляется и может быть повторно связан с проектом при помощи кнопки New. □ Attribute. Активизирует диалоговое окно для редактирования атрибутов компо- нента, таких как имя, идентификатор ресурса, флаги стиля, имена отдельных файлов ресурсов. □ Code Expert. Для окон, диалоговых окон и панелей инструментов эта кнопка вы- зывает эксперт кода для выбранного компонента. Для компонентов, которые хранятся в базе данных проекта (меню, диалоговые окна, окна, панели инструментов, строковые ресурсы), вы можете использовать буфер об- мена, чтобы создавать копии компонентов. Стандартные операции вырезания, ко- пирования и вставки работают с элементами, отображаемыми в списке окна проек- та. Действие таких операций состоит в том, что определение выбранного компонен- та помещается в буфер или копируется из буфера обмена. Этот механизм позволяет передавать компоненты между различными проектами. Внимание! J Нельзя отменить действие операций вырезания, копирования и вставки, выполнен- ных в окне проекта. Чтобы импортировать компоненты из файлов с расширениями vpr, res, ехе или dll, можно использовать команду Resource | Import. Если PRJ-файл находится в режиме "только для чтения", то пользователь не может добавить/удалить ресурсы или модули. Кнопки New и Delete окна проекта будут за- блокированы.
Глава 27. Особенности визуальной среды разработки для опытного пользователя 819 Окно сообщений Окно сообщений Messages (рис. 27.4) отображает различные текущие сообщения, например, что файл или проект сохранен, что модуль откомпилирован и т. д. Это окно формирует файл регистрации событий VDE. Для просмотра большего числа событий в окне можно использовать полосы прокрутки или изменить размер окна. gg Messages Generating 'OBJ32\CLOCK.DEB’| C:\Program Files\VIP52\DOC\GUIDTOUR\Myproj.prj saved Project components has been saved : Compiling 'CLOCK.PRO* to 'OB^CLOCK.OBJ' Generating 'OBJ32\CLOCK.OBJ' . Search... /Search Font Clear ; . * * • •• •: •• Рис. 27.4. Окно сообщений Messages Текст из окна сообщений можно скопировать в буфер или изменить размер шрифта. При помощи правой кнопки мыши можно вызвать контекстное меню окна сообще- ний. Окно сообщений можно открыть при помощи команды меню Options | Global | Environment. В этом меню также можно установить число строк для окна сообщений и положение этого окна. Браузер кода Получить доступ к браузеру кода можно, выполнив команду меню Project | Browse, нажав комбинацию клавиш <Ctrl>+<B> или кнопку на панели инструментов (см. гл. 25). Браузер исходного кода (рис. 27.5) может отображать информацию о предикатах, доменах, фактах, разделах фактов, классах и абстрактных классах. Можно выбрать, что отображать: глобальную информацию для проекта или локальную информацию для модуля. Кнопка Search позволяет искать предикаты по части имени. Нажав одну из кнопок GoTo, вы можете открыть окно редактора с курсором, распо- ложенным или в точке объявления, или на предложении предиката. Обратите внимание, что в редакторе имеются комбинации клавиш для поиска объ- явлений или предложений предикатов. Когда имя предиката выделено в редакторе: □ комбинация клавиш <Shift>+<Ctrl>+<D> открывает редактор на определении (Definition) предиката; □ комбинация клавиш <Shift>+<Ctrl>+<C> открывает редактор на предложении (Clauses) предиката. Отображаемая в этом диалоговом окне информация собирается компилятором во время компиляции файлов проекта. Компилятор генерирует информацию для брау- зера, только если включена опция Generate Browser Information в диалоговом окне Compiler Options. Компилятор записывает эту информацию в файл <Project_Name>.BRO
820 Часть VI. Возможности визуальной среды разработки и помещает его в тот же подкаталог, что и файл с расширением obj. Поэтому брау- зер исходного кода может отображать информацию только тех файлов проекта, ко- торые предварительно были откомпилированы. Для обновления информации о лю- бых изменениях в исходных файлах нужно перекомпилировать их, что можно сде- лать при помощи команды меню Project | Build. Рис. ^7.5. Браузер кода Браузер идентификаторов ресурсов Браузер идентификаторов ресурсов (рис. 27.6) проекта дает представление о кон- стантах, определенных в ресурсах проекта. Для каждого компонента ресурса, выде- ленного слева, можно увидеть константы всех его подкомпонентов, перечисленных справа. Для доступа к браузеру идентификаторов ресурсов нужно выполнить команду Project | Identifiers или нажать комбинацию клавиш <Ctrl>+<!>. Дерево модулей проекта Как только проект откомпилирован, вы можете открыть дерево проекта (рис. 27.7), которое показывает зависимости и структуру включений файлов для проекта. Выбе- рите команду меню Project | Tree, нажмите комбинацию клавиш <Ctr >+<Т> или кнопку Й на панели инструментов.
Глава 27. Особенности визуальной среды разработки для опытного пользователя Resource Identifiers / Нате* ; |[DIAL0GS"] Person About dialog [WINDOWS] : D irectory ' Editor T ree window 1 Picture I Clock I Sweep Cross [MENUS] Sweep Popup < Sweep menu ide dlq about ..st. copy idc_dlgLabout_st_firm idc_dlgLabout-st_norrti idc_dlg_about_st_pn idc_help ide ok Clgse SS8 -- : J •• :.uS . ’ г > ft . •> : 'm.'iuunj.ji । ! А ?/? *, ’’ : ? \ • ; j Л j i j.ll IjlU.IU 11Л Constant: idd_dlg_about Рис. 27.6. Браузер идентификаторов ресурсов Рис. 27.7. Дерево проекта Project Tree Чтобы выбрать любой узел в дереве, нужно щелкнуть на нем кнопкой мыши. Дваж- ды щелкнув на узле любого исходного файла, вы откроете этот файл для редактиро- вания. Если вы щелкните правой кнопкой мыши на узле, то появится всплывающее меню, содержащее нижеприведенные пункты. □ Add Dependence. Добавляет информацию о зависимостях файла (например, что RES-файл генерируется из RC-файла), если такая зависимость не поддерживает- ся автоматически.
822 Часть VI. Возможности визуальной среды разработки Основываясь на структуре зависимостей файла проекта, средство построения проектов (Build facility) VDE (когда оно активизировано) проверяет, какие части проекта должны быть обновлены. Например, VDE автоматически поддерживает зависимость name.PRO -> name.OBJ Поэтому средство построения проектов VDE перекомпилирует файл name.OBJ, если соответствующий файл name.PRO (или любой из включаемых в него фай- лов) был изменен. □ Add Including. Регистрирует включаемые файлы для данного узла. Для некоторых типов файлов (например, файлы С) это необходимо делать вручную. □ Delete Node. Удаляет выбранный узел. □ Add Script. Добавляет специфические действия, которые будут применены к узлу (файлу) перед действиями транслятора, которые применяются ко всем файлам проекта этого типа. Во время построения проекта утилита построения проекта использует правила трансляции, определенные сценариями построения для файлов с заданными расширениями. При добавлении сценария к узлу этот сценарий будет выполнен перед трансляцией данного узла. Например, программист может добавить про- грамму, обновляющую версию программы и вставляющую дату и время компи- ляции в некоторые исходные файлы. Обратите внимание, что здесь сценарии должны быть обыкновенной командной строкой DOS. В командной строке вы можете использовать символы сценариев, которые будут замещены перед выпол- нением команды. □ Edit Script. Редактирует сценарий для узла. Если правило трансляции для вы- бранного узла было переопределено при помощи команды Add script, то вместо этой команды во всплывающем, меню появится команда Edit script. □ Choose Script. Показывает список узлов, имеющих определенные пользователем сценарии трансляции, и разрешает выбирать сценарии для редактирования. □ Delete Script. Удаляет определенный пользователем сценарий трансляции для выбранного узла. □ Find Node. Отображает список файлов в дереве. Здесь можно быстро выбрать узел для отображения в клиентской области окна дерева проекта. □ Find Next. Повторяет последнюю операцию поиска узла и, если возможно, раз- мещает курсор на следующем подходящем файле. □ Show Selected. Прокручивает отображение дерева проекта для показа выбранных узлов в клиентской области окна дерева проекта. □ Show Parent. Выбирает предков выбранного узла и прокручивает отображение дерева проекта для показа этого узла в клиентской области окна дерева проекта. □ Show/Hide Subtree. Показывает/скрывает поддерево выбранного узла. □ Font. Выбирает шрифт для отображения дерева проекта.
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 823 Глобальные опции и INI-файлы В системном подкаталоге Windows Visual Prolog создает файл с именем vip32_5x.INI или VIP16_5x.INI. Файл с расширением ini хранит глобальные настройки: список ранее открытых проектов (последний открытый проект автоматически открывается при запуске VDE). В INI-файле также хранятся все опции, заданные в диалоговых окнах, открывающихся из подменю Options | Global. В основном это настройки для редактора и параметров оболочки. Пункты подменю Options | Global (.INI) показаны на рис. 27.8. Environment External Tools Directories > Рис. 27.8. Меню Options | Global (.INI) Опции автоматического сохранения На вкладке Auto Save диалогового окна Environment (рис. 27.9) можно определить следующие опции: □ должен ли сохраняться INI-файл при закрытии оболочки Visual Prolog; □ должны ли сохраняться файлы с расширениями vpr и prj при закрытии оболочки Visual Prolog; □ должны ли сохраняться файлы с расширениями vpr и prj перед компиляцией; □ должен ли весь проект (файлы с расширениями vpr и prj и исходные файлы, от- крытые в текстовых редакторах VDE) автоматически сохраняться через опреде- ленные интервалы времени; □ должны ли редакторы выводить приглашение сохранить содержимое при закры- тии окна; □ должен ли PRJ-файл сохраняться на диске при его модификации. Environment Fonts:| ^^^z j': 4 33^^31^ -З?: 'Т I . Auto Save Project op. Exit j : ^"’z* 3. . 3’i /3: z z-z|:::,:: £’ z}:: : z' z:; z ?:-z!z!;>: z3:;:3z:J:<:Z:3j:>:< ?;<:.:< : 7, •?’z^-^.z z^’.::;!: zCz ^3. ’.-j; Q z . f“ Auto Save Project before Cojripilation , : ’ I Auto Save every 110 minutes < ''i.:. : i^Z : -f Z ;:' V '' < 3 " '"3'. ’ • '' :z - .?• : -z. :?'? iZ z ’ П Auto Save Editors on Close . : ? . P Auto Save £RJ-file immediately after Adding/Deleting a Resource MijLjjT-[i iripy*]ii_L_ iij_rLj>jjj/»-L7irmi> iTtfiinTiiurmrn ~ ~ " ‘ • -wr--1^- Cancel Рис. 27.9. Вкладка Auto Save диалогового окна Environment
824 Часть VI. Возможности визуальной среды разработки Шрифты На вкладке Fonts диалогового окна Environment (рис. 27.10) можно определить шрифт для редактора, дерева проекта и окна сообщений. Рис. 27.10. Вкладка Fonts диалогового окна Environment Опции окна сообщений На вкладке Messages Window диалогового окна Environment (рис. 27.И) можно опре- делить положение окна сообщений и количество отображаемых строк. Рис. 27.11. Вкладка Messages Window диалогового окна Environment Если необходим файл регистрации всех сообщений окна Messages, то можно задать имя файла, в который можно выводить всю информацию. При каждой компиляции создается новый файл, а старый файл теряется.
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 825 Другие опции На вкладке Miscellaneous (рис. 27.12) можно определить другие установки среды раз- работки. Рис. 27.12. Вкладка Miscellaneous диалогового окна Environment Опции построения Приглашение DOS может потребоваться для вызова инструментов из командной строки DOS, например, вызов компоновщика (Linker). В некоторых средах это мо- жет быть необходимо для правильной компоновки. Иногда использование DOS дает задаче компоновки более высокий приоритет, что позволяет ей выполняться быст- рее. Включите дпцию Use DOS Prompt (использовать командную строку DOS), если у вас возникают проблемы при построении проекта. Опции представления Здесь вы можете определить, должны ли быть отображены панель инструментов и строка подсказки, нужно ли показывать диалоговое окно, отображающее, сколько памяти использует VDE (рис 27.13). Рис. 27.13. Окно используемых ресурсов памяти
826 Часть VI. Возможности визуальной среды разработки Опции редактора Здесь можно определить цвета, с которыми элементы разных типов файлов (различ- ным расширением) отображаются в редакторе. Нажатие на кнопку Change Token Coloring открывает диалоговое окно Token Coloring (рис, 27.14), где можно определить размер интервала табуляции и схему цветов, ко- торая используется в редакторе для каждого типа исходных файлов (RC, DEF, фай- лы С, Prolog и др.). Рис. 27.14. Опции схемы цветов редактора. Диалоговое окно Token Coloring Каталоги внешних инструментов Выбрав команду меню Options | Global | External Tools Directories, вы можете опреде- лить расположение различных внешних для Visual Prolog инструментальных систем (рис. 27.15). External Tools Directories C:\Program FiiesXMicrosolt Visual Studi V| S ...... .... ... -. -----------------------:--(.. C IBM CSet++for OS/2 P VisualAge C++ for OS/2 й MS Visual C++ ISbit MS Visual C++ 32bit d 0 S /2 T oolk.it Resource Compiler Windows Help Compiler < OS/2 Help Compiler ; Phar Lap 286ID0S Extender Temporary dCancelj 4-: ? ' Help Wl Ki; Root Directory of MS Visual C++ 32bit У 1 ’: :4 }- - : j j • - 2 2^ ч = ? Рис. 27.15. Каталоги внешних инструментов. Диалоговое окно External Tools Directories
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 827 Если модули С являются частью проекта, то нужно определить местоположение компилятора С. Также часто определяют расположение компилятора справки, т. к. он не является частью Visual Prolog. Вы можете задать путь к инструменту прямо с клавиатуры или использовать кнопку Browse для вызова диалогового окна Set New Directory. Обратите внимание на специальное имя каталога Temporary. Он определяет каталог по умолчанию для размещения временных файлов для утилиты VDE Test Goal. Этот каталог используется, если нет открытых проектов и не установлена переменная среды temp в операционной системе. Эксперт приложений Эксперт приложений — это инструмент, который помогает создавать новые проекты Visual Prolog, а также изменять параметры этих проектов. Чтобы написать и отладить свое приложение GUI, вам нужно как минимум: напи- сать код запуска, стандартные обработчики событий, диалоговое окно About, а также ресурсы, меню, DEF-файлы. На реализацию этих стандартных задач "с нуля" вы можете потратить пару дней. Эксперт приложений поможет вам выполнить все эти операции, используя установленные вами опции, за одну минуту. Общие параметры После того как вы выбрали необходимую информацию в эксперте приложений, на- жмите кнопку Create для того, чтобы создать файлы, структуру каталогов, опреде- лить сценарии, меню и т. д. (рис. 27.16). Минимальная информация, которую необходимо ввести, — это имя проекта (в поле Project Name) и основной каталог (в поле Base Directory), в котором будут находить- ся файлы проекта. Application Expert - General Target | VPI Options | Other Options | User Info | HelpWaker \ "J Protect Namet |MyProject 7^ : ' .1 N ame of. VPR File: I мамммймммм1йм1 •3 i Г S upport f or S ource Control Systems and M ultiprogrammef Projects -3 -- ' ...' .’ ' '.‘ ‘ ' Г . A J " '! , < Base Directory: CAProgram Files\VIP52\bin\V/IN\32 growse.. • А::"'' ................ ........ ' * : I Create I S-Я № + №4"B №# №№ № 4 ft Ь *4 ft Cancel : ШМММ Рис. 27.16. Вкладка General окна Application Expert
828 Часть\/1. Возможности визуальной среды разработки Внимание! Вся эта информация может быть просмотрена и изменена при помощи команды ме- ню Options | Project | Application Expert. Имя проекта Для нового проекта необходимо задать имя в поле Project Name. Эксперт приложений использует это имя для автоматической генерации имен фай- лов проекта, например, <ProjectName>.PRJ, <ProjectName>.VPR, <ProjectName>.INC, <ProjectName>.CON, <ProjectName>.DOM, <ProjectName>.PRE, <ProjectName>.PRO и т. д. VDE использует эти имена файлов для размещения сгенерированного кода, идентификаторов и т. д. Поэтому если вы измените позже имя проекта, то необхо- димо при помощи инструкций VDE переименовать эти файлы. Если строка, определенная в поле Project Name, содержит символы, которые не мо- гут использоваться в именах файлов, то они будут заменены символами подчерки- вания. Из соображений переносимости не следует задавать имя файла, превышающее во- семь символов, в случае, если проект должен иметь возможность компилироваться в операционной системе Windows 3.x (или на другой 16-разрядной платформе). Имя VPR-файла Каждый проект хранится в файле проекта (с расширением vpr), который фактически является внешней базой данных Пролога, содержащей описания компонентов про- екта: исходные файлы Visual Prolog (и другие), окна и диалоговые окна, меню и панели инструментов, строки, пиктограммы, курсоры и растровые изображения. Эксперт приложений автоматически генерирует файл проекта с расширением vpr при создании проекта. По умолчанию файлу проекта присваивается имя <ProjectName>.VPR, но при необходимости это имя можно изменить. Основной каталог Необходимо указывать каталог, в котором будет находиться проект. При помощи кнопки Browse вы можете открыть браузер каталогов (рис. 27.17) и указать тот, ко- торый вам необходим. Браузер каталогов можно использовать для: □ определения существующего подкаталога для нового проекта; □ просмотра родительских каталогов и указания в поле Subdirectory имени нового подкаталога, который должен быть создан. Обратите внимание, что если вы выбираете каталог, который уже содержит файлы с теми же именами, то после того, как вы нажмете кнопку Create, эксперт приложе- ний выдаст предупреждение "These files will be overwritten" (Эти файлы будут переза- писаны). Чтобы этого избежать, сохраняйте каждый проект в его собственном основном каталоге (все проекты VPI содержат файлы с именами VPItools.DOM, VPItools.PRE, VPItools.PRO).
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 829 Рис. 27Л7. Выбор каталога для проекта Поддержка систем сохранения исходного кода Группа Support for Source Control Systems and Multiprogrammer Projects содержит флажок Multiprogrammer Mode и поле ввода Name of .PRJ File. Для получения более подробной информации обратитесь к разд. "Поддержка работы нескольких програм- мистов над проектом в VDE" данной главы. Режим работы нескольких программистов над проектом Отметьте этот флажок для разрешения работы нескольких программистов над про- ектом (Multiprogrammer Mode). Данное свойство может быть изменено на любой стадии проекта. Если режим работы нескольких программистов над проектом вклю- чен, то: □ все ресурсы будут сохраняться в отдельных файлах описания ресурсов (вместо внутреннего сохранения всех ресурсов в файле проекта с расширением vpr); □ будет сгенерирован PRJ-файл проекта; имя этого файла должно быть задано в поле Name of .PRJ File. Имя PRJ-файла Здесь вы должны определить имя PRJ-файла проекта. Параметры целевого файла Visual Prolog разработан как открытая система и предоставляет широкий спектр стратегий программирования. Существует также множество комбинаций на других платформах, таких как DOS, DOS Extended, 16- и 32-разрядная Windows, использо- вания SQL и т. д. Кроме того, Visual Prolog может объединять несколько компилято- ров С с различными библиотеками и стратегиями компоновки. Visual Prolog старается поддерживать все эти стратегии, и эксперт приложений мо- жет создавать правильные значения по умолчанию для большинства этих комбина-
830 Часть VI. Возможности визуальной среды разработки ций. Требуется только выбрать необходимую комбинацию платформы (Platform), стратегии пользовательского интерфейса (User Interface (Ul) Strategy), типа целевого файла (Target Туре) и основной программы (Main Program), см. рис. 27.18. Если вам нужна такая комбинация, которая явно не поддерживается Visual Prolog и экспертом приложений, то выберите наиболее близкую к ней из поддерживаемых комбинаций, а затем вручную измените сценарии построения, сгенерированные экспертом при- ложений. Описание сценариев построения см. в разд. "Построение, компиляция и компоновка " данной главы. Однако основная особенность Visual Prolog — это VPI (Visual Programming Interface, интерфейс визуального программирования), который содержит средства, позволяю- щие упростить программирование для Windows и других графических платформ на языке Пролог. Самые значимые преимущества VPI: □ высокоуровневый программный интерфейс приложений; □ высокая степень переносимости; □ интегрированные эксперты кода VPI могут применяться для генерации кода. На вкладке Target необходимо определить значения четырех параметров. Application Expert '/* General I Target I VPI Options I Other Options I User.Info I Help Maker • ' I I ' ' ’ ..» • >- •« ••••• -Г Й Platform И indows32 Ul Strategy. : VPI Target Type exe Main Program Prolog •” 1-4:- Мам Create •; Cancel Ml •K Л Рис. 27.18. Выбор целевого объекта для компилятора Платформа Платформу можно выбрать из следующих вариантов (рис. 27.19). □ DOS. Эта платформа используется для построения приложений DOS в реальном режиме. Выбрав этот вариант, вы сможете компилировать и строить программы, совместимые с предыдущими программами PDC Prolog и Turbo Prolog, а также использовать старую систему PDC Prolog управления окнами в текстовом ре- жиме. □ DOS Extended. Выбор этого варианта требует использования PharLap 286 DOS Extended. В этом режиме программы могут работать под DOS в защищенном
Глава 27. Особенности визуальной среды разработки для опытного пользователя 831 режиме и использовать всю дополнительную память компьютера. С некоторыми исключениями здесь могут быть откомпилированы все предыдущие программы PDC Prolog и Turbo Prolog в текстовом режиме. □ Windowsl6. Выбор этого варианта используется, чтобы строить приложения, ко- торые работают на 16-разрядной платформе Windows. Несмотря на множество вариантов выбора для программирования пользовательского интерфейса (User Interface, UI), предпочтительная стратегия — это VPI. □ Windows32. Выбор этого варианта позволяет генерировать приложения, которые будут работать на любой 32-разрядной Windows (Windows NT/2000 или Windows 95/98/МЕ). Приложения являются истинными 32-разрядными программами, ко- торые используют длинные имена файлов, соглашения о расширенных именах файлов и т. д. DOS DOS Extended Windowsl 6 ;Window$32 OS/216 bit OS/2 32 bit Рис. 27.19. Варианты выбора для платформы Операционные системы OS/2, как 16-разрядные, так и 32-разрядные в настоящее время не используются. Пользовательский интерфейс В зависимости от того, какой целевой объект был выбран, существует несколько вариантов построения пользовательского интерфейса для приложения (рис. 27.20). Easy win Т extmode Other Рис. 27.20. Варианты выбора стратегии UI для 16-разрядной платформы Windows Эти варианты таковы. □ VPL Это предпочтительная стратегия UI для Visual Prolog. Использование VPI упрощает построение оконных приложений, которые запускаются под 16- и 32- разрядной Windows. VDE предоставляет множество инструментов для работы с VPI, а эксперты кода очень помогают в создании приложений. □ Easy Win, Если имеется маленькое приложение, которое не нуждается ни в каком пользовательском интерфейсе, кроме строки текста и ввода с клавиатуры, можно выбрать вариант EasyWin, который работает под 16- и 32-разрядной Windows. Приложение EasyWin запускается как реальное приложение Windows, но для вво- да и вывода могут использоваться только предикаты readchar, readln, write, writef и nl.
832 Часть VI. Возможности визуальной среды разработки □ WINBIND. Стратегия WINBIND применяется для построения приложений 16-разрядной Windows, которые обращаются прямо к API Windows 16. При этом используются заголовочные файлы и библиотеки из подкаталога WINBIND. □ Textmode. 16-разрядные приложения в текстовом режиме для DOS, DOS Extended могут использовать систему управления окнами PDC Prolog в текстовом режиме. Для 32-разрядной Windows приложения в текстовом режиме запускаются из командной строки операционной системы (консольные приложения для ввода И вывода могут использовать только предикаты readchar, readln, write, writef И nl). □ Other. При выборе этого варианта никакие предопределенные заголовки или библиотеки не включаются. В этом случае пользователь сам решает, что ему не- обходимо (в сценариях построения). Тип целевого объекта При любых выбранных платформе (Platform) и стратегии UI (UI Strategy) вы можете генерировать исполняемое приложение (рис. 27.21). ехе * II ;ехе j ь ьь----и_. - ж в. ь в. - ь Ж . - ж j в. Л Ж Id» I Рис. 27.21. Варианты выбора типа целевого объекта На платформе Windows также можно генерировать библиотеки динамической ком- поновки (файлы с расширением dll), которые могут быть вызваны из исполняемого приложения. Основная программа Вариант выбора основной программы (Main Program) определяет, какая языковая система будет ответственна за код запуска приложения (рис. 27.22). Рис. 27.22. Варианты выбора основной программы Если это Пролог, то инициализация будет выполнена в файле INIT.OBJ. Если вы- бран вариант С, то будет использоваться код инициализации и компоновщик С. Щелкнув на раскрывающемся списке Main Program, вы увидите поддерживаемые компоновщики. В настоящий момент они такие: □ Prolog; □ 32-разрядный MS Visual C++. Список компоновщиков зависит от выбранной платформы. Эксперт приложений по умолчанию предлагает правильную основную программу для комбинации платфор- мы (Platform), стратегии UI (UI strategy) и типа целевого объекта (Target Туре).
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 833 Опции VPI Диалоговое окно Application Expert имеет вкладку VPI Options (опции VPI) (рис. 27.23), на которой можно определить, какие VPI-пакеты будет использовать проект. Рис. 27.23. Определение VPI-пакетов, которые будут использоваться Возможны варианты, приведенные ниже. □ MDI Mode. Определяет, что будет создано многодокументное приложение. Обра- тите внимание, что в режиме MDI невозможно рисовать или создавать элементы управления в окне Task. 'Т.. □ Toolbar and Help Line. Определяет, что приложение должно иметь панель инстру- ментов в верхней части и строку подсказки в нижней части окна Task. □ 3D Look for Controls. Определяет, что элементы управления в диалоговом окне под MS Windows будут иметь 3-мерное представление. Для этого требуется, чтобы файл CTL3D32.DLL или CTL3DV2.DLL находился в каталоге W1NDOWS\SYSTEM или WINDOWS\SYSTEM32 □ Message Window. Определяет, что приложение будет иметь окно сообщений, по- этому в программе можно использовать предикат write. □ Filename to Include Packages: <VPITools.pro>. Эта опция доступна только при соз- дании нового проекта. Здесь вы можете определить имя файла, который будет включать исходные файлы выбранных пакетов VP1. По умолчанию это VPITools.pro. Все остальные варианты выбора означают, что файлы <VPI_Package>.DOM, <VPI_Package>.PRE и <VPI_Package>.PRO для соответствующих пакетов VPI будут включены в файлы проекта VPITools.dom, VPITools.pre и VPITools.pro (константы, определяющие, включать или нет пакеты, определены в файле <ProjectName>.lNC). Программист может использовать выбранный инструмент там, где ему это нужно.
834 Часть VI. Возможности визуальной среды разработки Другие опции На рис. 27.24 показана вкладка Other Options диалогового окна Application Expert. Application Expert -л,.'r?- jjr^OT^DgJJ rSOCKBIND \ > /:й jrpOCTTOOL,;; ^ГОСЙМЙЙ •I к mtj я 11 н । i -' i;? i Boject D : I* ^ТУП^Щг^ГуИМ1|ИМЙ*у ^.:r External Took Directories... IL ,НМ*»*М*Н<!>!й*!ШИм«Я^»МММШ4^ iMl'itf i: Г;:>? «мнём 3 <3 -t *- .=££I :^: ^! !!: ;!,j f ; ; ^ <x.>tL-^-.^---!-' , ,j' '-:L --^^' liWi'-'-'--ГР?^'-Ч L. : !• ,i, X: iitii ’ '•. •* J . ' V >! ’ f::!::. %. !,.!! -• -. . Л '.'/ ',‘i . !' i Л;''* "'4..:J : 3Jc': a . -• 1' -. : Lu- Г!*Л- 4 I'"!1 . : v.tfxS- ' N. л ЖФШМммушммиймм 4- treated .fenced Рис. 27.24. Дополнительные опции для проекта В групповом блоке Include other Tools можно установить/сбросить флажки, перечис- ленные ниже. □ SQLBIND. Определяет, что в проекте будут использован SQL интерфейс. Это приведет к включению заголовочных файлов SQLBIND и присоединению биб- лиотек SQLBIND. □ ODBCBIND. Определяет, что в проекте будет использован ODBCBIND, интер- фейс между прикладной программой, написанной в Visual Prolog, и открытым интерфейсом доступа к базам данных Microsoft (Open Data Base Connectivity, ODBC). Каталог ODBCBIND включает полное связывание c API ODBC, позво- ляя Windows-приложениям пользоваться всеми возможностями API ODBC. □ SOCKBIND. Определяет, что в проекте будет использован пакет SOCKBIND, который обеспечивает переносимый интерфейс между Visual Prolog и сокетами. Каталог SOCKBIND содержит библиотеки и объявления, обеспечивающие этот интерфейс. □ DOC_TOOL. Определяет, должен ли пакет DOC_TOOL быть включен в проект. Пакет обработки документов позволяет работать с документами различных форматов. Предикаты пакета могут преобразовывать из/в форматы RTF, HTML или IPF. □ PDCRUNT. Определяет, что в проект будут включены два заголовочных файла: PDCRUNT.DOM и PDCRUNT.PRE. Они содержат объявления некоторых до- полнительных предикатов, определенных в библиотеке PROLOG. L1B. Эти пре- дикаты в основном требуются для согласования с С, Delphi и т. д.
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 835 Генератор кода Кнопка Code Generator в окне Application Expert открывает диалоговое окно Code and File Generation, задающее настройки для эксперта кода. Настройки по умолча- нию показаны на рис. 27.25. Рис. 27.25. Опции генерации кода Перечислим опции генератора кода и файлов. □ Automatically update source files if resources have been modified — опция автомати- ческого обновления исходных файлов при изменении ресурсов позволяет пол- ностью отключить любое автоматическое обновление кода экспертом кода. □ Generate .RC and .Н files — опция генерирования файлов с расширениями гс и h определяет, что генератор кода должен генерировать в проекте файлы с расши- рениями гс и h для ресурсов. RC-файлы предназначены для компиляции внеш- них ресурсов, и Н-файлы — это заголовочные файлы для использования в ком- пилирующихся модулях С. □ Generate .RES and .CON files — опция генерирования файлов с расширениями res и con. Под Windows эти файлы обычно генерируются для ресурсов в проекте Visual Prolog. □ Generate ,HLP and HLPTOPICS.CON files - опция генерирования файлов с расширением HLP и файла HELPTOPIC.CON. Для приложений Windows вы мо- жете использовать генератор справки Visual Prolog для создания переносимой ин- терактивной справки. Каталоги проекта Здесь можно задать каталоги, используемые компилятором, компоновщиком, гене- раторами ресурсов и т. д. В именах каталогов могут быть символы сценария компоновки. Например, при за- дании имени $(PRODlR)VPI\include, $(PROD1R) будет автоматически расширен до фактического пути, где находится система Visual Prolog. Этот способ делает возможным перемещение системы в другое место без переопре- деления структуры каталогов и перемещение проекта в другое место без изменения сценариев построения.
836 Часть VI. Возможности визуальной среды разработки Каталоги исходного кода □ Portable Res — переносимые ресурсы (рис. 27.26). Этот каталог используется для определения расположения по умолчанию отдель- ных файлов ресурсов проекта, содержащих переносимое (в текстовом формате) описание переносимых ресурсов: диалоговые окна, окна, меню, панели инстру- ментов, строки. Этот подкаталог (обычно RES) находится в каталоге проекта. □ Sub-Directories for Images (non-portable resources) — подкаталоги для изображе- ний (непереносимые ресурсы) для Windows и OS/2. Эти подкаталоги используются для определения расположения по умолчанию непереносимых ресурсов: курсоров, пиктограмм и растровых изображений. Они находятся в каталоге переносимых ресурсов проекта. Рис. 27.26. Вкладка Source диалогового окна Project Directories Каталоги выходных данных □ Intermediate — промежуточный подкаталог (рис. 27.27). Этот подкаталог является местом размещения всех промежуточных файлов. При- мерами промежуточных файлов могут служить файлы с расширениями bro, Рис. 27.27. Вкладка Output диалогового окна Project Directories
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 837 obj, sym, map, rc, res, rtf и т. д. Обратите внимание, что утилита Test Goal также использует этот подкаталог для размещения сгенерированных файлов (включая сгенерированный исполняемый файл Test Goal с именем ”goal$000.exeu). При очи- стке проекта эти файлы можно удалить. Этот подкаталог (обычно OBJ) находится в каталоге проекта. □ Final — подкаталог для размещения сгенерированных исполняемых файлов (DLL) и файлов справки. Он (обычно EXE) находится в каталоге проекта. Другие каталоги □ System — системный каталог (рис. 27.28). В поле редактирования отображается каталог, из которого вызывается VDE Visual Prolog. Это поле нельзя редактировать. Каталог определяет значение символа $(SYSD1R). □ Project — в поле редактирования Project отображается каталог проекта. Это поле нельзя редактировать. Каталог определяет значение символа $(SYSDIR). Рис. 27.28. Вкладка Other диалогового окна Project Directories Включенные каталоги В списке отображаются каталоги, в которых компилятор будет искать включаемые файлы; каждая строка содержит один каталог (рис. 27.29). В этих каталогах компи- лятор будет искать любые включаемые файлы, имена которых не содержат полный (абсолютный) путь. Одна точка указывает на текущий каталог, который всегда явля- ется каталогом проекта. Две точки — на родительский каталог текущего каталога. При помощи кнопки Browse вы можете добавить в список новый каталог, при этом активизируется диалоговое окно Set New Directory. Также можно изменить сущест- вующий каталог, нажав кнопку Edit, при этом откроется диалоговое окно Edit Include Directory, где вы непосредственно можете ввести путь. Кнопки Up и Down позволяют изменять порядок, в котором компилятор будет использовать указанные включаемые каталоги.
838 Часть VI. Возможности визуальной среды разработки Рис. 27.29. Вкладка Include Directories диалогового окна Project Directories Каталоги внешних инструментов Это глобальные настройки для инструментов; они не сохраняются вместе с проек- том, но для удобства существует ссылка на диалоговое окно. Они сохраняются в INI-файле и были описаны в разд. "Глобальные опции и INI-файлы" данной главы. Обратите внимание на имя специального каталога Temporary (рис. 27.30). Оно опре- деляет каталог по умолчанию для размещения временных файлов утилитой Test Goal. Это свойство используется, только если нет загруженных проектов и в операционной системе не установлена переменная среды temp. External Tools Directories fa iCAProgram FilesVMicrosoft Visual Studi y | >*«***** £ IBM CSet++ for OS/2 , VisualAge C++ for OS/2 MS Visual C++ 1 Gbit :1. MS Visual C++ 32bft ; OS/2 Toolkit Resource Compiler J Windows Help Compiler OS/2 Help Compiler Phar Lap 28610OS Extender = Temporary £апсёГ 5- ift.- Рис. 27.30. Диалоговое окно External Tools Directories Опции компилятора Подробное описание опций компилятора рассмотрено далее (см. разд. "Опции компи- лятора" данной главы), здесь же заметим только, что диалоговое окно Compiler Options становится доступным лишь после создания проекта.
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 839 Окружение Это глобальные настройки среды визуальной разработки (VDE, Visual Development Environment), которые не сохраняются вместе с проектом, но для удобства на диало- говое окно Environment существует ссылка (эти настройки сохраняются в INI-файле и уже были описаны в разд. "Глобальные опции и INI-файлы" данной главы). Пользовательская информация Информация, определяемая на вкладке User Info диалогового окна Application Expert (рис. 27.31), размещается в заголовках исходных файлов Пролога в диалоговом окне по умолчанию, которое открывается командой меню Help | About. Application Expert General | Target | VPI Options | Other Options | User j ............................................................. ’ ’ r yrrnTilriHiiXl ..........1 Prolog Development Cente й •я- Author Visual Prolog •,, • .'y.L£,j: ч 4-; • i & •. •. • Hvi • Л •! "i . . •. •. Г*. •. • Copyright Copyright (c)1997 - Version Version 1.0 мим* Description N □ description ,4 /I К I £ Рис. 27.31. Вкладка User Info диалогового окна Application Expert Генератор справки Информация, определяемая на вкладке Help Maker диалогового окна Application Expert (рис. 27.32), используется для генерации интерактивной справки. Если вы не используете генератор справки как редактор справки проекта, то вам нужно отклю- чить опцию автоматической генерации в окне Code Generator. Выберите команду меню Options ( Project | Code Generator и сбросьте флажок Generate .HLP and HLPTOPICS.CON files. В противном случае утилита построения будет пытаться пе- рекомпилировать файлы справки при каждом вызове команды Rebuild All. На вклад- ке Help Maker диалогового окна Application Expert необходимо определить нижепри- веденные поля. □ Hyperbase Name. Определяет имя гипербазы справки — по умолчанию это < ProjectName >. НАМ. □ Help Compiler. По умолчанию компилятором справки Windows является HCW.EXE. Обратите внимание, что с Visual Prolog не поставляется компилятор справки
840 Часть VI. Возможности визуальной среды разработки Windows. Его вы можете найти в таких продуктах, как Visual C++ (и в других продуктах Microsoft), Delphi и некоторых других, а также "скачать" его из катало- га Softlib по адресу ftp://ftp.microsoft.coni/softlib. Рис. 27.32. Вкладка Help Maker диалогового окна Application Expert Файлы, используемые в проекте Для проекта MyProject, который использует VPI (UI Strategy), эксперт приложений генерирует файлы и структуру каталогов, приведенные в табл. 27.3. Таблица 27.3. Файлы и каталоги в демонстрационном проекте Файл или каталог Описание MyProject. VPR MyProject.PRJ MyProject.def MyProject.INC MyProject.PRO MyProject.dom MyProject. pre VPITools.pro Основной файл проекта для VDE Файл проекта, используемый в режиме работы нескольких про- граммистов над проектом Файл определений модулей, обеспечивающий дополнительную информацию для компоновщика Основной файл, определяющий включенные в проект пакеты Файл основного исходного модуля Объявления глобальных доменов для модуля MyProject.PRO Объявления глобальных предикатов в модуле MyProject.PRO Модуль, включающий PRO-файлы используемых в проекте инст- рументальных пакетов VPI
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 841 Таблица 27.3 (окончание) Файл или каталог Описание VPITools.pre VPITools.dom MyProject.ham Hiptopic .con MyProject.con MyProjectbkl MyProject.grd *.BAK EXE\MyProject.EXE EXE\MyProject.DLL EXE\MyProject.HLP EXE\PROLOG.ERR OBJ\MyProject.BRO OBJ\MyProject.MAP OBJ\MyProject.RES OBJ\*.OBJ OBJWDEB OBJ\MyProject.SYM RES\*.W1N RES\*.DLG RES\*.TB RES\*.MNU RES\*.STR RES\WinV.BMP RES\Win\*.ICO RES\Win\*.CUR Файл, включающий объявления глобальных предикатов (PRE- файлы), используемых в проекте инструментальных пакетов VPI Файл, включающий объявления глобальных доменов (DOM-фай- лы), используемых в проекте инструментальных пакетов VPI Гипертекстовая база данных (содержащая исходные описания разделов справки), созданная генератором справки PDC Файл, содержащий символьные константы, которые определяют идентификаторы разделов в интерактивной справке, создавае- мой генератором справки Объявления констант для ресурсов Резервная копия VPR-файла проекта Временный файл, используемый VDE при открытии проекта Резервные копии других файлов Созданный целевой модуль (исполняемый или DLL) Файл интерактивной справки Содержит короткие сообщения об ошибках Информация, генерируемая компилятором для браузера кода Файл, генерируемый компоновщиком Двоичный файл описания ресурсов, генерируемый VDE Сгенерированные объектные файлы Отладочная информация, генерируемая компилятором для каж- дого модуля Таблица символов — специальный объектный файл, содержащий таблицу символов (и строки, если такая опция определена ком- пилятором) Файлы ресурсов, содержащие описания окон Файлы ресурсов, содержащие описания диалоговых окон Файлы ресурсов, содержащие описания панелей инструментов Файлы ресурсов, содержащие описания меню Файлы, содержащие группы строк Файлы, содержащие растровые изображения, зарегистрирован- ные в проекте Файлы, содержащие пиктограммы, зарегистрированные в проекте Файлы, содержащие курсоры, зарегистрированные в проекте
842 Часть VI. Возможности визуальной среды разработки Новые модули исходного кода При разработке файлов исходных модулей необходимо придерживаться правил имено- вания и включения. Все модули будут иметь инструкцию include ”<MyProject>.INC”. Это гарантирует включение во все модули проекта идентичных наборов глобальных доменов и глобальных предикатов. Каждый исходный модуль может генерировать глобальные домены, которые должны быть расположены в отдельном файле <ModuleName>.DOM, а инструкцию включе- ния include ”<ModuleName>.DOM" следует поместить в файле <ProjectName>.lNC. Каждый исходный модуль может генерировать глобальные предикаты. Заголовки предикатов нужно располагать в отдельном файле <ModuleName>.PRE, а ин- струкцию включения include "<ModuleName>. PRE” необходимо поместить в файл <ProjectName>.lNC. Это гарантирует, что каждый модуль будет включать объявления всех глобальных доменов и предикатов проекта. При добавлении новых или удалении старых модулей, а также глобальных доменов и предикатов, файлы <ProjectName>.INC, <ModuleName>.DOM и <ModuleName>.PRE всегда будут корректны; проект будет перекомпилирован в зависимости от значений меток времени файлов. Если требуется выборочное включение объявлений глобальных предикатов и гло- бальных доменов, это должно быть сделано программистом. Такая стратегия под- держивается синтаксисом Visual Prolog, но не этой поддерживаемой VDE философи- ей автоматического включения. Для больших проектов такая стратегия может быть полезной (а иногда единственно возможной), потому что она дает меньше заголо- вочных файлов для компиляции, а также из-за ограничений на максимальное число доменов и предикатов, используемых в модуле. Если проект называется MyProject и вы создали новый модуль Test.pro, то вы увиди- те диалоговое окно, показанное на. рис. 27.33. File Inclusions for Module p; Create H eader Fifes : * j P Create Test.pre'-. r I nclude H eader files in ‘МуРЬкДinc' l-iF: jnclude ‘T est. dom’ L P f ijiclcjcte 'T est. РЙ*:! I • I ncludeH eader Files in'T est. pro* iPlndude 'MyPr eject, con’ > У Г < !; | ndi^e *H Iptopicxon' ; l lncludgTesLpre’ •• . Cancel w Г К И ! Рис. 27.33. Создание нового исходного модуля
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 843 Опции диалогового окна File Inclusions for Module Диалоговое окно, показанное на рис. 27.33, помогает создавать и включать заголо- вочные файлы нового модуля Test.pro. Обратите внимание, что если модуль Test.pro будет определять новые глобальные домены, то, чтобы следовать правилам именова- ния и включения VDE, вам нужно отметить флажки Create "Test.dom" и Include "Test.dom" в диалоговом окне File Inclusions for Module. □ Create <ModuIeName>.dom. Файл < Module Name >. dom будет создан в том же ка- талоге, что и файл <ModuleName>.pro. Поместите сюда все глобальные домены, объявленные в модуле. Эту опцию необходимо включить, если вы будете экспор- тировать глобальные домены из модуля. □ Create <ModuIeName>.pre. Файл < Module Name >.pre будет создан в том же ката- логе, что и файл <ModuleName>.pro. Поместите здесь все глобальные предикаты, объявленные в модуле, а также объявления классов, представленных в модуле. □ Include <ModuIeName>.dom. В файле <ProjectName>.inc будет сгенерирована конструкция включения include ”<ModuleName>.dom". Если у вас есть файл <ModuleName>.dom для модуля, то вам следует отметить этот флажок. Обратите внимание, что вы можете включить глобальные домены только в те мо- дули, которые действительно используют эти домены, но объявления всех гло- бальных доменов должны быть включены в основной модуль (модуль, содержа- щий раздел проекта GOAL). □ Include <ModuIeName>.pre. В файле <ProjectName>.inc будет сгенерирована ин- струкция включения include "<ModuleName>.pre". По умолчанию этот флажок отмечен, но для больших проектов включение всех глобальных предикатов в каж- дый модуль проекта может привести к превышению максимально возможного числа предикатов, объявленных в модуле. Поэтому в больших проектах следует отключать эту опцию и включать файл < Module Name >.рге только в те модули (PRO-файлы), которые действительно используют глобальные предикаты, объяв- ленные в файле <ModuleName>.pre. □ Include <ProjectName>.con. В файле <ModuleName>.pro будет сгенерирована ин- струкция включения include "<ProjectName>.con". Эту опцию нужно включать, только если ваш модуль ссылается на идентификаторы ресурсов. □ Include Hiptopic.con. В файле <ModuleName>.pro будет сгенерирована инструкция включения include "Hiptopic.con". Эту опцию нужно включать, только если ваш модуль ссылается на идентификаторы интерактивной справки. □ Include <ModuIeName>.pre. В файле <ModuleName>.pro будет сгенерирована ин- струкция включения для файла <ModuleName>.pre. В больших проектах необхо- димо прямо включать PRE-файлы в модули (PRO-файлы), которые используют глобальные предикаты, объявленные в этих PRE-файлах.
844 Часть VI. Возможности визуальной среды разработки Поддержка работы нескольких программистов над проектом в VDE Когда несколько программистов работают над одним проектом, они обычно исполь- зуют системы управления исходным кодом (SCS, Source Code Control System), такие как Microsoft Visual SourceSafe, INTERSOLV PVCS Version Manager и т. д. Система управления исходным кодом — это программа для работы с версиями ис- ходных файлов разрабатываемого проекта. Она сохраняет только изменения, сде- ланные в файле вместо самого изменяемого файла, таким образом позволяя сущест- вовать в системе нескольким последовательным версиям одного и того же файла. Системы управления исходным кодом разрешают членам группы разработчиков по- лучать, изменять и возвращать любые версии файла в безопасной, систематической и совместимой форме. С использованием таких систем вы минимизируете риск пе- резаписи изменений, сделанных другим разработчиком, и вам не придется волно- ваться о потере данных, потому что все изменения легко доступны. Типичный алгоритм использования SCS группой разработчиков, разделяющих про- ект, следующий: □ программисты имеют главную копню (master сору) всех исходных файлов в SCS; □ каждый программист имеет локальную копню (local copies) файлов проекта; □ когда программист изменяет какие-либо файлы в своей локальной копии, то он копирует их в главную копию в SCS; □ при работе над разделяемым проектом всем программистам необходимо регуляр- ное обновление локальных копий, чтобы гарантировать объединение изменений, сделанных другими разработчиками. SCS имеет несколько механизмов для синхронизации изменений, сделанных раз- личными программистами в одном файле. Обычно используется один из двух основных методов: • блокировка — когда программист хочет изменить какие-либо исходные файлы, он блокирует файл в SCS для обновления. Другие программисты не могут де- лать изменения до тех пор, пока обновление не будет завершено или не отме- нено; • объединение — программисты могут обновлять исходные файлы одновременно при взаимном подтверждении изменений. PRJ-файл проекта Основная база данных проекта VDE — VPR-файл проекта — хранит полные описа- ния всех ресурсов проекта. Но программисты не могут одновременно изменять ре- сурсы, сохраненные в одном VPR-файле проекта, потому что нет простого и безо- пасного способа объединить их изменения в конечном VPR-файле проекта. В этом случае следует включить опцию Multiprogrammer Mode в эксперте приложе- ний, тогда VDE генерирует и использует PRJ-файл проекта. VDE разделяет содер-
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 845 жимое VPR-файла проекта на PRJ-файл проекта и набор маленьких текстовых фай- лов, описывающих ресурсы проекта (отдельные файлы описания ресурсов) — не- большой отдельный файл описания ресурса для каждого окна, диалогового окна, меню, панели инструментов и т. д. PRJ-файл проекта — это текстовый файл, он содержит следующие три типа инфор- мации: П полное описание настроек проекта из эксперта приложений (Target Platform, Ul strategy, Main Program, VPI & Make options, User Info, Help Maker settings и т. д.); П список модулей проекта — ссылки на файлы, зарегистрированные в списке Modules окна проекта; □ список ресурсов проекта — ссылки на сгенерированные файлы описаний ресур- сов, соответствующие ресурсам проекта, зарегистрированным в окне проекта. Такое разделение оптимально для поддержки режима работы нескольких програм- мистов над проектом. Когда программист добавляет (удаляет) новый ресурс (или модуль) в проект, то VDE просто вставляет строку текста с фактом сохранения соответствующего файла опи- сания ресурса в PRJ-файл. Это дает два преимущества: П программисту придется обновлять PRJ-файл, только если он регистрирует (уда- ляет) новый элемент в окне проекта или изменяет настройки проекта; П изменения, сделанные в PRJ-файле, легко могут быть отображены SCS, т. к. это текстовый файл. Если программист редактирует какой-либо ресурс (или исходный модуль), то обнов- ляться будет только соответствующий файл, описывающий этот ресурс. Поэтому программисту нужно будет только обновить этот файл в главной копии проекта. При объединении изменений различных ресурсов (и исходных модулей), параллель- но выполняемом несколькими программистами, им нужно только копировать новые и обновлять измененные файлы в SCS и затем синхронизировать все локальные ко- пии с главной копией. Чтобы сделать разработку проекта несколькими программистами безопаснее и удоб- нее, VDE предоставляет следующие возможности: П поддерживает специальную работу режимов "только для чтения" и редакторов ресурсов для PRJ-файла; П поддерживает автоматическую перезагрузку для всех типов файлов в проекте, если они были изменены в локальной копии. Такие изменения происходят при синхронизации с главной копией в SCS (т. е. при изменениях, импортируемых другими программистами). Работа с файлами "только для чтения" в режиме работы нескольких программистов Если система SCS использует блокировку как метод синхронизации, то она обычно контролирует это путем создания файлов "только для чтения" (read-only), которые заблокированы для изменений. VDE проверяет атрибут "только для чтения" загру-
846 Часть VI. Возможности визуальной среды разработки женных файлов (исходные файлы модулей, отдельные файлы ресурсов и PRJ-файл) и защищает файлы "только для чтения" от изменений. Происходит это следующим образом: □ Открытие PRJ-файла "только для чтения": • окно проекта не позволяет добавлять/удалять ресурсы или модули. Кнопки New и Delete окна проекта заблокированы; • большинство опций в эксперте приложений будут заблокированы. Для удоб- ства программиста эксперт приложений разрешает настройку некоторых ло- кальных опций, таких как Compiler Options и Code Generation в течение сес- сии, но эти изменения будут потеряны на выходе. □ Когда редактор открывает исходный код или файлы ресурсов с атрибутом "только для чтения", он отображает ’’Read-only" в строке состояния и не позволяет редак- тировать этот файл или ресурс. □ Эксперт кода блокирует кнопки редактирования (Default Code, Add Clause, Edit Clause, Delete Clause, Update Code и т. д.), если выбранный модуль имеет атрибут "только для чтения". Всякий раз, когда VDE получает фокус, он проверяет атрибут "только для чтения" открытых файлов и изменяет соответственно возможности редактирования. Автоматическая перезагрузка измененных файлов Работая с разделяемыми проектами, программист должен периодически синхрони- зировать свою локальную копию файлов проекта с главной копией, чтобы объеди- нить изменения, сделанные другими разработчиками. VDE "помнит" метки времени открытых файлов. При получении фокуса VDE срав- нивает метки всех открытых файлов с файлами в локальной копии. Если во время синхронизации любой из открытых файлов изменился и если метки времени неко- торых файлов более новые, то VDE делает следующее: □ если программист не изменил открытый файл, то VDE просто перезагружает из- мененный файл; □ если программист изменил открытый файл, то VDE отображает диалоговое окно, спрашивающее, загружать внешние изменения. файла или игнорировать изме- ненный файл, или загружать внешние изменения и сохранять результаты редак- тирования в другом файле. Если PRJ-файл был модифицирован, то VDE импортирует этот новый PRJ-файл. Сохранение ресурсов в отдельных файлах описаний ресурсов Если включен режим работы нескольких программистов (Multiprogrammer Mode), то, как было отмечено, VDE сохраняет все ресурсы в отдельных файлах описаний ресурсов. Создавая новый переносимый ресурс (окно, диалоговое окно, меню, па-
Гпава 27, Особенности визуальной среды разработки для опытного пользователя 847 нель инструментов или группу строк), VDE спрашивает программиста об имени файла для сохранения описания ресурса (в элементе группирования Storing in Separate File). Описание ресурса, сохраненное в отдельном файле, содержит всю информацию о ресурсе, включая атрибуты ресурса, размещение ресурса и параметры настройки для экспертов кода. По умолчанию имя файла ресурса — это имя ресурса с соответствующим расшире- нием (табл. 27.4). Таблица 27А. Имена ресурсов Ресурс Диалоговые окна Окна Панели инструментов Меню Строки Растровые изображения Пиктограммы Курсоры Расширение dig win tb mnu str (один файл для каждой группы строк) bmp ico cur Если ресурсы сохраняются в отдельных файлах описаний ресурсов, то они, по умол- чанию, помешаются в подкаталоги, которые могут быть определены в диалоговом окне Project Directories. Настройки по умолчанию представлены в табл. 27.5. Таблица 27.5. Каталоги ресурсов Каталог Описание \RES Переносимые ресурсы: файлы с расширениями dig, win, tb, mnu и str (могут быть изменены в элементе управления Portable Res диалогового окна Pro- ject Directories) \RES\WIN Специальные файлы Windows: файлы с расширениями ico, cur и bmp (мо- гут быть изменены в элементе управления For Windows диалогового окна Project Directories) Изменение сохраняемых ресурсов Когда программист регистрирует (или удаляет) новый ресурс, PRJ-файл изменяется. Если флажок Auto Save .PRJ-file immediately after Adding/Deleting a Resource — авто- сохранение PRJ-файла сразу после добавления/удаления ресурса в диалоговом окне Environment отмечен (по умолчанию), то PRJ-файл будет сохранен немедленно. Если
848 Часть VI. Возможности визуальной среды разработки файл, определенный для ресурса, уже существует, ресурс, описанный в этом файле, будет добавлен к проекту. Если файл не существует, то для этого ресурса будет сге- нерирован новый файл. После регистрации файла ресурса в проекте запускается соответствующий редактор. Когда редактор ресурса закрыт, будет сохранен только файл ресурса; изменять PRJ-файл нет необходимости. Файлы проекта, сохраняемые в системе контроля SCS При использовании SCS следующие файлы должны быть помещены в систему кон- троля исходного кода: □ PRJ-файл (<ProjectName>.PRJ); □ исходные файлы Пролога (с расширениями inc, con, dom, pre, pro, кроме файла <ProjectName>.CON); □ файлы ресурсов (с расширениями dig, mnu, win, cur, bmp, ico, tb, str); □ исходные библиотеки и исходные объектные файлы (т. е. библиотеки и объект- ные файлы, которые не генерируются). С другой стороны, следующие файлы не должны быть помещены в SCS-базу: □ <ProjectName>.CON — генерируется системой, на основании файлов ресурсов; □ <ProjectName>.ENV — сохраняет установки VDE (такие как позиции окна) между сессиями; □ <ProjectName>.VPR — содержит только информацию, извлеченную из других файлов, таких как PRJ-файл и файлы описаний ресурсов; □ <ProjectNamе>.ВRO — содержит сгенерированную информацию браузера; □ сгенерированные библиотеки и сгенерированные объектные файлы; □ резервные файлы (с расширениями bk, bak, bkl). Построение, компиляция и компоновка Команды Compile Module и Build вызываются из меню Project, но все настройки для каталогов, компилятора и построителя программ (make facility) выполняются через команду меню Options | Project. Рассмотрим подробнее опции проекта. Опции компилятора Опции компилятора используются для установки параметров компиляции исходного кода Пролога.
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 849 Опции генерации кода Окно выбора опций генерации кода показано на рис. 27.34. □ Alignment — опция выравнивания. Эта опция показывает выравнивание данных и кода, выполняемое компилято- ром. Ее не следует изменять без необходимости. □ Errorlevel — опция уровня ошибок. Контролирует уровень детальности (генерируемой в исполняемый файл) инфор- мации о позиции предикатов в исходных файлах (табл. 27.6). Compiler Options Code Generation | Output I Warnings | Miscellaneous ***** Errorlevel Medium Runtime Checking P; Place S (rings in. SYM File ♦ Г* Break Check. Г: Convert AN SI to 0 EM Cancel мм ЙИМ Рис. 27.34. Вкладка Code Generation диалогового окна Compiler Options □ Place Strings in .SYM File — опция размещения строки в SYM-файле. Компилятор обычно помещает все строки в таблицу символов в SYM-файле. Эта оптимизация означает, что одна и та же строка, используемая во многих местах, будет сохранена только в одном месте. Однако для 16-разрядных целевых плат- форм размер SYM-файла имеет границу в 64 Кбайт. Поэтому в больших 16-разрядных проектах, возможно, придется отключить это свойство. □ Print Menu in DOS .EXE — опция меню печати в DOS. Для программ DOS в текстовом режиме, которые используют PDC-систему управления окнами в текстовом режиме, эта опция может использоваться для определения поддержки комбинации клавиш <Alt>+<P>, которая включает и выключает печать. □ Convert ANSI to OEM — опция преобразования ANSI в OEM. Предписывает компилятору преобразовывать символы между набором символов OEM (Original Equipment Manufacturer, изготовитель оборудования) и набором символов ANSI. Эта опция может быть необходима в случае, если Windows VDE формирует программу для целевой платформы DOS. То есть это преобразование должно быть сделано, когда вы используете Windows VDE для генерации DOS- приложения. С другой стороны, если вы загружаете файл с набором символов OEM, не должно выполняться никакое преобразование.
850 Часть VI. Возможности визуальной среды разработки О Integer Overflow Check — опция проверки переполнения целых чисел. Эта опция определяет, должна ли программа генерировать ошибки числового пе- реполнения во время выполнения. О Break Check — опция проверки нажатия клавиши <Break>. Для программ DOS в текстовом режиме и приложений EasyWin эта опция может активизировать или блокировать проверку нажатия клавиши прерывания <Break> во время выполнения. Если эта опция включена, то нажатие клавиши <Break> прервет выполнение программы. □ Stack Overflow Check — опция проверки переполнения стека. Эта опция может использоваться для отключения проверки переполнения стека во время выполнения кода для хорошо протестированных приложений. Таблица 27.8. Уровни ошибок Уровень ошибки Описание Minimal Никакая информация не сохраняется. Генерируется самый маленький и самый быстрый исполняемый файл. Этот режим используется только для хорошо протестированных приложений Medium Информация о позиции в исходном коде генерируется для каждого вызо- ва любого стандартного предиката, который потенциально может вызвать ошибку исполнения. Этот режим устанавливается по умолчанию Maximal При таком значении параметра генерируется информация о позиции в исходном коде для каждого вызова любого предиката. В результате будет показана точная позиция ошибки, включая проблемы с памятью, напри- мер, переполнение стека Выходные данные компилятора Окно настройки выходных опций компилятора приведено на рис. 27.35. □ .SYM File Name. Эта опция позволяет определить имя специального объектного файла, который содержит таблицу символов проекта (содержит все элементы до- мена symbol). Например, при компоновке в среде Visual C++ все объектные файлы должны иметь расширение obj. □ Verbosity. Эта опция управляет количеством информации, отображаемом в окне Messages во время компиляции (табл. 27.7). Ошибки и предупреждения по- прежнему будут представлены в окне Errors (Warnings). О Diagnostics Output. Эта опция вызывает генерацию информации обо всех отком- пилированных предикатах для непосредственного просмотра после компиляции. О Generate Browser Information. Эта опция определяет, будет ли компилятор гене- рировать информацию о том, где объявлены домены, где расположены заголовки предикатов и предложения предикатов. Информация браузера генерируется
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 851 в файле <ProjectName>.BRO в каталоге OBJ. Эта информация необходима брау- зеру исходного кода. □ Generate Debug Information. Эта опция определяет, нужно ли генерировать и раз- мещать в целевых модулях отладочную информацию, которая необходима отлад- чику Visual Prolog. Она действительна только для 32-разрядной платформы Windows. Обратите внимание, что данная опция автоматически включается, когда VDE активизирует PDC-отладчик. Если соответствующий флажок отмечен, то всегда генерируется информация о числе строк (только для основного модуля). □ Disable Optimizations. Эта опция определяет, должен ли компилятор генерировать оптимизированный объектный код (OBJ). По умолчанию компилятор всегда генерирует оптимизированный объектный код и опция Disable Optimizations выключена. Если опция Disable Optimizations вклю- чена, то компилятор не оптимизирует код для хвостовой рекурсии. Для получе- ния более подробной информации см. гл. 13. Если возникают проблемы с использованием функции отладчика Visual Prolog Call stack, то необходимо включить опцию Disable Optimizations. Обратите вни- мание, что в этом случае довольно простые рекурсивные предикаты могут при- вести к переполнению стека. Опция Disable Optimizations может быть включена, только если включена опция Generate Debug Information. Line Number Information. Если эта опция включена, то компилятор будет распола- гать информацию о номерах строк в файлах с расширением ехе. Это может быть необходимо для того, чтобы выполнять программу в традиционных отладчиках С. Информация о номерах строк может быть сгенерирована только для главного модуля (содержащего секцию GOAL). О Max Allowed Errors. Эта опция определяет количество ошибок, при достижении которого компиляция автоматически останавливается. Рис. 27.35. Вкладка Output окна Compiler Options
852 Часть VI. Возможности визуальной среды разработки Таблица 27.7. Опции отображения информации окна Messages Опция Описание None Показывает сохранение базы данных (VPR или PRJ), информацию о про- цессе генерации ресурсов и заключительное состояние процесса компи- ляции Default В дополнение к предыдущему показывает начало компиляции каждого модуля Maximum В дополнение к предыдущему показывает включаемые файлы, количество доменов и предикатов в модуле и т. д. Предупреждения компилятора Окно настройки опций предупреждения компилятора показано на рис. 27.36. □ Suppress All Warnings. Эта опция определяет, что компилятор должен игнориро- вать предупреждения. □ Treat Warnings as Errors. Эта опция определяет, что компилятор должен интер- претировать все предупреждения как ошибки. □ Max Allowed Warnings. Эта опция используется для установки ограничения на число предупреждений. При превышении этого числа компиляция будет прекра- щена. □ Default Predicate Туре. Эта группа переключателей определяет, как компилятор будет трактовать предикаты, объявленные без ключевых слов procedure, determ, nondeterm, failure, erroneous или multi (табл. 27.8). Таблица 27.8. Типы предикатов по умолчанию Переключатель Описание Nondeterm Заставляет компилятор добавлять ключевое слово nondeterm Determ Заставляет компилятор добавлять ключевое слово determ Procedure Заставляет компилятор добавлять ключевое слово procedure Для проектов, которые содержат большие части процедурного кода, нужно устанав- ливать значение Procedure, тем самым компилятор будет информировать о возмож- ностях неуспеха в выполнении предикатов. □ Duplicated Includes. Если эта опция включена, то компилятор будет генерировать предупреждение везде, где программа включает уже включенные ранее файлы. □ Not Quoted Symbols. Эта опция проверяет не взятые в кавычки идентификаторы. Язык позволяет использовать идентификаторы (объявленные как домен symbol) в виде имен, начинающихся со строчной буквы, без предупреждений компилято-
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 853 ра. Если опция Not Quoted Symbols включена, то компилятор будет выводить предупреждения. □ Strong Type Conversion Check. Эта опция строгой проверки преобразования типов. Компилятор Пролога проверяет опасные преобразования типов и разрешает сво- бодные преобразования между числовыми типами, но включение этой опции вы- зовет вывод предупреждений в тех случаях, когда "большие” типы преобразуются к "меньшим", например, real к long. □ Check Type of Predicates. Эта опция проверяет тип предикатов. Компилятор проверяет предложения предикатов и генерирует предупреждения для каждого предиката, которому компилятор не может гарантировать, что он соответствует объявленному типу предиката. Например, что предикаты procedure не могут завершиться неуспешно или что предикаты determ будут давать только одно решение и т. д. □ Unused Variables. Эта опция проверяет неиспользуемые переменные. Когда эта опция включена, компилятор будет выводить предупреждение в каж- дом месте, где переменная используется только один раз в предложении преди- ката. Обычно это указывает на ошибку. Если это не ошибка, то для отключения вывода сообщения об ошибке, имя переменной можно начать с символа подчер- кивания. Самый общий пример — это использование в экспертах кода win в ка- честве дескриптора окна. □ Unused Predicates. Если эта опция включена, то компилятор будет выводить пре- дупреждения для каждого локального предиката, который объявлен, но не ис- пользуется в модуле. □ Unreachable Code. Если эта опция включена, то компилятор будет выводить пре- дупреждение при обнаружении каждого фрагмента кода, который никогда не будет выполнен. Compile! Option №».A.^(A**«4S****t^.**i Code: Generation | 0 utput | Warnings I Miscellaneous | L : $ uppress Alt Warnings; Г Jieat^arrungiA^ErtorX Max Allowed Warnings[25 r. Default PredicateType ~ BUondeterm u В Qeterm 17 Duplicated Includes J p : N, on Quoted Symbols j: P.... trong Type Conversion Check p Check Type of Predicates . T P U nused Variables W-Buri p Unused Predicates ! № P; Unreachable £ode u d? ' тй^гй******^ j ir । й W*» OK «ММВШМййй «ИММЙмШММШЙЙММЙ Рис. 27.36. Опции предупреждений компилятора (вкладка Warnings окна Compiler Options)
854 Часть VI. Возможности визуальной среды разработки Другие опции компилятора Остальные опции компилятора устанавливаются на вкладке Miscellaneous окна Compiler Options (рис. 27.37). О Predefined Constants. В этом поле вы можете определить некоторые идентифика- торы констант, которые будут проверяться в if def-директивах в проекте. Иден- тификаторы должны быть разделены пробелами. Здесь можно определить некоторые параметры, которые будут переданы компи- лятору. Пробел следует использовать как разделитель. Можно указывать следую- щие два типа параметров: • символьные константы, на которые существуют ссылки из директив ifdef/ifndef в проекте; • идентификаторы, начинающиеся со знака —, интерпретируются как опции компилятора командной строки. Эта возможность используется, если VDE не может установить некоторые опции компилятора из диалогового окна Compiler Options. Например, если вы опреде- лите -R+ то компилятор получит опцию -R+, которая позволяет стандартным доменам ста- новиться ссылочными доменами. В этом случае компилятор не генерирует ошиб- ку "Basic domain becomes reference domain" (Базовый домен стал ссылочным до- меном). □ GStack Size. Эта опция определяет размер глобального стека в килобайтах и работает только для 32-разрядной Windows. По умолчанию это значение равно 100 Мбайт. Обратите внимание, что директива компилятора gstacksize (если она определена в тексте основного модуля программы) переопределяет число, заданное этой опцией. Минимальное допустимое значение — 128 Кбайт. Рис. 27.37. Вкладка Miscellaneous в окне Compiler Options
Глава 27. Особенности визуальной среды разработки для опытного пользователя 855 □ Stack Size. Размер стека используется следующим образом: • для приложений DOS в реальном режиме эта опция определяет размер стека в Кбайт; • если Пролог — это Main Program под 16-разрядной Windows и DOS Extended, то размер стека всегда будет равен 64 Кбайт; • для 32-разрядных платформ размер стека по умолчанию равен 1024 Кбайт. Это значение определяется компоновщиком PDC и может быть изменено оп- цией —s командной строки или определением директивы stacksize файла описаний компоновщика. Начиная с Visual Prolog версии 5.2, VDE может передавать значение, определен- ное в Stack Size, компоновщику PDC. Эксперт приложений генерирует подстро- ку ”-s$ (stacksize) ” в символе сценария построения linkflags. VDE построи- тель программ (Make facility) преобразует строку "~s$ (stacksize) ” в опцию -s компоновщика PDC. В проектах, созданных в прежних версиях Visual Prolog, символ сценария по- строения linkflags не содержит ”-s$ (stacksize)". Чтобы установить параметр Stack Size из VDE в таких проектах, программист может вручную добавить ”-s$ (stacksize) ” к linkflags. Это делается в редакторе символов, который мож- но открыть при помощи команды меню Options | Project | Make Options | Symbols. Вы можете проверить действительное значение опции -s компоновщика PDC в сценарии построения (команда меню Options | Project | Make Options | Preview Script). Здесь значение stacksize должно быть определено в байтах. Директива stacksize файла определений, с расширением def (если она определе- на), переопределяет опцию -s компоновщика PDC. Здесь значение stacksize должно быть определено в байтах. □ Heap Size. Размер динамической памяти только для DOS. Эта опция может использоваться для резидентных программ реального режима DOS, чтобы определить размер динамической памяти. Во всех других случаях динамическая память расширяется до значения доступной памяти. Опции генератора кода Система Visual Prolog может генерировать несколько типов файлов ресурсов (табл. 27.9). Таблица 27.9. Типы файлов ресурсов, которые может генерировать Visual Prolog Расширение Описание гс Текстовое описание ресурсов, которые могут быть откомпилированы компилятором ресурсов res Двоичное описание ресурсов. Оно может быть дано непосредственно компоновщику PDC для связывания ресурсов в исполняемый файл
856 Часть VI. Возможности визуальной среды разработки Таблица 27.9 (окончание) Расширение Описание hip Файл интерактивной справки, производимой генератором справки Visual Prolog Помимо этих файлов генерируется файл определений констант. Он используется для объединения имен ресурсов в исходных модулях с именами идентификаторов, за- данными в различных редакторах ресурсов (табл. 27.10). Таблица 27.10. Различные типы файлов определений констант Имя айда Описание *.CON Файл определения констант ресурсов Visual Prolog * Н Файл определений констант С. Он генерируется, если выбрана опция генерации RC-файла Hiptopics.con Константы, определенные в этом файле, идентифицируют разделы справки в интерактивной справке Выбрать необходимый тип файла можно в диалоговом окне, которое вызывается командой меню Options | Code Generator (рис. 27.38). Рис. 27.38. Опции эксперте кода для генерации кода и файлов Опция Automatically update source files if resources have been modified может использо- ваться для отключения автоматического обновления исходного кода экспертами ко- да при изменении расположения или атрибутов окон, диалоговых окон или панелей инструментов. Построитель программ Построитель программ (Make facility) строит проект, генерируя ресурсы, файлы кон- стант, модифицируя секции исходного текста для экспертов кода, компилируя и связывая все модули, а также генерирует файл справки. Построитель программ про-
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 857 веряет временные метки файла, чтобы видеть, какие файлы должны быть откомпи- лированы или перекомпилированы. Файл должен быть перекомпилирован, если OBJ-файл старше, чем исходный файл или любой из его включаемых файлов, или вовсе не существует. Построитель программ получает требуемую информацию из файла проекта (с рас- ширением prj или vpr). Эта информация включает набор исходных файлов и сцена- рий построения (make scripts). Сценарии построения состоят из следующих частей: □ собственно сценарий построения (build script) — описывает, как строить целевой файл проекта (исполняемый модуль или DLL); □ правила (rules) — определяют, как компилировать файлы с заданным расширением; □ локальные правила (local rules) (привязанные к отдельным файлам в дереве) — описывают специальные команды, которые следует применять к данному файлу; □ символы (symbols) — символьные имена, которые могут быть использованы в сценариях построения и в правилах. Эксперт приложений генерирует сценарий построения по умолчанию, программист может его изменить и расширить. Настройки для построителя программ (кроме локальных правил) находятся в меню Options | Project | Make Options (рис. 27.39). Рис. 27.39. Меню Make Options Символы Сценарии построения могут обращаться к символам. Синтаксис, используемый в сценариях для обращения к определенному символу: $ (<symbol>). Эти символы могут быть такими: □ символы среды (окружения) определяются командой SET операционной систе- мы. Для Windows NT/2000 он может быть определен в системной утилите Computer Management Properties на вкладке Advanced диалогового окна Environ- ment Variables; □ специальные символы, предопределенные VDE (которые обрабатываются автома- тически и не могут быть изменены программистом); □ определенные пользователем символы (изначально генерируются экспертом при- ложений), которые могут быть установлены при помощи команды меню Options | Project) Make Options | Symbols. Поиск значения заданного символа будет производиться в обратном порядке таким образом, что, если пользователь определяет символ непосредственно (все символы, отображающиеся при помощи команды меню Make Options | Symbols), система будет использовать только этот символ. Иначе построитель программ проверяет, имеет ли
858 Часть VI. Возможности визуальной среды разработки VDE такой предопределенный символ. Если нет, то VDE будет пытаться найти, оп- ределен ли такой символ как переменная окружения в операционной системе. Если и этот поиск закончится неудачно, то будет выдано предупреждение. ( Замечание j Символ определяет макрокоманду и — что касается синтаксиса и семантики — при- мерно эквивалентен символам, используемым в стандартных построителях про- грамм (make utility), таких как Microsoft NMake и Borland Make. Далее приведен при- мер определений символов: vpilib="$(PRODIR)Vpi\Lib\Win32\MSC\Vpi.lib" "$(PRODIR)Vpi\Lib\Win32\MSC\EDIT.lib" prolib="$(PRODIR)Lib\Win32\Prolog.lib" "$(PRODIR)Lib\Win32\MSC\Win32.lib" libs=$(vpilib) $(prolib) init="$(PRODIR)Vpi\Lib\Win32\MSC\Init32.obj" "$(PRODIR)Vpi\Lib\Win32\MSC\CMain.obj" link=pdclinkDLL linkflags=-TPE -SGUI -E_PDCPrologStart -s$(stacksize) Символы сценариев построения (make script), предопределенные VDE, перечислены в табл. 27.11. Таблица 27.11. Список предопределенных символов VDE Имя символа Объяснение PROJECT_OBJ Список объектных файлов (с расширением obj), сгенерированных из исходных файлов модулей проекта и явно зарегистрированных как модули проекта PROJECTJJB PROJECT_RES PROJECT_DEF OBJDIR EXEDIR PRODIR SYSDIR CDIR Список LIB-файлов, явно зарегистрированных как модули проекта RES-файлы, которые явно зарегистрированы в окне проекта. Можно вручную добавить этот символ в сценарий построения DEF-файлы, которые явно зарегистрированы в окне проекта. Можно вручную добавить этот символ в сценарий построения Путь к промежуточному каталогу (т. к. он установлен в диалоговом окне Project Directories) Путь к заключительному каталогу (т. к. он установлен в диалоговом окне Project Directories) Путь к корневому каталогу Visual Prolog Каталог, из которого был запущен VDE (т. к. он отображается в сис- темном каталоге в диалоговом окне Project Directories) Корневой каталог дополнительного компилятора С (т. к. он установ- лен в диалоговом окне External Tool Directories). Этот символ пре- допределяется VDE, если С выбрана как основная программа в экс- перте приложений
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 859 Имя символа Объяснение Таблица 27.11 (окончание) SYMFILE Имя специального объектного файла, который содержит таблицу сим- волов проекта. Оно может быть определено в поле .SYM File Name в диалоговом окне Compiler Options pdclink.dll Специальный символ, используемый для сообщения VDE, что он должен вызвать редактор связей PDC не как выполняемую програм- му (pdclink.exe), а как функцию из pdclink.dll Имя текущего целевого файла (расширение удалено) Вместо каждого знака ' + ’ (плюс, окруженный одиночными кавычка- ми) VDE использует комбинацию +<cr><LF> (плюс, сопровождаемый символами возврата каретки и перевода строки) в конечном сцена- рии построения (командная строка) Если ' + ’ используется вместе со ссылкой на символ $' + ' (Symbol), то VDE добавит комбинацию +<CR><LF> после каждого имени, под- ставленного из Symbol IMG DIR Определяет выбранное местоположение для зависимых от платфор- мы изображений, т. к. этот каталог устанавливается для подкаталогов изображений (непереносимые ресурсы) в диалоговом окне Project Directories. IMGDIR главным образом используется, чтобы опре- делить независимое от платформы местоположение файлов, содер- жащих пиктограммы / курсоры / изображения (icons/cursors/bitmaps) в диалоговом окне <Resource Туре> Attributes Правила построения сценариев Правила построения сценариев включают некоторые правила по умолчанию, на- пример, о том, как компилировать файлы. Каждая строка — это команда, необхо- димая для преобразования файла с одним расширением к файлу с другим расшире- нием. c->obj: $(сс) $(cflags) -с cpp->obj:$(сс) $(cflags) -с ”$*.срр" rc->res:”$(RCDIR)rc" -г -fo ”$(OBJDIR)$*.res” "$(OBJDIR)$*.rc" pro->obj:#$*.pro Очевидно, для компиляции файла ресурсов с расширением гс в файл с расширением res необходимо выполнить следующую команду "$(RCDIR)rc" -г -fo "$(OBJDIR)$*.res" ’’$ (OBJDIR) $*. rc" где $* будет замещен именем реального файла. Обращение к # означает вызов компилятора Пролога с выбранными опциями. Синтаксис правила сценариев построения такой: inputExtension -> OutputExtension : CommandLine
860 Часть VI. Возможности визуальной среды разработки Специальное замечание для тех, кто знаком с другими аналогичными программами (make utilities): перечисленные нами правила примерно эквивалентны тем, которые известны как неявные правила. Здесь определить явные правила (локальные прави- ла) невозможно. Используйте всплывающее меню в браузере дерева проекта, чтобы определить зависимости и соответствующие действия (локальные правила). Явные правила, указанные для файла с использованием окна дерева проекта, будут приме- няться к файлу раньше, чем определенные здесь неявные правила (т. е. прежде, чем правила сценариев построения по умолчанию). Невозможно определить правила, которые имеют размер больше одной строки. В таких случаях нужно создавать подходящие командные файлы. Локальные сценарии (правила) В окне дерева проекта правая кнопка мыши активизирует всплывающее меню, при помощи которого можно добавить некоторые зависимости к отдельным файлам (рис. 27.40). Например, RES-файл может быть сгенерирован из RC-файла. Также возможно явно зарегистрировать некоторые включаемые файлы для данного узла. Это необходимо для типов файлов, которые система Пролога не знает, например файлы С. Можно добавлять специальные локальные сценарии, определяющие спо- соб построения некоторых узлов. Эти локальные правила (действия) будут приме- няться к файлу (соответствующему узлу) перед заданным по умолчанию правилом сценария построения (если такое правило определено для этого типа файлов) (см. разд. "Дерево модулей проекта" данной главы). ’ Add Including. Д Add Dependence... Show Selected Shoat Parent : ShgwZH tde S ubtree •; f Рис. 27.40. Всплывающее меню для дерева проекта Сценарий построения Сценарий построения — это основная команда, описывающая способ построения проекта. $(link) -F« $(linkflags) -о"$(EXEDIR)$*.exe" -M"$(OBJDIR)$*.map" $(init) $ (PROJECT_OBJ) "$(OBJDIR)$(SYMFILE)" "$ (OBJDIR) $*.res" (PROJECT LIB) $(libs)«
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 861 Синтаксис "«text" означает: положить TEXT во временный файл и заменить TEXT именем этого файла. Команды построения Команда Project / Compile Module Эта команда (ей соответствует комбинация клавиш <Ctrl>+<F9>) делает попытку компилировать модуль, содержащий редактируемый в данный момент файл. Выпол- нение команды зависит от следующих свойств файла: □ если файл имеет расширение pro и является модулем текущего проекта, то VDE пытается компилировать этот файл; □ если файл не является модулем текущего проекта и его расширение — pro, pre, inc, con или dom, то VDE пытается найти модуль проекта, который включает этот файл, и откомпилировать первый найденный модуль; □ во всех остальных случаях VDE пытается компилировать модуль, выбранный в окне проекта. VDE не может компилировать файл, который не является частью открытого проек- та. Вместо этого файла VDE будет компилировать модуль, выбранный в окне проекта. Если в VDE не открыт ни один проект, то никакие файлы компилироваться не бу- дут. Команда меню Project | Compile Module заблокирована; комбинация клавиш <Ctrl>+<F9> не работает. Единственно возможное действие — это запустить утилиту Test Goal. Команда Project I Build Если со времени последнего построения проекта были изменены какие-либо ресур- сы, то эксперты кода могут обновить некоторые секции в исходных файлах перед построением. Эта команда (ей соответствует комбинация клавиш <Alt>+<F9>) строит проект, проверяя метки времени всех исходных файлов в проекте, поэтому если исходные файлы (или файлы, которые в них включены) являются более новыми, чем зависи- мые OBJ-файлы, то соответствующие модули проекта будут перекомпилированы. Команда Build также строит файлы ресурсов и файл интерактивной справки (если необходимо). Затем проект компонуется для генерации целевого модуля (исполняемая программа или DLL). Команда Project / Rebuild All Эта команда (ей соответствует комбинация клавиш <Ctrl>+<Alt>+<F9>) выполняет то же действие, что и Project | Build, причем все файлы будут повторно сгенерирова- ны или откомпилированы и скомпонованы независимо от их меток времени.
862 Часть VI. Возможности визуальной среды разработки Команда Project / Stop Building Эта команда (ей соответствует комбинация клавиш <Alt>+<F10>) используется для остановки компиляции/компоновки. Команда Project / Run Если необходимо, то эта команда (ей соответствует клавиша <F9>) выполнит дейст- вие Project | Build и затем запустит сгенерированный исполняемый файл. Команда Project / Link Only Эта команда (ей соответствует комбинация клавиш <Shift>+<F9>) используется для выполнения компоновки. В этом случае построитель программ вызывает компонов- щика и не проверяет, нужно ли повторно компилировать какие-либо модули проек- та (или даже впервые компилировать). Команда Project / Test Goal Эта команда (ей соответствует комбинация клавиш <Ctrl>+<G>) используется для тестирования простых целей (Goals). Программа компилируется и компонуется в специальном режиме, и затем запускается соответствующий исполняемый файл. Утилита Test Goal ищет все решения для определенной в программе цели. Для каж- дого решения Test Goal отображает значения всех переменных из секции GOAL и число решений. Эта особенность — удобный способ проверить локальные предика- ты в модуле. Например, следующая цель: GOAL X = 2; X = 1, Y - X + 1 приведет к такому результату (рис. 27.41): (Inactive D:\TEMP\DEMO\OBJV.. ИЕЗ£3 Х=2 Х=1, У=2 2 Solutions Рис. 27.41. Вывод режима тестирования цели Команда Resource / Build Resource Only Когда окно проекта активизировано, в меню Project появляется команда Resource. При выборе этого пункта (или нажатии комбинации клавиш <Alt>+<F8>) генери- руются выбранные файлы с расширениями гс и res и необходимые файлы констант.
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 863 Редактирование ресурсов Как уже отмечалось (см. гл. 26), в GUI-системах термин ’’ресурсы*’ относится к ок- нам, диалоговым окнам, изображениям, строковым таблицам, пиктограммам, курсо- рам, контекстам устройства, перьям, кистям, распределениям памяти и т. д. для всех активных приложений. Когда мы обращаемся к ресурсам в Visual Prolog, то обычно имеем в виду описания диалоговых окон, меню, панелей инструментов и справки, курсоров, растровых изо- бражений, пиктограмм и таблиц строк, которые создаются различными редакторами ресурсов в VDE. Размещение и атрибуты этих ресурсов сохраняются в базе данных проекта <ProjectName>.VPR (в режиме совместной работы нескольких программи- стов VDE использует текстовый формат файла проекта <ProjectName>.PRJ, который ссылается на текстовое описание ресурсов, размещенных в файлах описания ресур- сов), и, когда вы запускаете процесс компоновки, VDE генерирует стандартный двоичный файл ресурсов (<ProjectName>.RES) и файл с определениями констант (<ProjectName>.CON) для включения в исходные модули. Вы можете указать необходимость генерации файлов <ProjectName>.RC и <ProjectName>.H, вызвав меню Options | Project | Code Generator. Эти файлы обес- печивают стандартное текстовое описание ресурсов и могут быть преобразованы в RES-файл любым из доступных инструментов (компилятор RC или любой из инст- рументов для подготовки ресурсов). В файл ресурсов входят следующие элементы: диалоговые окна, меню, курсоры, рас- тровые изображения, пиктограммы и строки. Для окон, панелей инструментов и строк подсказки код Пролога генерируется непо- средственно в исходных модулях приложения, т. к. эти элементы не предусматрива- ются стандартным форматом файлов с расширениями гс или res. Название ресурсов После того как ресурс создан одним из редакторов VDE, ему необходимо присвоить имя. VDE предоставляет стандартное имя, основанное на ряде правил, и пользова- тель может принять, изменить или полностью заменить его. Эти символы, которые позже станут константами Пролога (идентификаторами ресурсов), будут использо- ваться приложением для обращения к ресурсам, а во время компиляции превратятся в уникальные целочисленные значения. Во время построения проекта генерируется файл с расширением res, предназначен- ный для компоновщика. Также VDE генерирует файл с расширением con, который содержит константы всех сгенерированных ресурсов и должен быть включен в каж- дый модуль проекта, работающий с ресурсами. Эти числовые значения, а не сим- вольные имена, заданные в приложении, будут использоваться в VPI-предикатах и GUI. Поддерживаются и два других способа названия ресурсов: непосредственное указа- ние целочисленных констант и именованные ресурсы. Эти способы поддерживаются для совместимости со старым кодом, внешними специальными элементами управ- ления, связывания с другими языками (С, VB, Delphi) и для специальных целей. Постоянно использовать их не следует.
864 Часть VI. Возможности визуальной среды разработки Целые числа для ресурсов будут, например, использоваться при импортировании ресурсов из файлов с расширениями dll, res или ехе. После импортирования ресурсы будут иметь номера, которые у них были в "родном'* ресурсе. Тогда необходимо вручную задать символьные имена различных элементов управления и пунктов ме- ню. Для любого ресурса, элемента управления или пункта меню вы можете просто ввести целое число, если этот ресурс должен иметь определенный номер. Также можно включить имя константы для идентификатора ресурса в кавычках. В этом случае идентификатор будет вставлен в файл ресурсов как есть (без замены на целочисленное значение). Но это представление идентификатора не поддержива- ется VPI. Поэтому вы не можете обращаться к такому ресурсу в программах VPI, но можно получить символьный идентификатор в кавычках при импортировании ре- сурсов из приложения, которое использовало это свойство. Например: □ обычное имя идентификатора ресурса: id About; □ целочисленная константа: 102; □ символьное имя ресурса: "ABOUTDIALOG". Связывание ресурсов Напомним, что RES-файл включается в исполняемый файл, в котором он формиру- ет сегмент(ы) ресурсов (см. гл. 26). При использовании компоновщика PDC или Microsoft эта операция может быть выполнена компоновщиком, в противном слу- чае — компилятором ресурсов для связывания сегмента ресурсов в исполняемый файл после обычной компоновки. Во время выполнения приложения предикаты VPI получают доступ к диалоговым окнам, меню, пиктограммам, растровым изображе- ниям и строкам из сегмента ресурсов ЕХЕ-файла. Растровые изображения, пиктограммы и курсоры Эти элементы могут быть созданы как в редакторе изображений VDE, так и при помощи других инструментов, предназначенных для создания, сохранения или из- менения графических изображений. В отличие от диалоговых окон, меню и строк, определения пиктограмм, растровых изображений и курсоров будут находиться в отдельных файлах перед тем, как они будут переданы в RES-файл. Диалоговые окна Диалоговые окна обычно создаются в редакторе окон и диалоговых окон в VDE. Он выполняет работу по размещению и установке размеров элементов управления в диалоговом окне. Во время выполнения, если в сегменте ресурсов есть определе- ние диалогового окна, то это диалоговое окно может быть создано вызовом преди- ката win_CreateResDialog ИЛИ dialog_Create. Меню Меню обычно проектируются в редакторе меню VDE. Если окно создано вызовом предиката win Create, то меню для окна передается как параметр. Если вызвать
Гпава 27, Особенности визуальной среды разработки для опытного пользователя 865 предикат resjnenu(Resid), то окно автоматически получит меню из ресурса, ассо- циированного с идентификатором Resld. Строки Строки помещаются в строковые таблицы по нескольким причинам. Наличие их в ресурсах позволит упростить выполнение изменений, таких как перевод на другой язык, без необходимости изменять исходный код. Приложение может получить строку вызовом предиката vpi_GetResStr. Импортирование ресурсов Прежде чем создавать определение нового ресурса, напомним, что можно импорти- ровать ресурсы из существующих файлов с расширениями vpr, res, ехе или dll. Чтобы импортировать ресурсы, вы должны сначала выполнить команду меню Resource | Import, тем самым открыв обычное окно открытия файлов, где нужно вы- брать файл с расширением vpr, ехе, dll или res. Затем появится диалоговое окно со списком ресурсов в этом файле (рис. 27.42). Рис. 27.42. Импортирование ресурсов Здесь можно прокручивать список ресурсов вверх и вниз. Когда меню выбрано, оно будет отображено как главное меню в Visual Prolog и может быть проверено до на- жатия кнопки мыши в любом другом окне. Чтобы импортировать определение ресурса в ваш проект, нажмите кнопку Load (произойдет фактический импорт ресурса). Эксперты кода В Visual Prolog существует несколько экспертов кода. Мы уже ознакомились с экс- пертом приложений, который генерирует по умолчанию весь код, ресурсы и сцена- рии построения для приложения.
866 Часть VI. Возможности визуальной среды разработки Другими экспертами кода являются: □ эксперт окон и диалоговых окон; □ эксперт пакета диалоговых окон; □ эксперт панелей инструментов. Эти три эксперта кода используются для генерации исходного кода после размеще- ния компонентов ресурсов. Преимущества экспертов кода: □ они выполняют большой объем работы (предохраняя вас от необходимости на- бирать большие объемы стандартного кода); □ предоставляют стандартизированный способ обработки ресурсов; □ вы получаете готовый к работе пользовательский интерфейс приложения. Используя эксперты кода, вы можете легко ориентироваться в коде, выбирая ком- поненты пользовательского интерфейса. Небольшой недостаток состоит в том, что эксперты кода добавляют дополнительные комментарии, которые используются для нахождения сгенерированного кода на более поздних стадиях. Эксперты кода могут автоматически изменять генерируемый ими исходный код при изменении положения или атрибутов компонента пользовательского интерфейса. Такое автоматическое изменение может быть включено или выключено для каждого компонента, причем автоматическое изменение всегда выполняется только в той области, которая ограничена закомментированными строками "Code automatically updated" (Автоматически изменяемый код), как показано ниже: %BEGIN MyWindow, CreateParms, 13.11.2000, Code automatically updated! win_mywindow_WinType = w_TopLevel win_mywindow_Flags = [wsf_SizeBorder,wsf_TitleBar,wsf_Maximize, wsf_Minimize,wsf_Close] win_mywindow_RCT = ret(100,80,440,240) win_mywindow_Menu = no_menu win_mywindow_Title = "MyWindow" win_mywindow__Help = idh_contents %END MyWindow, CreateParms Код для обработки ресурсов вставляется в исходные файлы, так что можно открыть текстовый редактор и изменить код. Однако вы должны следить за тем, чтобы не изменить комментарии %begin - %end. Также необходимо помнить, что если вы по- местите код в секции, код которой "Автоматически изменяется", то потеряете его без какого-либо предупреждения при следующем автоматическом изменении. Существует такое ограничение: код, сгенерированный и управляемый экспертами кода, должен находиться в "корневых" файлах модулей проекта (<ModuleName>.PRO) и не может быть во включаемых файлах. Главное преимущество экспертов кода проявляется тогда, когда кто-то еще исполь- зует ваш код или если вы вернулись к работе с ним после перерыва. Код, генери- руемый экспертами кода, можно посмотреть при помощи команды меню Resource | Preview PRO Text.
Глава 27. Особенности визуальной среды разработки для опытного пользователя 867 Эксперт окон и диалоговых окон Эксперт окон и диалоговых окон — это инструмент, который объединяет код Пролога с размещением окон и диалоговых окон. После того как окно или диалоговое окно спроектировано, эксперт окон и диалоговых окон используется для вставки необхо- димого кода, чтобы управлять созданием окна, диалогового окна и обработкой со- бытий окна (рис. 27.43). Dialog and Window Expert ' ' Dialog or Window Selection . .j - DieJOg- •:::•>?- i.r^nn, • Window I M-,’’Wlndow , i f- Place Source Code in... ~e i Module (^Project pro \ OK J: Cancel Help ; Cocle Style j Vsijn_cfeate^ j i P Automa tic S puree Update г Event Handling ' " - - j Event Type • ' . D elete Code; Window Menu S crollbar Control Key Mouse OwnDraw Misc Event or Item e CloseR equest Declaration Layout M|—1T--~- .. г L- H e_Create e_ Des troy e_EndApplication e_EraseBackGround e_GetFocus e_LoseFocus e_Move e_Native Toolbars.., ? ji •rnniytnitiiiii JiijpttH iii ij|n iiiijli fiijt Г । ,11,. ! Event Help Add Clause Ujg^iieJCpde |*шМммМ1ая1Мй1ммммма1кааа : I 1 J Рис. 27.43. Эксперт окон и диалоговых окон Генерация кода по умолчанию для окна Если окно или диалоговое окно выбраны в окне проекта и нажата кнопка Code Expert, то появляется эксперт окон и диалоговых окон с выбранным окном или диа- логовым окном. Этот предварительный выбор может быть изменен. Если эксперт инициализирован вне контекста окна или диалогового окна, то сначала необходимо указать, является ли оно окном или диалоговым окном, и потом выбирать фактиче- ское окно или диалоговое окно. Затем выберем модуль, который содержит или должен содержать сгенерированный исходный код, используя раскрывающийся список Module из списка зарегистриро- ванных модулей. Далее нужно выбрать Code Style, определяющий способ создания окна или диалого- вого окна. Выберите пока только windereate, другие возможности будут объяснены позже. Флажок Automatic Source Update определяет, должны ли эксперты кода автоматиче- ски обновлять исходный код всякий раз при изменении расположения.
868 Часть VL Возможности визуальной среды разработки Когда эти основные поля должным образом установлены, нажмите кнопку Default Code. Если эта кнопка недоступна, то, возможно, выбран неподходящий стиль кода. Нажатие кнопки Default Code вызывает объявление глобального предиката, создаю- щего окно, которое будет добавлено к файлу заголовка (<ModuleName>.PRE) для модуля, а предложения предиката создания окна и код обработчика событий будут добавлены в конец выбранного модуля. Заголовок кнопки при этом изменится на Update Code. Эксперт кода изменяет исходный код при нажатии кнопки Update Code или, если в эксперте кода определено автоматическое изменение кода, перед компиляцией мо- дуля. Просмотр кода, сгенерированного по умолчанию для окна Нажмите кнопку Edit Code в эксперте окон и диалоговых окон, чтобы открыть мо- дуль в редакторе с курсором, установленным на сгенерированном по умолчанию коде для создания окна и обработки событий: %BEGIN_WIN MyWindow Создание и управление событиями для окна: MyWindow constants %BEGIN MyWindow, CreateParms, 7.11.1999, Code automatically updated’ win_mywindow_WinType = w_TopLevel win_mywindoW—Flags = wsf_SizeBorder,wsf_TitleBar,wsf_Close, wsf_Maximize,wsf_Minimize,wsf_ClipSidlings,wsf_ClipChildren] win_mywindow__RCT = ret (100, 80, 440, 240) win_mywindow_Menu = no__menu win_mywindow_Title = "ww" win_mywindow_Help = idh_contents %END MyWindow, CreateParms predicates win_mywindow_eh : EHANDLER clauses win_mywindow_Create (_Parent) w i n_C r e a t e(win_mywi ndоw_Wi nT yp e, w in_mywind оw_RCT, win^mywindow__Title, win_mywindow__Menu,_Parent, win_mywindow_Flags,win_mywindow_eh,0). %BEGIN MyWindow, e_Create win_MyWindow_eh(_Win,e_Create(_) ,0) : -!, %BEGIN MyWindow, InitControls, 7.11.1999, Code automatically updated1. %END MyWindow, InitControls %BEGIN MyWindow, ToolbarCreate, 7.11.1999, Code automatically updated! %END MyWindow, ToolbarCreate i %END MyWindow, e_Create
лава 27. Особенности визуальной среды разработки для опытного пользователя 869 %MARK MyWindow, new events <------------extra events are added here -END-WIN MyWindow Рассматривая этот код, обратите внимание на следующее: □ маркеры %BEGIN "Code automatically updatedl" И %END отмечают секцию исход- ного кода, в которой эксперты кода будут — если установлен флажок Automatic Source Update — автоматически изменять исходный код при изменении любых параметров окна в редакторе окон или в диалоговом окне Window Attributes; □ атрибуты и параметры расположения для окна (область, меню, заголовок и т. д.) определены как константы в секции constants CreateParms. Эти константы могут быть использованы или проигнорированы; □ в заголовочном файле модуля (<ModuleName>.PRE) вставляется объявление гло- бального предиката для создания окна: %BEGIN_DECL, System generated global predicates global predicates... % <- Other predicates win__MyWindow_Create (WINDOW Parent) — (i) □ в большинстве приложений возвращаемый дескриптор нового созданного окна должен быть сохранен. При необходимости в предикат win_MyWindow_Create можно передать несколько параметров. Эти изменения могут быть сделаны, потому что сгенерированный код не будет изменен экспертами кода; □ если есть некоторый код в блоке "Code automatically updated", который уже не нужен, или вы хотели бы его изменить, то можно закомментировать /* */ мар- керы блока %; □ BEGIN - end win xxx, e Create отмечает место предложений для обработки со- бытия создания. Эти маркеры используются для размещения кода, и пользова- тель может делать любые изменения внутри области, ограниченной маркерами; □ в предложении для события e_Create имеется блок, отмеченный "%begin ww, initControls". Этот блок будет использоваться, если в окне существуют какие- либо динамически создаваемые элементы управления. Также предложение собы- тия e_Create содержит блок %BEGIN ww, ToolbarCreate, куда будет вставлен код создания любых панелей инструментов. Обработка событий В эксперте окон и диалоговых окон имеется элемент группирования Event Handling. В этой группе можно добавить (или удалить) предложения для предиката обработчи- ка событий окна. События сгруппированы по различным типам, поэтому прежде чем выбрать собы- тие, вам нужно выбрать подходящий тип события в списке Event Туре. Для того чтобы прочитать описание события в интерактивной справке, необходимо выбрать это событие и нажать кнопку Help.
870 Часть VI. Возможности визуальной среды разработки Если еще не существует предложения для события, нажмите кнопку Add Clause, что- бы добавить новое предложение для обработки события. Например, будет вставлен следующий фрагмент кода, если вы нажмете кнопку Add Clause для события e_Update: %BEGIN MyWindow, e_Update win_mywindow_eh(_Win,ejjpdate(JJpdateRct) , 0):-!, i « %END MyWindow, ejjpdate Комментарии ”%BEGIN MyWindow, ejjpdate" И ”%END MyWindow, ejjpdate" ИСПОЛЬЗу- ются для последующего нахождения предложения. Если предложение для события уже существует, то название кнопки изменится на Edit Clause. Таким способом можно легко найти код, обрабатывающий какое-либо событие. Если код для события больше не нужен, можно нажать кнопку Delete Clause, тем самым удалив код между комментариями %begin - %end, включая эти комментарии. Также можно удалить код в редакторе вручную. Стили кода В эксперте окон и диалоговых окон существует опция Code Style. При помощи нее можно определить: □ что окно должно быть создано предикатом win_Create; □ что окно должно быть создано предикатом win_CreateDyn с использованием структуры WINDEF; □ что окно должно быть окном редактора, создаваемым предикатом edit_create; □ что окно должно быть окном дерева, создаваемым предикатом tree_Create; □ что окно должно управляться предикатом пакета диалоговых окон dialog_Create (в основном для диалоговых окон). Варианты выбора ссылаются на предикаты, используемые для создания окон. Чтобы понять стили кода, необходимо внимательно изучить каждый из этих предикатов. Код создания окна отличается для каждого из стилей кода, но предикаты обработ- чика событий всегда те же самые. Стиль кода можно изменить на более поздней стадии. Предложения обработчика событий будут сохранены, но любой код, измененный в предикате создания окна, будет потерян. Стили кода называются по имени используемых предикатов VPI. Эти предикаты описаны в гл. 26, Они формируют три группы: создание окна, создание диалогового окна и создание специального элемента управления. Стили кода для создания окна □ win_Create Этот стиль кода наиболее часто используется для окон. Сгенерированный код вызовет предикат win_Create, и необходимые элементы управления окна будут созданы в предикате обработчика события e Create.
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 871 1 win_CreateDyn В этом случае окно должно быть создано динамически, как определено в струк- туре windef. Эта структура будет сгенерирована в исходном модуле на основе информации в базе данных проекта для данного окна. .•dit_Create Создает окно, которое будет использовать редактор VPI во время выполнения. 3 предикате обработчика событий можно изменить обработку события, заданную о умолчанию. .•di t_Cr eat eWrap {елает вызов редактора в режиме переноса строк во время выполнения. dit_CreateHyper Создает окно с гипертекстовым редактором. .гее Create Создает окно с отображением дерева. или кода для создания диалогового окна dialog_Create В основном используется для диалоговых окон, но также может применяться и для окон. Создает диалоговое окно или окно посредством вызова пакета диало- говых окон, который имеет множество возможностей проверки правильности и инициализации. 1 win_CreateResDialog Это наиболее общий стиль кода диалоговых окон, он применяется для создания диалоговых окон из определения ресурсов. Пакет диалоговых окон не использу- ется, поэтому всеми элементами управления приходится управлять индивидуаль- но в предложениях обработчика событий. □ win_CreateDynDialog Создает диалоговое окно из структуры windef. Пакет диалоговых окон не исполь- зуется. Структура windef будет сгенерирована в исходном модуле на основе ин- формации в базе данных проекта для этого диалогового окна. □ dialog_Create(DynDialog) Создает диалоговое окно из структуры windef с использованием пакета диалого- вых окон. Структура windef будет сгенерирована в исходном модуле на основе информации в базе данных проекта для этого диалогового окна. Стили кода для создания специальных элементов управления □ class_Create Применяется для создания класса специального элемента управления. Создает тип нового элемента управления, который может быть использован в диалоговых окнах.
872 Часть VI. Возможности визуальной среды разработки Активизация Эксперт окон и диалоговых окон может быть активизирован или повторно вызван при помощи комбинации клавиш <Ctrl>+<W>. Если он вызван повторно, то будут отображены настройки, заданные при последнем вызове эксперта. Изменение исходного модуля Можно изменить исходный модуль для окна или диалогового окна. Эксперт кода запросит подтверждение перемещения. Если оно будет получено, то переместит код в выбранный модуль. Также можно перемещать объявление глобальных предикатов создания окон и диалоговых окон в PRE-файл для выбранного модуля. Другие кнопки в эксперте окон и диалоговых окон □ Edit Code (Редактировать код) Эта кнопка вызывает отображение предложения предиката, который создает окно. □ Delete Code (Удалить код) Эта кнопка вызывает удаление всего кода для окна, включая объявление гло- бального предиката создания окна в PRE-файле. □ Declaration (Объявление) Эта кнопка вызывает редактор PRE-файл а с курсором, расположенны м на объ- явлении глобального предиката для создания окна или диалогового окна. □ Layout (Размещение) Эта кнопка открывает редактор окон и диалоговых окон для данного окна. □ Toolbars (Панели инструментов) Эта кнопка вызывает эксперта панелей инструментов. □ Dialog Pack (Пакет диалоговых окон) Эта кнопка вызывает эксперт пакета диалоговых окон для окна или диалогового окна со стилем кода dialog_Create, Эксперт пакета диалоговых окон Если стилем кода для окна или диалогового окна является dialog_Create, то в экс- перте приложений должен быть включен флажок Dialog Package. Пакет диалоговых окон — это инструмент, находящийся в файлах DIALOG.PRO, DIALOG.PRE и DIALOG.DOM на прилагаемом CD-диске в каталоге <CDROM>:\RUN\VPI \INCLUDE\DIALOG. Пакет диалоговых окон упрощает инициализацию и получе- ние значений для диалоговых окон и имеет множество вариантов обработки и про- верки достоверности значений элементов управления. Эксперт пакета диалоговых окон делает возможным определение опций для пакета диалоговых окон в некото- рых диалоговых окнах. Пакет диалоговых окон описан в гл, 25 и приложении 2, Там можно найти и описа- ние кода, генерируемого экспертом пакета диалоговых окон.
Гпава 27. Особенности визуальной среды разработки дня опытного пользователя 873 I" I I I — 11II ИМ I I I — ! piimh4w—im Эксперт пакета диалоговых окон (рис. 27.44) вызывается из эксперта окон и диало- говых окон при помощи кнопки IQjjliiH. А ♦ ide cancel Dialog Package Expert: <mvdlg> Ki variable Рис. 27.44. Эксперт пакета диалоговых окон J Й: ▼ - I * ♦ idc_push_button 4 idc_edit 4* idc_hscroll 4 ide vscroll «“ idejistbox ! ^Alternatives 4 Init value ♦ idc_check_box ♦ idct_static_text 4 idejistedit 4 ide list button *4/1— -ж: —T^>- LISTvALS В левой стороне окна находится список элементов управления. Некоторые из типов элементов управления имеют несколько опций, которые можно отобразить, раскрыв список двойным щелчком на элементе. Свернутое и развернутое состояние опреде- ляется знаком плюс или минус перед идентификатором элемента управления. Справа можно выбрать различные настройки для выбранного элемента. Для некоторых элементов управления, возможно, понадобится инициализация. На- пример, для списка может быть необходим набор строк и целых чисел, чтобы вы- брать элемент для выделения. Рассмотрим подробнее опции элементов управления. Метод установки Эта опция (рис. 27.45) определяет для заданного элемента способ его инициали- зации. Рис. 27.45. Методы установки переменных диалогового окна В табл. 27.12 приведено действие всех трех вариантов для командной кнопки (кноп- ка может иметь заголовок во время инициализации).
874 Часть VI. Возможности визуальной среды разработки Таблица 27.12. Код, генерируемый для различных методов установки Метод установки Сгенерированный код аrt«*манашевн||вьамм Use resource value Ничего не генерируется (использовать значение ресурса) User's initial value (Начальное значение, df (idc_xx, pushbutton ("XX") ,nopr) заданное пользователем) Variable (Переменная) df (idc_yy, pushbutton (YY) ,nopr) Определять как Введите начальное значение для заданного элемента в поле Initiate as. Опция пока- зана на рис. 27.46. Рис. 27.46. Установка начальных значений для элементов управления Строки нужно заключать в кавычки, что позволяет определять и переменную, и константу. Для многоязыко вых приложений строки должны выбираться из групп строк в сегменте ресурсов и не играть роль литералов в программе. Связывание переменных В поле Variable (рис. 27.47) можно определить имя переменной, которую нужно ис- пользовать вместо данного элемента. Рис. 27.47. Определение имени переменной для элемента управления По умолчанию генерируется имя, которое является именем элемента управления в верхнем регистре. Имена переменных будут использоваться и для инициализации значения, и для получения нового значения после закрытия диалогового окна. Когда имя переменной определяется первый раз, оно будет инициализировано зна- чением по умолчанию. Код инициализации будет вставлен прямо перед маркиров- кой "%MARK MyDialog, new variables". Эта строка может быть изменена програм- мистом для того, чтобы связать переменную с реальным значением параметра или факта: MEMMODEL = idc_huge, %MARK MyDialog, new variables Обратите внимание, что изменение типа переменной или удаление элемента управле- ния и т. д. приведут к ошибкам компиляции, если эту строку не изменить вручную.
Глава 27. Особенности визуальной среды разработки для опытного пользователя 375 Тип переменной 1я полей редактирования можно определить тип данных, отображаемых элементом равления (рис. 27.48). Рис. 27.48. Выбор типа переменной, связанной с полем редактирования данный момент пакет диалоговых окон умеет работать с целыми, длинными 1ыми и вещественными числами. войства полей редактирования, строковые значения нсет диалоговых окон имеет несколько возможностей проверки правильности no- li редактирования. Установки зависят от типа поля редактирования (рис. 27.49). Рис. 27,49. Свойства поля редактирования, которое содержит строковую переменную Можно выбрать следующие установки: □ mandatory. Если этот флажок отмечен, то пакет диалоговых окон будет следить за тем, введено ли значение в это поле. Кнопка ОК будет блокирована до тех пор, пока не будут заполнены все обязательные поля; □ upper. Означает, что строка будет преобразована к верхнему регистру перед воз- вращением значения из пакета диалоговых окон; □ lower. Означает, что строка будет преобразована к нижнему регистру перед воз- вращением значения из пакета диалоговых окон; □ default. В этом поле можно задать значение по умолчанию, которое будет воз- вращено пакетом диалоговых окон, если пользователь не определит другого зна- чения для этого поля; □ length. Здесь устанавливается максимальная длина для входной строки. Свойства полей редактирования, числовые значения Опция показана на рис. 27.50. Параметры: □ min — здесь определяется минимальное значение для целых, вещественных и длинных целых чисел;
876 Часть VI. Возможности визуальной среды разработки □ max — здесь определяется максимальное значение для целых, вещественных и длинных целых чисел. Рис. 27.50. Свойства полей редактирования, которые содержат числовые значения Элементы списков, редактируемых списков и раскрывающихся списков Для определения элементов списка можно задать список значений, разделенных запятыми (рис. 27.51). item list: a,b,c,d,e Рис. 27.51. Определение элементов списка Начальные значения для списков, редактируемых списков и раскрывающихся списков Для списков можно определить начальное значение. Для списков с множественным выбором можно задать список значений, как показано на рис. 27.52. Рис. 27.52. Определение начальных значений для списков с множественным выбором Свойства полос прокрутки Для полос прокрутки можно установить значения Minimum и Maximum, начальную позицию (Position) и шаги, которые должны выполняться при нажатии клавиш управления курсором <Т> или <Ф> (параметр Lineinc) или клавиш <PgUp> или <PgDn> (параметр Pagelnc) (рис. 27.53).
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 877 Control type: wc_HScroll Lineinc: 1 Pagelnc: 10 Minimum: ° Maximum: 100 Position: J 20 Рис. 27.53. Определение свойств полосы прокрутки Группы переключателей В группе может быть отмечен только один переключатель. Выбор любого другого переключателя в группе выключает все остальные. Группировка переключателей может быть определена в эксперте окон и диалоговых окон в пункте RB groups, ко- торый всегда находится в нижней части списка. Этот пункт открывает диалоговое окно, где можно добавить новые группы и перемещать переключатели между груп- пами. Чтобы инициализировать группу переключателей, нужно определить пере- менную, которая должна содержать константу элемента управления начальной уста- новки. Под Windows для группы переключателей также может быть установлен атрибут AutoState, но обработка в эксперте пакета диалоговых окон переносима на все плат- формы (рис. 27,54). Dialog Package Expert: <mydlg> 4 Minimum ♦ Position 4 idc_vscroll ““ idejistbox ♦ Alternatives RB groups: Available RB ide far idc huge ADD > NEW RB in group: ide huqe idccompact ide near < DEL 4 idc_check_box ♦ idct_static_text 4* idejistedit 4 idejistbutton ♦ idc_groupbox 4 RB groups Checkedradiobutton: idc_huge; P Variable OK У? MEMMODEL Cancel Help / rt ' Рис. 27.54. Обработка переключателей экспертом пакета диалоговых окон
878 Часть VI. Возможности визуальной среды разработки Эксперт панелей инструментов Для каждой панели инструментов, созданной из окна проекта, требуется предикат для реального создания панели инструментов. Исходный код для этого предиката может быть сгенерирован экспертом панелей инструментов (рис. 27.55). Toolbar Expert •r: Assign T oolbars to Window I Window. | MyWindow * A Toolbar Selection" £ 1 Controls Г Controls i ____ ___ j: Moveable 5" = 'Delete Layout i III/ill'll w, V,. f r. Place Source Code in.. - J Module: :. I MyProjec t pro l P Automatic Source Update r.' ? t । .•.;.»... . ' ...; • v • <’ • В emarnrng T odiban : i I Help line . ..:•••• v./. Declaration ? Delete Code .1. л-Edit Code J^; »i »1Ш1Г|1|ЪЯИММ^............ А Г £ £ Й А > QK. | Canqgl -дар Рис. 27.55. Эксперт панелей инструментов Эксперт панелей инструментов выполняет две различные операции. В левой части диалогового окна он назначает панель инструментов выбранному окну; в правой — добавляет исходный код для панели инструментов. Так же, как и при работе с окнами, вы должны определить модуль, содержащий код панели инструментов. Затем эксперт панели инструментов вставит предложение для генерации в этом модуле и объявление предиката в соответствующем PRE-файле. Если отмечен флажок Automatic Source Update, то исходный код будет автоматиче- ски изменяться всякий раз при повторном построении проекта. %BEGIN__TLB Controls, 17 ; 13 : 20-17.11.1999, Code automatically updated! ^****^4г********9г********9г^9г***9г9г9г9г9гЧ-**********9г*******9г* + 'А- + ЧгЧг + 'А--^'А-Чг*ЧгЧг Создание панели инструментов: элементы управления ** + *t***Jr*ii + ++*****r**Jr**Jr*****+**Jr*T + ** + ****+**Jr*****Jr**+**** + Jr + *+ y clauses tb_controls_Create(_Parent):- ifdef use_tbar toolbar_create(tb_right,0x808080,^Parent, [tb_ctrl(id_clear,pushb,idb_mybmp_norm,idb_mybmp_down, idb_mybmp_down,"", 1,1)]) , enddef true. %END TLB ControlsCHAPTER
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 879 Отладчик Visual Prolog Отладка — это процесс поиска ошибок в программе. По некоторым оценкам ис- правление ошибок в программе занимает около 90% от общего времени разработки. Это доказывает необходимость хорошего инструмента для отладки. Отладчик Visual Prolog может отлаживать приложения 32-разрядного Windows (Win32) в зависимости от установок Target: VPI, EasyWin и Textmode. Отладчик Visual Prolog позволяет проследить выполнение программы, установить точки останова и выполнять программу по шагам (отлаживаемый код можно смот- реть как на уровне Пролога, так и на уровне ассемблера). Отладчик предоставляет возможности для просмотра переменных, используемых в отлаживаемом предложе- нии, просмотра и отказа от фактов, просмотра событий VPI, просмотра использова- ния памяти (динамическая память и GStack) и дампов памяти. Отладчик Visual Prolog — это отдельное приложение (рис. 27.56), которое может быть запущено как из операционной системы, так и из VDE (в отличие от старого отладчика PDC Prolog, являвшегося частью среды разработки). Ffe £cft View Дип &tndpw ДеЬ я rnyprojectexe - Visual Prolog Debugger for Windows 32 О Call Stack PROLOG GoalQ WiWiйймЙЙЙйЙй t ж Ж module module module module: In In In Рис. 27.56. Отладчик Visual Prolog MyProiect.exe Й : MyPfpiectpro ? dlg_about_dialog_create{ a j-i dfgbjnydtalogLcreate( windc ; д proiect_showhelpcontext( jr tb_controls„create( window : tb_help_line_create( window ; tb_moveable_create( winds (b_proiect_tooltiaf_create( \ - wtn_mywindow_create( win ; _PR0L0S_Goal L long dlg_about_dialog_eh(1 long dlgjY)ydialogL_eh( wind /V dg_mydialogLhandle_answ c*ig_myclialog_i4>date( dialo< long task_win_eh( window, vpi^SetAttrVal (attr_win_Bidi,b_true), enddef ifdef ws_win ifdef use_3dctrl vpi__SetAttrVal (attr__win_3dcontrols,b_true), enddef enddef vpi_Init( task_win_Flags, task_win_eh, task_win_l!enu/'Myprogec^ myproject. exe myproject. exe myproject.exe myproject. exe breakpoint breakpoint breakpoint breakpoint set at RunOSError (address = 49419B). Goal (address » 449CF0). vpi_CallEH (address = 45B693). _vpi_BeforeEvent (address - 456FC4). Entry point of thread (Id: B5) is: 77F762E6 Генерация отладочной информации Первое, что нужно сделать, — это включить генерацию отладочной информации в диалоговом окне Compiler Options, которое показано на рис. 27.57.
880 Часть VI. Возможности визуальной среды разработки Рис. 27.57. Генерация отладочной информации Включение флажка Generate Debug Information заставит компилятор генерировать файлы с расширением DEB в каталоге OBJ. Эти файлы используются отладчиком для позиционирования исходного кода, переменных, фактов и т. д. во время выпол- нения программы. Включение флажка Disable Optimizations блокирует оптимизацию хвостовой рекур- сии. Программа, которая откомпилирована с оптимизацией хвостовой рекурсии, не будет показывать все пункты в окне Call Stack (потому что они оптимизированы). Однако при включении флажка Disable Optimizations программа будет отличаться от реальной, и значительно увеличится риск переполнения стека исполняемой про- граммы. Если вы используете командную строку для компиляции и компоновки, то необхо- димо учитывать следующие аспекты: □ файлы с расширениями sym, map/obj и deb должны быть расположены в одном каталоге перед компоновкой. Обратите внимание, что изначально эти файлы мо- гут компилироваться в разных каталогах; □ могут использоваться компоновщики Microsoft и Borland, но в этом случае мы не гарантируем, что все имена будут видны из отладчика Visual Prolog. Запуск отладчика Повторим, отладчик Visual Prolog может быть запущен из VDE или из операционной системы. Для активизации процесса отладки из VDE просто выберите команду Project | Debug (также можно использовать комбинацию клавиш <Ctrl>+<Shift>+<F9> или кнопку Debug панели инструментов). Сначала будет построен (если необходимо) проект, затем будет запущено целевое приложение и активизирован отладчик Visual Prolog на это приложение. Также можно запустить отладчик из операционной системы. Если Visual Prolog уста- новлен программой инсталляции, то эта программа создаст пиктограмму для от- ладчика. Отладчик — это исполняемый файл, путь к которому — BIN\WIN\32
Гпава 27, Особенности визуальной среды разработки для опытного пользователя 881 \V1PDEBUG.EXE. Отладчик Visual Prolog может запускаться под 32-разрядной Windows (Windows 95/98/MЕ и NT/2000) для отладки программ пользовательского интерфейса 32-разрядной Windows и консольных приложений. Загрузка программы Выберите команду Load в меню Files для загрузки исполняемой программы в отлад- чик. В появившемся диалоговом окне (рис. 27.58) определите ЕХЕ-файл для загруз- ки. Если вы переместили файлы с расширением deb, то необходимо определить их новое местоположение. Рис. 27.58. Загрузка программы в отладчик mypfojectexe - Visual Piolog Debugger for Windows 32 file £сй View Bun WindowHelp я Л £ 'nUim.iuUW.HU'W it;- VPITools.pro module: module: module: myproject.exe myproject.exe myproject. exe ££ MyPioiect.exe MyPioiect.pfo ’ [1MYPROJECT.PRO HQE3 • 97:3. W Insert vpi_SetkttxVal (attr_win enddef ifdef ws_win if def use_3dctrl vpi_SetAttrVal(attr_uin_3dcontrols,b_true), enddef enddef vpi_Init(task_uin_Flags,task_win_eh,task_win_Menu,"myprojec : 7'"r»-.i..t.-j., mdi,b_true) 4BEGIN_TLB Project toolbar, 15:17:15-9.11.2000, Code automati Creation of toolbar: Project toolbar In ; In In Entry point of thread (Id: C5) is: 77F762E8. breakpoint breakpoint breakpoint set set set at Goal (address = 449CF0). vpi_CallEH (address = 45B693). _vpi BeforeEvent (address * 458FC4). Mt at MM Рис. 27.59. Окно сообщений Messages
882 Часть VL Возможности визуальной среды разработки В поле Command Line нужно задать строку с аргументами командной строки для от- ладки программы. Список Run позволяет выбрать режим запуска программы: в пол- ном, минимизированном или максимизированном окне. Когда программа загружена в отладчик, то стартовая точка и другая информация (какие модули загружены, какие из них имеют отладочную информацию и т. д.) мо- гут быть просмотрены в окне Messages в нижней части экрана (рис. 27.59). Программу можно запустить при помощи меню Run или кнопок на панели инстру- ментов. Окна просмотра В отладчике можно открыть несколько окон. Они открываются из меню Views (рис. 27.60). Рис. 27.60. Меню Views Окно модулей Если отладчик запущен и программа загружена, то она отображается в окне моду- лей, а структура файлов может быть просмотрена при помощи дерева (рис. 27.61). В этом окне можно дважды щелкнуть на файлах, из которых состоит проект, и два- жды щелкнуть на отдельных предикатах. В результате откроется окно исходного ко- да с курсором, установленным в выбранную позицию. Чтобы увидеть исходный код, программа должна быть написана на Visual Prolog. Все остальные программы можно просматривать в ассемблерном виде. Нельзя редактировать код в отладчике PDC, потому что он автономный. Наиболее удобный способ исправлять ошибки — это найти соответствующие предикаты в от- ладчике и в исходном коде с использованием VDE.
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 883 Щ Modules MyProject MyProject.exe ф-Rl MyProject. pro Hi=l Test.pro ЁНй| VPITools.pro dialog, pro (EAVPRO\vpi\in । dialog_checkeditpropert j HeS dialog_checkeditpropert : A 'i H«S window dialog_create( y- | И?*' window dialog_createmc i His window dialog_createmc / I integer dialog_getcheck I HS3 dialog_getcustom(windCyj <: • • > Рис. 27.61. Окно модулей Окна исходного кода Команда меню View | Go to Executing Predicate Source (или комбинация клавиш <Ctrl>+<E>) устанавливает курсор на строку, содержащую выполняющийся преди- кат. Окно исходного кода (рис. 27.62) может быть открыто почти из любого пункта меню в отладчике. Q MYPROJECT.PRO > И ; 97:3 ;• А'; a Insert vpi_SetAttrVal (attr__uin_mdi ,b_true) enddef ifdef w3_win • ; ifdef use_3dctrl ЙО vpi_SetAttrVal(attr_win_3dcontrols,b_true), v ; enddef enddef - vpi_Init(task_uin_Flag3, task_win_eh, task_win_Menu, "myproj ec %BEGIN_TLB Project toolbar, IS:17:1S-9.11.2000, Code automatic. Creation of toolbar: Project toolbar Jffil Рис. 27.62. Окно исходного кода Код будет выделяться белым цветом в тех местах, где возможно установить точки останова. В диалоговом окне Debugger Configuration можно выключить опцию "отте- нить исходный код" (флажок Gray Source Lines not Containing Executable Code). В окнах исходного кода вы можете использовать несколько команд, которые активи- зируются из меню Edit или при нажатии правой кнопки мыши. Существуют воз- можности поиска текста (команда Search), перемещения на определенную позицию или строку (команды Go to Line Number и Go to Position соответственно), перехода
884 Часть VI. Возможности визуальной среды разработки к объявлению или к предложению указанного предиката (команды Go to Declaration и Go to Clause соответственно). Можно копировать выделенный исходный код в буфер обмена: выбрать требуемый фрагмент, перемещая мышь с нажатой левой клавишей, и использовать команду Сору. Команда Show Position отображает абсолютную позицию, указанную мышью в коде. Из любой позиции вы можете перейти к выполняющемуся предикату (команда Go to Executing Predicate). При помощи команды Put Position on Clipboard for in VDE вы можете записать позицию, указанную мышью, в буфер обмена. В дальнейшем в VDE вы сможете использовать комбинацию клавиш <Alt>+<F2>, чтобы перейти к этой позиции. Меню Run Меню Run используется для управления выполнением программы в отладчике (рис. 27.63). □ Run. Эта команда (или клавиша <F9>) запускает приложение. Она обычно ис- пользуется в сочетании с установкой точек останова. □ Run Until Fail. Эта команда выполняет код от текущего предиката до первого от- каза. Курсор будет размещен на предикате, который вызывается после этого от- каза. □ Break Program. Если программа запущена в отладчике, то можно ее прервать, чтобы войти в отладчик. Затем можно проверить стек вызовов для просмотра то- го, что программа делает; или установить точку останова в том месте, где нужно остановить процесс выполнения. □ Wait for VPI Events. Отладчик ждет первое событие VPI и затем отображает (и помещает курсор на) первое предложение предиката обработчика событий, ко- торый получил событие VPI. □ Trace Into. Эта команда (или клавиша <F7>) используется для пошагового пере- мещения по исходному коду с заходом во все вызываемые предикаты. □ Step Over. Проход по шагам через исходный код без захода в вызываемые преди- каты. Если эта команда (или клавиша <F8>) используется, когда вы достигаете вызова предиката, то предикат выполняется как простая команда. ..fiun F3 • ' Run until Fail ... v .. ..... . .' : PsC'C'S"!: Cxri Д' ллйк ...bSsdJ .... /J:’:1:’;..’ • •>*’ ’• Jjace Into F7 g Sill: . ч чч лн. ч -. чч ччч t • j, м. > t чин *ччм- ym-ч чw-“- * . . . . . •• . .......................... Рис. 27.63. Меню Run
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 885 П Run to Cursor. Эта команда (или клавиша <F4>) работает как при установке точ- ки останова в текущей позиции исходного кода, выполняя команду Run. Поместите курсор в нужное место в исходном коде и выполните эту команду. П Invoke Fail. Эта команда немедленно вызывает отказ (подобно вызову предиката fail). П Invoke Exit. Эта команда имитирует вызов предиката exit/0. П Restart. Эта команда перезапускает приложение. Установка точек останова Установка точек останова (Break Points) очень важна для изоляции ошибок или про- верки корректности выполнения фрагмента программы. Когда открыто окно исход- ного кода, на строке можно щелкнуть правой кнопкой мыши, после чего откроется меню, откуда может быть установлена точка останова. Если выбрана команда Run (горячая клавиша <F9>), то программа выполнится только до этой точки. При помощи окна Break Points можно гораздо удобнее и эффективнее управлять точками останова (рис. 27.64). Это окно вызывается командой Break Points из меню View. СЗ Break Points Enable my_test Enable get values ( integer,, string, integer ) - (0,0,0) Рис, 27.64. Окно точек останова В списке будут перечислены все точки останова и их состояние (включена или вы- ключена). Точка останова может быть удалена при помощи команд Remove и Remove All. Состояние точки останова переключается командой Toggle. Все команды могут быть выполнены из всплывающего меню или из меню Edit. При помощи команды Go to Code можно перейти к файлу исходного кода с установленной точкой остано- ва. Выбрав команду Properties, вы активизируете диалоговое окно Breakpoint State, которое показано на рис. 27.65. П Счетчик периода (Counter Period). Если точка останова установлена в цикле (на- пример, в рекурсивном предикате), то приложение будет возвращаться к этой точке тысячи раз. Если вы не хотите останавливаться на каждом цикле, то вы можете определить периодичность (интервал), с которой отладчик будет останав- ливаться в этой точке. Для этого определите необходимое значение p_value периода для этой точки в ноле Counter Period. Отладчик связывает внутренний счетчик с каждой заданной точкой останова. При каждом проходе через эту точку отладчик добавляет 1 к соответствующему счетчику. Текущее значение счетчика отображено в поле Current Count.
886 Часть VI. Возможности визуальной среды разработки По умолчанию значение Counter Period — 1; это означает, что отладчик должен останавливаться при каждом попадании на эту точку. Если для счетчика периода установлено различное значение p_value, то отладчик остановится только на значении счетчика, кратном указанному P_Value. Breakpoint State S our c e: ТЕ ST. PRO Li n e: 2 8 Counter Period Current Count [o P Enable Breakpoint Г Auto Delete OK ИМЙММШММШЙИШММ Cancel Help Рис. 27,65. Свойства точки останова П Текущее значение счетчика (Current Count). Отображает текущее значение счет- чика. Это значение равно числу повторяющихся попаданий на точку останова во время выполнения приложения. П Разрешить точку останова (Enable Breakpoint). Сбросив этот флажок, вы можете временно отключить точку останова без ее удаления. Позже точку останова опять можно включить. П Автоудаление (Auto Delete). Если этот флажок отмечен, то точка останова авто- матически будет удалена после первого попадания в нее. Окно переменных Во время выполнения можно просматривать значения переменных (рис. 27.66). Для этого нужно выполнить команду View | Local Variables. Когда курсор мыши указывает на некоторую переменную в исходном коде, то через короткую паузу можно увидеть значение этой переменной. Это очень удобный спо- соб для проверки значения переменной без открывания окна переменных. Окно фактов При исправлении ошибок программ очень важно гарантировать, что были добавле- ны корректные факты. Факты программы могут быть просмотрены в окнах фактов (рис. 27.67). Это окно выбирается при помощи команды Facts в меню View. Стек вызовов Окно стека вызовов показывает список предикатов, которые уже были вызваны в отлаживаемой программе (рис. 27.68). Двойной щелчок на предикате показывает код, где произошел вызов выбранного предиката. Если были сделаны изменения, окна можно обновить командой Refresh из всплы- вающего меню. Как описано выше в разд. "Генерация отладочной информации"
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 887 Рис. 27.66. Окно переменных g MyProject i-:-SS MyPrciject.exe - Test i - ОЙ myfacts D deterrn_fact_db{ string) n S single Jact_db( integer) VPITools + ondet fact db(1) I non.det_f act_db (2) nondet^fact_db(3) nondet fact dbf integer ] Рис. 27.67. Окно фактов task_win_eh( ~0xB20408, е_депи(10016,0), _ ) Рис. 27.68. Представление стека вызовов
888 Часть VI. Возможности визуальной среды разработки данной главы, возможно, понадобится включить опцию Disable Optimizations в диалоговом окне Compiler Options. Остальные опции для окна стека вызовов описаны в разд. "Опции настройки отлад- чика " данной главы. Окно вызовов предиката trap Окно вызовов предиката trap (Trap Points) показывает список точек вызова преди- ката trap отлаживаемой программы (рис. 27.69). Двойной щелчок на точке trap вы- зывает код, где находится эта точка. Т rap Pointe 1 test._t.rap Рис. 27.69. Окно Trap Points Окно точек отката Окно точек отката показывает список точек отката отлаживаемой программы (рис. 27.70). Двойной щелчок на точке отката вызывает строку кода, где находится эта точка. Рис. 27.70. Окно Backtrack Points Представления машинного уровня Можно просматривать коды ассемблера, регистры, дампы памяти и потоки прямо в отладчике PDC. Основной целью является проверка кода, написанного не на язы- ке Пролог. Дизассемблирование Окно просмотра кодов ассемблера показано на рис. 27.71.
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 889 IQ Disassembly - (myprojectexe) : koO43O3e6 push 004303f4 004303eb push ebp . . ' f. . 004303ec push [ebp+08] 004303ef jmp Te st$ non.de t_fac db$468 (oc;d 004303Г4 push 00430404 004303Г9 push ebp 004303fa push [ebp+04] . . • •• • 004303fd >P Теsdeteac _db$469 (og*j: Рис. 27.71. Окно ассемблера Память Окно просмотра памяти показано на рис. 27.72. HJMemory Dump (Unknown Module) Г0095004С I0095005B 0095006A ;00950079 L00950088 100950097 Г009500А6 |009500B5 г 00950004 :009500D3 |:009500E2 ;009500F1 ? •• ЕайШййййЬйЫйаййймШй 00 00 12 90 5С 23 сс 02 00 ОЕ 98 00 00 Е0 00 FA 79 00 01 00 01 00 00 00 00 00 00 00 04 00 00 02 00 00 00 00 64 00 95 00 01 78 41 00 70 00 84 BF F6 ВО 00 78 ВС СО 00 00 01 03 0Е 00 ВС 67 ОС 47 0В 68 00 F7 00 FA 00 41 DA А4 3F F6 03 00 67 0C 01 DC СЗ 00 00 F7 00 Е0 47 68 9F FF 01 СО 01 DC 00 ....d. ..xA.p •. .....4g. G ,a^hUAYy".A.Uh N 95 00 02 00 21 DA С4 3F 00 00 ОБ 00 СЗ 00 34 00 95 00 9F FF 00 01 01 СО 01 02 95 00 ED CF 10 СО 01 00 ЕО 01 00 F4 BF 95 95 00 D4 00 \ub-?hUAYy~. A. fl #9".'.A...! Iy\ud-i-?4iaiDi. A ...........•.6. • 00 00 00 01 03 DA 14 СЗ 01 9F FF 95 00 xA.a..o.... 4g.G..aihUAYy A.Uh.. й Рис. 27.72. Окно памяти Наиболее удобный способ перемещаться в окне памяти — явное определение адре- сов в функциях Goto всплывающего меню (или подменю Edit). Команда Goto Address принимает адреса трех форматов: □ прямое задание шестнадцатеричного адреса (например, 012FEF4); □ имена регистров. Например, esp. Если имя регистра определено, то область про- смотра прокручивается на адрес, соответствующий значению, сохраненному в указанном регистре; П глобальные объектные имена (объектные имена глобальных предикатов). Область просмотра прокручивается на адрес, соответствующий этому имени. Предикат Goto dword Prt включается, когда отмечен флажок Show as Dword, и кур- сор указывает на номер, который может быть принят как адрес формата домена dword. В этом случае Goto dword Prt изменяется на Goto "address". Эта функция прокручивает область просмотра на address.
890 Часть VI. Возможности визуальной среды разработки Регистры Окно просмотра машинных регистров показано на рис. 27.73. .U.J ДЛИЛИ и! I!! | Registers EAX=0Q95004С ЕВХ=000Е0354 ЕСХ=00950С4С ЕПХ=ОС!68Г’ЕС8 ESI-000E0354 ЕП1=0012Г9АС ДВР=0012ЕА68 ESP=0D12FA48 Е1Р=00421073 CS=QO1B DS=0023 ES=0023 FS = OCJ38 GS=OOOQ :jS5B0023 C=0 P=1 A=0 Z=U S=C 0=0 : 'j . ' . . Рис. 27,73. Окно машинных регистров Опции настройки отладчика Окно опций настройки отладчика (рис. 27.74) Debugger Configuration можно открыть при помощи команды меню View | Configuration. Рис. 27.74. Опции настройки отладчика Глобальные опции Эти опции являются глобальными (Global) и сохраняются в файле VIPDEBUG.INI. □ Show Startup Code. Разрешает отладку кода инициализации программы (на уров- не ассемблера), который выполняется перед вызовом goal-программы. □ Save Breakpoints. Разрешает сохранение точек останова для текущего проекта в инициализационном файле отладчика VIPDEBUG.INI. Обратите внимание, что после изменения или перекомпиляции модуля некото- рые или все точки останова, заданные в этом модуле, могут быть потеряны. Когда отладчик открывает новый проект, он удаляет всю сохраненную в файле инициализации информацию о ранее отлаживаемом проекте. Поэтому информа- ция о точках останова удаляется, когда отладчик открывает другой проект.
Гпава 27. Особенности визуальной среды разработки для опытного пользователя 891 □ Wait for Event Instead of Showing Assembler Code. Ссылки на код ассемблера и сам код ассемблера не отображаются. Вместо этого отладчик ждет первое событие, полученное отлаживаемым приложением, и переходит к обработчику этого со- бытия. □ Gray Source Lines not Containing Executable Code. Если строки кода программы не содержат выполняемого кода (комментарии, конструкция if def и т. д.), то они закрашиваются серым цветом. Обратите внимание, что точки останова не могут быть установлены на таких строках. □ Visualize Fail. Отладчик явно отображает, что вызов выполняемого предиката за- вершился неуспехом. Он показывает маркер г~> перед таким вызовом. Стек вызовов Здесь вы можете определить параметры окна Call Stack. □ Update. Эта группа переключателей содержит следующие элементы: • Manual. Окно Call Stack обновляется только при нажатии клавиши <F5>; • Smart. Окно Call Stack обновляется каждый раз, когда изменяется регистр ESP (см. пункт меню View j Machine Level | Registers); • Always. Окно Call Stack обновляется при каждом шаге отладчика. Обновление окна Call Stack может требовать значительных вычислений, ко- торые замедляют выполнение отладчика. Например, если параметры предика- тов — большие списки значений и т. д. □ Scanning Call Stack from Goal. Окно Call Stack будет показывать вызовы только тех предикатов, которые вызываются после вызова цели goal программы. □ Show Module Name. Если опция выбрана, то имена предикатов будут предварять- ся именами соответствующих исходных модулей. □ Show Argument Domains. Будут отображаться домены аргументов предикатов. Дамп памяти В окне Update Window every <NN> Sec вы можете определить, что окно Memory Dump должно обновляться каждые NN секунд. Fonts Позволяет изменить шрифты для окон отладчика Visual Prolog. □ Source Win. Здесь вы можете выбрать шрифт для отображения исходного кода. □ Messages. Здесь вы можете выбрать шрифт для окна сообщений Messages. □ Other Win. Здесь вы можете выбрать шрифт для всех остальных окон. Мы реко- мендуем вам использовать шрифты только с фиксированной шириной.
892 Часть VI. Возможности визуальной среды разработки Изменение путей к исходным файлам Это диалоговое окно может быть открыто из меню Files. В этом окне вы имеете возможность временно определить различные имена подкаталогов, содержащих ис- ходные файлы проекта. Например, запустить отладчик с приложением, исходные файлы которого находятся в сети. Информация, определенная в этом диалоговом окне, сохраняется в файле инициа- лизации отладчика VIPDEBUG.INI. Когда отладчик открывает проект, он удаляет всю информацию о других проектах, сохраненных в файле инициализации отладчи- ка. Поэтому информация об измененных путях к исходным файлам проекта стира- ется, как только отладчик открывает другой проект.
Предметный указатель А abs 385 accessmode 524, 552 API-функции 805 append 356, 920 Application Expert 633 arc tan 386 AS СП-коды 299 assert 369 asserta 369 assertz 369 attr_CheckPortability 795 attr_color_activeborder 797 attr_color_activecaption 797 attr_color_appworkspace 797 attr_color_btnface 797 attr_color_btnshadow 797 attr_color_btntext 797 attr_color_captiontext 797 attr_color_desktop 797 att r_col о r_g raytext 7 97 attr_color_highlight 797 attr_color_highlighttext 797 att r_c о 1 о r_ i п ac t ivebo rd e r 7 9 7 attr_color_inactivccaption 797 attr color_inactivccaptiontext 797 attr_color_menu 797 attr_cо 1 оr inenutext 797 attr_color_scroll bar 797 attr_co!or tooltipback 798 attr_color_tooltiptext 798 attr_color_window 798 attr_color_windowfranie 798 attr_color_windowstatictext 798 attr^color windowtext 798 attr_ctl_button_height 795 attr_ctLcheckbox_height 795 attr_ctl_ cdit_text_heiglit 795 attr_ctl_horz_sbar_hcight 795 attr_ctl_radiobutton_height 795 attr_ctl_static_textjieight 795 attr ctl_vert sbar_width 795 att r_d bl fra me_ height 796 attr_dblframe_width 796 attr_docframe_height 796 attr_docframc_width 796 attr_erase_background 799 attr_franie_hcight 796 attr_frame_width 796 attr_have_color 795 attr_have_mouse 795 attrjconjieigh 795 attr_icon_width 795 attr_menu_height 796 attr_nativc__window 798 attr_num_timers 795 attr_printcr_height 796 a tt r_p ri n te r_h re s 796 attr_printer_vres 796 attr_printer_width 796 attr_screen_height 796 attrscreenjires 796 attr_screen_vres 796 attr_screen_width 796 attr scrccn_window 795
Предметный указатель 973 attr_suppress_update__check 798 attr_task__window 795 attr_title_height 796 attr_win_3dcontrols 798 attr_win_instance 798 attr_win_lbar 798 attr_win_mdi 798 attr_win_mdi_client_hwnd 798 attr_win_rbar 798 attr_win_sbar 798 attr_win_tbar 798 Automatic code updating 652 В B+ дерево 520, 535 0 bt copyselcctor 537 О внутренний указатель 538, 549 О двойные ключи 536 0 длина ключа 535 0 закрытие 537 0 имена 528 О множественный просмотр 536 О обновление 538 0 открытие 537 0 порядок 535 0 создание 536 О статистика 537 0 страницы 535 0 удаление 537 backtracking 267, 31.5 beep 571 binary domain 419 bitand 572 bitleft 573 bitnot 572 bitor 572 bitright 573 bitxor 502, 573 bk_Opaque 762, 766 bk_Transparent 762, 766 ВМР-файл 658 bt__close 537 btcopyselcctor 537 bt_create 536 bt_delete 537 bt open 537 bt_selector 583 bt_statistics 537 bt_updated 556 byte 592 c C: О интерфейс 609 О передача списков 616 О передача структур 615 0 списки 622 caretOfT 794 caret_Set 794 caret_Size 794 cast 799 cb_BinAvailable 793 cb_GetBin 793 cb GetMetafile 793 cbGetPicturc 683, 793 cb_GetSize 793 cb_GetString 793 cb_PictureAvailable 793 cb_PutBin 793 cb_PutMetafile 793 cb_PutPicture 683, 793 cb_PutString 793 cbStringAvailable 793 CDIR858 CD-ROM-инсталляция 198 chain_delete 530 chain_first 531 chain_Jnserta 529 chajn_insertafter 530 chain_insertz 529 chainjast 531 chain_next 531 chain~prev 531 chain_terms 530 char 299, 591 char_int 514 check_determ 326, 586, 604, 608 class 446 class_Create 799 class_Destroy 799 closefile 488 comline 569 composebinary 421 concat 510 consult 370 control_info 728 ' copyfile 494 cos 385 CTL3D32.DLL 833 CTL3DV2.DLL 833 cursor Set 672
974 Предметный указатель cut green 294 cut red 294 cuts 282, 326 О установка 604 D date 568 db_begintransaction 554, 555, 564 db_btrees 528 db_chains 528 db_close 528 db_copy 526 db_create 525 db_delete 528 db_endtransaction 554, 555 db_flush 527 db_open 526, 555 db_openinvalid 527 db_reuserefs 525 db_selector 583 db-Setretry 556 db_statistics 529 db_updated 555 dbasedom 367, 583 DC 806 DDE 700 dde_Connect 700 DEF-файл 858 deletefile 493 denymode 524, 552 determ 410, 587 Dialog Base Units 771 DIALOG.DOM 898 dialog_Create Modal 896 dialog_CreateModeless 896 dialog_int 898 dialogjong 898 dialog_real 898 dialog_SetState 898 difftime 570 dirclose 499 dirfiles 500 dirmatch 499 diropen 498 disk 494 diskspace 571 div 384 dlg_Ask 753 dlg_ChooseColor 675, 770 dlg_ChooseColor2 756 dlg_ChooseFont 756 dlg_Error 757 dlg_GetFileName 753 dlg_GetStr 754 dlg_ListSelect 755 dlg_Note 640, 758 dlg_PrintSetup 756, 775 DLL 865 domains binary 419 draw_FloodFill 764 drawicon 764 draw_Pixel 764 draW-Text 765 draw_TextInRcct 676 drawtools 760 dumpDba 550 dword 592 E C-Activate 729 e_Char 723 e_CloseRequest 709, 720 e_Control 727, 738 e_Create 673, 677, 687, 707, 710, 719, 759, 787, 799, 800 e_DDE 727 e_Deactivate 730 eDestroy 670, 707, 712, 720 C-EndApplication 729, 807 e_EndSession 707, 720 e_Erase Background 724 e_GetFocus 724 e_HScroll 723 e_InitMenu 721, 793 e_KeyDown 723 e_KeyUp 723 e_LoseFocus 724 e_Menu 670, 674, 721, 788 eMouseDbl 722 C-MouseDown 668, 721, 722 e_MouseMovc 667, 669, 722 e-MouseUp 669, 680, 722, 723 e_Move 724 e_Native 709, 725, 751, 805 e_OwnerDraw 726 e_OwnerMeasureItem 710, 726 e_Size 656, 676, 725 e_State 725 e_Timer 676, 677, 727, 788
Предмег-*^ ,<азатель 975 ejjpdate т?? —9.717,718,726 e_User ” ?< '2' e VScrc. ”2- *edit -Create — ? editJ2reateS_m Class 800 editGetCmet 681 edit GetSe ection 681 edit_GetTev 681 edit_PareSir 681 edit_S-e.ectWord 681 chancier "2*9 elsedet 59" endc.ass -946 enddef 597 envsymbol 567 eof 491 erroneous 407, 587 errorlevel 435, 605 event 719 exe 865 exedir 858 existdir 499 existfile 493 exit 432 exp 386 fail 280, 318 failure 407, 587 file 582 file_bin 485 file_str 482 fileattrib 502 filemode 487 filenameext 497 filenamepath 496 filepos 490 files domains 583 findall 358 flow pattern 242 flush 492 font_Create 767 font_GetAttrs 768 font_SetAttrs 768 format 511 free 621 frontchar 508 frontstr 510 fronrtoken 509 G getbinarysize 421 getbyteentry 422 getfocus 728 getrealentry 422 getwordentr 422 global 590 GOAL 703 green cuts 282 GUI 805 H heapsize 571 Hello World 640 IEEE 299 if/then 256 ifdef 597 ifndef 597 imjdir 859 implement 447 include 259 INI-файл 823 installation procedures 198 integer 592 IO_WrStrCon 905 IODECEL.PRE 905 isname 511 к key_current 539 key_delete 538 key_first 538 keyjnsert 538 keyjast 538 key_next 539 key_prev 539 key_search 538 keyboard 583 lasterror 436, 804 listdba 543
976 Предметный указатель lists 312 hi 386 log 386 long 592, 709 losefocus 728 M make 856 makebinary 421 malloc 621 marktime 569 matching 241, 264 MDI-режим 708 member 355, 585 me mbyte 574 memdwoid 574 memword 574 menu 786 menu_Check 787 menu_Enable 787 menu_GetR.es 786 menuPopUp 788 menuSetText 786 menu_Tag 721 menu_ Update 787 MESSAGE.PRE 905 MESSAGE.PRO 905 MetafileAvailable 793 mf_Close 803 mf-Destroy 802 nif_Load 802 mf_Open 803 mf_Play 802 nif_Save 803 mod 384 modified 729 multi 587 mykey_next 549 mykey_prev 549 mykey_search 549 N nl 473 no_extra_info 729 nocopy 590 nondeterm 409, 586 not 286, 908 NullWin 807 о OBJDIR 858 openappend 487 openfile 495 openmodify 487 open read 341, 486 openwrite 341, 487 operands 380 operators 380 osversion 571 other_event_handler 716 p pict Close 683, 789 pict_Destroy 790 pict_Draw 678, 790 О версия с масштабированием 790 pict_FromBin 789 pict_Get From Res 791 pict GetFroniWin 791 pict GetSize 792 pict Load 678, 791 pict_Open 683, 789 pict_Rotate 792 pict_Save 792 pict_ToBin 789 print-AbortJob 777 print EndJob 683, 776 print EndPage 683, 777 print-GetConfig 777 print SetConfig 777 print^SetO dentation 776 prints tart Job 683, 776 print_StartPage 683, 776 ptr_dword 574 R random 383 randominit 383 RCT: 0 домен 707 0 структура 7 07 readblock 484 readchar 482 readdevice 341, 488 readint 481 readln 481
Предметный указатель 977 read real 481 readterm 482 real 299 rect_GetClient 714 rectjnflate 780 rect_Interaect 780 rect- IsEinpty 780 rect_Offset 714, 781 rect_PntInside 781 rect_Union 781 rec uraion 320 red cuts 282 ref 524, 583 ref_tenn 532 reg 583 renamefile 494 repeat 319 nes_(nenct 784 RES-файл 863, 865 retract 371 retractall 372 round 387 s samekey_next 549 samekey_prcv 549 save 373, 378 screen 583 scroll 728 scroll_type 748 scrollCode 748 SDI-режим 708 searchchar 512 searchfile 493 searchstring 513 set MapMode 684 set_MapScale 684 setbyteentry 422 setdwordentry 422 setrealentry 422 setwordentry 422 short 592 sin 385 single 410 sizeof-функция 620 sleep 569 sound 570 sqrt 386 static 447 stderr 583 stdin 583 stdout 583 stop_color 764 storage 571 str_char 514 str_DOSStr2 793 str_int 514 str len 511 str real 515 strings 300 struct 616 subchar 511 substring 512 SWEEP.PRO 657 symbols 300 SYMFILE 859 SYSDIR 858 syspath 569 system 566 tan 385 Target Type 832 term_bin 424 term_delete 531 term_replace 531 term str 515 time 568 time/4 676 timeout 570 timer_Kill 788 timer_Set 676, 727, 788 TOOLBAR.DOM 906 TOOLBAR. PRE 906 toolbar_create 906 toolbar__GetValue 906 toolbar_GroupControls 907 toolbar_remove 906 toolbar_resize 906 toolbar_SetValue 906 trap 432 tree_Create 678 tree_CreateSubClass 800 trunc 387 u UI strategy 831 along 592
978 Предметный указатель unification 264 upper_lower 515 ushort 592 V Value List 898 VIP.EXE 814 Visual Prolog: О запуск 200 О обновление версии 200 О системные требования 197 VPI-приложение 633 VPI.CON 723, 759, 769 vpi_AigValidation 803 vpi_C heck Exist Di al og 784 vpi_CheckExistString 784 vpi_CloseProcess 807 vpi_ComposeRGB 770 vpi_CreateProcess 729, 806 vpi_GetAttrVal 684, 769, 799 vpi_GetResStr 784 vpi_GetTaskWin 716 vpi_HelpClose 801 vpijnit 703, 707, 794, 798 vpi_ Process Events 781 vpi_ProcessEvents/l 781 vpi_ProcessEvents/2 782 vpi_SetAttrVal 703, 799 vpiSetErrorHandler 804 vpi_ShowHelp 801 vpi_ShowHelpContext 801 vpi_ShowHelpKeyWord 801 VPRCONV.EXE 815 VPR-файл 814, 863 w win_B ri ngToTop 717 win_CaptureMouse 779 win_Check 741 win_Clear 670 win_Create 710, 759, 784, 785 win_CreateControl 711, 752 win_CreateDyn 771 win_CreateDynControl 712, 771 win_CreateDynDialog 771 win_ Create Res Dialog 711, 759, 784 win_Destroy 707, 709, 712, 720 win_DPtoLP 774 win EnableHook 726, 805 win_GetClientRect 655 win_GetActiveWindow 717 win_GetAttrVal 806 win_GetBrush 763 win_GetClient 684 win_GetClientRect 713 win_GetClip 713 win_GetCtlHandle 730 win_GetData 716 win_GetDrawMode 763 win_GetDrawTools 760 win_GetFocus 717 win_GetFont 763, 768 win_GetFontMetrics 768 win_GetMapScale 774 win_GetNativeGraphicContext 806 win_GetOuterRect 714 win_GetParent 717 win_GetPen 675, 761 win_GetResDialog 784 win_GetState 705, 715 win_GetTextExtent 766 win_GetType 704 win_Invalidate 656, 718, 725, 727 win_IsChecked 688 win LPtoDP 774 win_MapPoints 770 will-Move 714, 724 win_NeedsUpdate 718 win_PostEvent 782 win_ReleaseMouse 779 win_SendEvent 709, 782 win_SetBackColor 766 win_SetBrush 763 win_SetClip 713 win_SetDrawMode 763 win_SetFocus 717 win_SetFont 763, 767 win_Set Fore Color 766 win_SetHandler 716 win_SetIcon 716 win_SetMapMode 773, 802 win_SetMapScale 773, 802 win_SetPen 675, 761 win_SetState 705, 715 win_SetSubClassHandler 801 win_SetText 716 win_Update 718 win_ValidateRect 718 windef_list 752 windef_list 738
Предметный указатель 979 windowtype 704,711, 728, 759 word 592 write 473 write_a_list 916 writeblock 485 writedeyice 341, 488 writef 478 wsf_DlgBorder 796 wsfjnvisible 806 wsf_Maximized 806 wsf_Minimized 806 wsf_Restored 806 A Абсолютное значение 384, 385 Абстрактные классы 461 Автоматическое преобразование типов 256, 300 Аксиомы: 0 Пеано 105 0 равенства 104 Алгебра 95 Алгоритм унификации 118 Аргументы 224, 233 0 множественные 311 0 составные 301 о унификация 266 0 выходные 587 0 входные 242 Арифметика: 0 модульная 384 0 операции 380 0 целочисленная и вещественная 384 Арктангенс 386 Арность 255, 586 Ассемблер, вызов подпрограмм 626 Атом 117 Атрибуты файлов 494 Б База данных: 0 внешняя 520 D accessmode 552 с denymode 552 = блокирование 558, 564 - вставка термов 529 = вывод содержания 543 = выгрузка в текстовый файл 550 - отмены 524 ° доступ через В+ деревья 539 ° журнал изменений 544 ° закрытие 528 ° защищенная 544 ° имена В+ деревьев 528 ° имена цепочек 528 D копирование 526 ° неисправная 527 ° обновление 545 ° обработка 525 ° обработка термов 531 ° освобождение 527 ° открытие 526 ° перемещение 527 ° программирование 541 ° просмотр 541 ° разделение файлов 552, 554, 564 ° расположение 525 ° режим разделения 553 ° селекторы 522 ° создание 525 а статистика 529 ° структура 521 ° транзакции 554 ° удаление 528 ° удаление цепочек 530 ° указатель 535 ° числа-указатели 531 0 внутренняя 257 0 числО'указатель на терм 524 База фактов: о внутренняя 366 ° безымянная 367 ° использование 368 ° обновление 369 ° объявления 366 0 предикаты, ограничения 367 0 раздел 366
980 Предметный указатель Базовый домен: О char 250 О real 250 О string 250 О symbol 250 Бесфункторные термы 616 Бинарные поисковые деревья 337 Бинарный терм: О доступ 422 О размер 421 О реализация 420 О создание 421 О сравнение 422 О текстовый формат 420 О унификация 422 Браузер: О исходного кода 638, 639, 819 О дерева проекта 639 О идентификаторов ресурсов 639, 820 Булевская переменная 56 Буфер обмена: О вставка 639 О вырезание 639 О копирование 639 в Варианты типа 585 Ввод/вывод, переопределение 489 Версия ОС 571 Вещественные числа 299 Внутренняя цель 262 Восьмеричные числа 380 Вывод: 0 логический 223 0 форматированный: ° примеры 479 Вызов: 0 детерминированный 281 0 недетерминированный 281 Вызов по умолчанию: {> окна и панели 639 0 предикаты 639 0 соглашений 610 Выравнивание памяти 594, 617 Выражения 380 0 порядок вычислений 381 Высказывание 92, 93 Вычисления 380 Гипертекст 343 Глобальный стек 608 Голова предложения 591 Грамматический разбор 361, 363 Данные: 0 надежность 528 0 объекты составные 301 0 структуры рекурсивные 331 0 типы 332 Декларативный язык 223 Деление 380 6 слов на слоги 920 Деревья: 0 бинарный поиск 337 0 обход 333 0 создание 335 0 целевые 272 0 как типы данных 332 Деструкторы 458 Детерминизм 281 0 отсечение 286 Диалоговое окно 708, 752, 753 Дизъюнкция 238 Директивы компилятора 259, 604 0 check_determ 326, 586, 604 о determ 368 0 errorlevel 605 0 struct 616 Дискриминант 389 Длина списка 349 Домены: 0 bkmode 762 0 brush 762 0 byte 592 0 controMnfo 728 0 cursor 777 0 db_selector 522, 583 0 dbasedom 367 0 dialog_Jield_list 897 0 dialog itein val 897 0 dialog val list 897 0 drawtools 760 0 dword 592 0 ehandler 709 0 errhandler 804
Предметный указатель 981 О event 719 О file 489, 582 О integer 592 О long 592, 709 О menu 786 О menu_tag 721, 906 О реп 761 О ref 524 О scrollCode 748 О short 592 О toolbar_list 906 О toolbar_value 906 О ulong 592 О ushort 592 О windefjist 711 О windowtype 711, 728 О word 592 О бинарные 419 О внешних баз данных 524 О заданные пользователем 585 0 классов 449 0 объявление 580 0 пользовательские 593 0 предикатные 584 0 предопределенные 583 0 раздел 246, 579 0 сокращение объявлений 580 0 составные смешанные 311 0 специально заданные 583 0 списковые 580 0 ссылочные 583 0 стандартные 580, 591 Доступ к ОС 566 Дочерние окна 708 Е Единицы базовые, диалоговые 745, 771 Единицы логические 794 Если/тогда, условный оператор 256 ж Журнал изменений 544 3 Заголовки в модулях 839 Закрытие окна 670 Занесение фактов во время исполнения 369 Запросы 122, 225, 237 Значение: 0 возвращаемое 588, 611 0 выражения 380 и Идентификаторы 300, 392 0 сравнение 391 Имена 576 0 ограничения 576 0 правильные 511 0 предикатов 244 п внешних баз данных 521 Имя файла проекта 814 Индивидуальность 445 Инкапсуляция 444 Инсталляция: 0 обновление 200 0 системные требования 197 0 с CD-ROM 198 Интерфейс с: 0 другими языками 609 0 множеством документов 708 0 одним документом 708 Исчисление предикатов 104 к Квадратное уравнение 389 Квадратные корни 384, 386 Кванторы 85 Клавиши "горячие" 639 Классы 444, 447 Клаузальная форма 110 Ключевое слово 577, 639 0 signed 250 0 unsigned 250 0 detenu 587 0 erroneous 587 0 failure 587 0 global 590 0 multi 587 0 посору 590 0 nondeterm 586 0 procedure 587 Команда: 0 выполнение проекта 639 0 построение проекта 639
982 Предметный указатель Командная строка 569 Комбинация CR-LF 793 Комментарии 240 0 сгенерированные 651 Компилятор, директивы 259 Компиляция условная 597 Конкатенация строк 509, 510 Константы: 0 объявление 257 0 предметные 92 0 предопределенные 597 0 простые 591 0 раздел 257, 595 0 символические 257 0 символьные 299 Конструкторы и деструкторы 458 Контекст устройства 806 Конъюнкция 238 Косинус 385 Курсор, предикаты 778 Куча, размещение из С 621 л Листья дерева 128 Литералы 93 Логарифм натуральный 386 Логика предикатов 223 м Макроопределение 595 Масштабирование анизотропное 774 Матрица 109 Меню 811 0 контекстные 811 0 создать 657 Метафайл 802 Метки 787 Методы 445 Механизм: 0 вывода 223 0 логического вывода 909 Моделирование элементов аппаратуры 917 Модель 95 Модуль: 0 компиляции 367 0 проекта 367 н Набор: 0 входной 357 0 выходной 357 Наследование 445 Нотация инфиксная 388 О Области видимости 457 Обновление: 0 базы фактов 369 0 внешней базы данных 545 Обработка: 0 внешних баз данных 525 О строк 5(18 0 термов 531 0 цепочек 529 Обход дерева 333 Объединение списков 355 Объекты 224, 444, 447 0 простые 251 0 составные 593 Объявление: 0 внешнего имени 614 0 класса 446 0 констант 595 0 предикатных доменов 584 0 предикатов 243 0 раздела фактов 366 0 селекторов В+ деревьев 524 0 селекторов баз данных 524 0 составных доменов 307 0 списков 346 0 ссылочных доменов 583 0 функции 588 Ограничения на: 0 имена 576 0 использование предикатов баз фактов 367 0 символические константы 596 0 структуры программы 578 Окно: 0 Screen 707 0 Task 707 0 активизировать 665 0 верхнего уровня 708 0 дочернее 708
Предметный указатель 983 О код по умолчанию 867 О создать 662 ° пример 646 О дерева проекта 820 0 задачи 643, 707 0 проекта 815 0 сообщений 819 0 экрана 707 Округление 384, 387 Оператор: 0 присваивания 388 0 отношения 388 Операции 380 0 логические 56, 572 0 на уровне бит 572 0 порядок 381 0 растровые 790 Определения, составные смешанные домены 311 Оптимизация последнего вызова 323 Опции: О генерации кода 856 О глобальные 823 О компилятора 848 ОС, доступ из приложений 566 Основы Пролога 223 Откат, точка 267 Отладчик 879 Отношения 224, 233 Отсечение 282 О зеленое 294 О и детерминизм 286 О использование 282 0 как goto 293 0 красное 294 п Пакет диалоговых окон 895 Палитра 768 Память: 0 выравнивание 594, 617 0 динамическое размещение 621 : освобождение 608 < размещение 618 = динамическое 621 Панель инструментов 813, 878 : добавить к окну 663 < создать 660 Параметры: 0 входные 610 0 выходные 610 0 командной строки для VDE 814 0 процедур 412 Передача параметров 610 Переменные 226, 229, 234, 593 0 „ShiftCtlAlt 645 0 _Win 645 0 анонимные 236, 593 0 предметные 92 0 свободные 265, 583, 593 0 связанные 265, 583, 593 0 унификация 265 0 цикта 328 Переход к разделу объявлений 639 Петля, поиске возвратом 319 Платформа 830 Повтор и рекурсия 315 Подкаталоги 805 Подкласс 801 Подключение файлов в программу 598 Подкомпоненты 593 Подсчет элементов списка 349 Подцель, отрицание 287 Поиск: 0 записи в базе данных 535 0 сначала — вглубь 334 0 с возвратом 267, 315 ° прерывание с отсечением 282 Полная теория 103 Полоса прокрутки: 0 вертикальная 747 0 горизонтальная 747 0 позиционирование 749 0 соразмерность 749 Порядок вычислений 381 Поток параметров 242, 358, 393 0 несуществующий 588 Правила 121, 224, 225, 591 0 заголовок, унификация 266 0 использование 292 0 синтаксис 255 0 вывода 77, 101 О как процедуры 291 Предикатные символы 92 Предикаты 84, 233 0 beep 571 0 bitand 572 Продолжение рубрики см. на с. 984
984 Предметный указатель Предикаты (прод.)\ О bitleft 573 О bitnot 572 О bitor 572 О bitright 573 О bitxor 502, 573 0 bt_close 537 О bt_copyselector 537 О bt_create 536 О bt__delete 537 О bt_open 537 О bt_statistics 537 О bt_updated 556 О caret_OfF 794 О caretSct 794 О caret_Size 794 О cb BinAvailable 793 О cb GctBin 793 О cb_GetMetafile 793 О cb_GetPicture 683 О cb_GetSize 793 О cb_GetString 793 О cb_PictureAvailable 793 О cbPutBin 793 О cb_PutMetafile 793 О cb_PutPictLire 683, 793 О cb_PutString 793 О cb_StringAvailable 793 О chain_delete 530 О chain_first 531 О chain_inserta 529 О chain_inscrtafter 530 О chain_insertz 529 О chain last 531 О chain_next 531 О chainprev 531 О chain_terms 530 О char_int 514 О classCreate 799 О class_Destroy 799 О closefile 488 О comline 569 О concat 510 О copyfile 494 О date 568 О db begintransaction 555 О db_btrees 528 О db_chains 528 О db_close 528 О db_copy 526 О db_delete 528 О db_endtransaction 555 О dbjlush 527 О db_open 526, 555 О db_openinvalid 527 О db_reuserefs 525 О db_setretry 556 О db_statistics 529 О db_updated 555 О dde__Connect 700 О delctefile 493 О determ 410 О dialog_SetState 898 О dialog_VLGet* 898 О difftime 570 О dirclose 499 О dirfiles 500 О dirmatch 499 О diropen 498 О disk 494 О diskspace 571 О d!g_Ask 753 О d!g_ChooseColor2 756 О dlg_ChooseFont 756 О dlgError 757 О dlg__GetStr 754 О d!g_ListSelect 755 О dlg_Note 758 О dlg_PrintSetup 756 О draw_FloodFill 764 О draw icon 764 О draw_Pixel 764 О draw_Text 765 О draw_Text!nRect 676 О edit_CreateSubClass 800 О edit_GetCaret 681 О editPasteStr 681 О envsymbol 567 О cof 491 О existfile 493 О exit 432 О file_bin 485 О file__str 482 О fileattrib 502 О filemode 487 О filenameext 497 О filenamepath 496 О filepos 490 О flush 492 О font Create 767
Предметный указатель 985 ? font_SetAttrs 768 ' format 511 : frontchar 508 С- frontstr 510 0 fronttoken 509 0 isname511 0 key_current 539 0 key_delete 538 0 key_first 538 0 key _ insert 538 0 keyjast 538 0 key_next 539 0 key_prev 539 0 key_search 538 0 lasterror 804 0 listdba 543 0 marktime 569 0 mem byte 574 0 memdword 574 0 mem word 574 0 menu_Check 787 0 menu_Enable 787 0 incnu GetRes 786 0 menu_PopUp 788 0 menu_SetText 786 0 menu_Update 787 0 nif_Close 803 0 mf_Destroy 802 0 mf_Load 802 0 mCOpen 803 0 mCPlay 802 0 mfSavc 803 0 nondeterm 409 0 not 286, 908 0 openappend 487 0 openmodify 487 0 openread 486 0 openwrite 487 0 osversion 571 0 pict_Close 683, 789 0 pict^Destroy 790 0 pict__Draw 678 0 pict_FromBin 789 0 pict_GetFrom Res 791 0 pict_Get From Win 791 pict_GetSize 792 pict_Load 678, 791 pict Open 683, 789 ^Rotate 792 ’ i* Save 792 0 pictToBin 789 0 print-AbortJob 777 0 print„EndJob 683, 776 0 print-EndPage 683, 777 0 print__GetConfig 777 0 print SctConfig 777 0 print_SetOrientation 776 0 print__StartJob 683, 776 0 print-Start Page 683, 776 0 ptr_dword 574 0 random 383 0 randomin it 383 0 readblock 484 0 readchar 482 0 readdevice 488 0 readint 481 0 readin 481 0 readreal 481 0 readterm 482 0 rect_GetClient 714 0 rect__lsEmpty 780 0 rect_PntInside 781 0 ref_term 532 0 renamefile 494 0 res_menu 784 0 retract 371 0 searchchar 512 0 searchfile 493 0 searchstring 513 0 set_MapMode 684 0 set_MapScale 684 0 single 410 0 sleep 569 0 sound 570 0 storage 571 0 str_char 514 0 str_DOSStr2 793 0 str_int 514 0 str_len 511 0 str_real 515 0 subchar 511 0 substring 512 0 syspath 569 0 system 566 0 terni-bin 424 0 term-delete 531 0 term_replace 531 0 terni-Str 515 0 this 456 Продолжение рубрики см. на с, 986
986 Предметный указатель Предикаты (пред.): О time 568 О time/4 676 О timeout 570 О timer_Kill 788 О timer_Set 676, 788 О toolbar_GroupControls 907 О trap 432 О tree_CreateSubClass 800 О upperjower 515 О vpi_CheckExistDialog 784 О vpi_CloseProcess 807 О vpi_ComposeRGB 770 О vpi_CreateProcess 806 О vpi_GetAttrVal 769, 799 О vpiGetTaskWin 716 4» Ж1 О vpi_ProcessEvents/l 781 О vpi__SetAttrVal 799 О vpi_SetErrorHandler 804 О vpi_ShowHelp 801 О vpi_ShowHelpContext 801 О vpLShowHelpKeyWord 801 О win BringToTop 717 О win__CaptLii’cMouse 779 О win_Check 741 О win_Create 710, 759, 784, 785 О win__CreatcControl 7J1, 752 0 win_CreateDyn 771 0 win__CreateDynControl 712, 750, 771 0 win_CreateDynDialog 771 0 win_CreateResDialog 711 0 win_Destroy 712 0 win DPtoLP 774 0 win__EnableHook 726, 805 0 win__GetActiveWindow 717 0 win_GetAttrVal 799, 806 0 win__GetBrush 763 0 win_GetClip 713 0 win__Get Draw Mode 763 0 win GetDrawTools 760 0 win_GetFocus 717 0 win__GetFont 763, 768 0 win_Gct Font Metrics 768 0 win__GctMapSca|e 774 0 win__GetNativeGraphicContext 806 0 win GctParcnt 717 0 win__GctPen 675, 761 0 win_GetState 705, 715 0 winjnvalidate 718, 725, 727 0 win LPtoDP 774 0 win__MapPoints 770 0 win Move 714 0 win_NeedsUpdate 718 0 win_PostEvent 782 0 win_ReleaseMouse 779 0 win_SendEvent 782 0 win_SetBackColor 766 0 win_SetBackMode 762 0 win_SetBrush 763 0 win_SetClip 713 0 win_SetData 716 0 win_SetDraw Mode 763 0 win SetFocus 717 0 win_SetFont 763, 767 0 win_SetForeColor 766 0 win_SetHancfter 7Г6 0 win_SetIcon 716 0 win_SetMapMode 773, 802 0 win_SetMapScale 773, 802 0 win_SetPen 675, 761 0 win_SetState 705, 715 0 win_SetSubClassHandler 801 0 win_SetText 716 0 win_Update 718 0 win_ValidateRect 718 0 writeblock 485 0 writedevice 488 0 writef 478 0 арности 255, 586 0 баз фактов 590 0 виртуальные 453 0 внешние 610 0 детерминированные 586 0 защищенные 462 0 имен 244 0 инфиксные 302 0 как аргументы 412 0 курсоров 778 0 множественных объявлений 585 0 объявлений 243 0 потоков параметров 611 0 равенства 104, 388 0 раздела 243, 585 0 разделения файлов 555 0 реализации в других языках 610 0 символа вставки 794 0 специально определенные 577 0 специальные 495
Предметный указатель 987 Предложения 230 альтернативное 326 голова 591 игнорирование cut 326 недетерминированные 605 : обратимое 358 : раздел 243, 591 : Хорна 223 Преобразование: < вещественных в строки 515 < единичного символа в строку 514 С регистра 515 0 -дмволов в целые 514 < ;_рюк в вещественные 515 0 в~рок в целые 514 0 :~роки в символ 514 0 "илов 256, 514 0 целых в символы 514 < „алых в строки 514 < числа 387 Прерывание поиска с возвратом 282 Префикс 109 Приблизительное равенство 389 Признак 85 Приключения в опасной пещере 914 Примеры Пролог-программ 908 Проблема расстановки N ферзей 922 Проверка типа 372 Программирование на системном уровне 566 Программы: 0 автономные исполнительные 578 0 выбора кратчайшего пути 913 0 ограничения 578 Проект: 0 error level 606 О база данных 818 О дерево 820 0 имя 828 0 окно 815 Процедура 225, 243 0 рекурсивная 346 0 управления строками 508 Процесс повторения 315 р Равенство 388 0 знак, унификация составных объектов 302 0 предикат 388 0 приблизительное 389 Раздел: 0 глобальный 259 0 доменов 246, 579 0 констант 257 0 предикатов 243 0 предложений 243 0 фактов 257, 589 ° безымянный 0 базы фактов, обновление 369 0 цели 249 Разделение файлов 564 Разделы: 0 глобальные 259 0 программ 577 ° основные 242 Реализация: 0 бинарных термов 420 0 классов 447 0 типов 614 Регистры, резервирование 624, 629 Режимы: 0 MDI 708, 785 0 SDI708 0 доступа 495 0 отображения: D English 773 ° Metric 773 ° Twips 773 0 разделения 495 Резолюция 111 0 линейная 122 Рекурсия 320, 345 0 повтор 315 0 списки 345 0 с процедурной точки зрения 356 0 хвостовая 351 Решения: 0 альтернативные 267 0 единственное 281 0 множественные 281 0 найти все сразу 358 0 управление поиском 280 Родительский класс 446 С Свободное вхождение 93 Свободные переменные 265, 583, 593
988 Предметный указатель Свойство 85 Селекторы внешних баз данных 522 Символ подчеркивания в запросе 237 Символические константы 576 О ограничения 596 Символы 299, 391 О преобразование в строки 514 О преобразование в целые 514 О сравнение 391 Синтаксис правил 255, 470 Синус 385 Системы аксиом 76 Сканирование 363 Скулемовская форма 110 Следствие логическое 81 Сложение 380 Случайные числа: 0 генерация 383 0 инициализация 383 Событие 668—670, 674, 676 0 e_Control 738 0 е_Сreate 677 0 е_Мепп 788 0 e_MouseUp 680 0 e_Native 725, 751 0 e^Timer 677, 727, 788 0 динамического обмена данными 707 Соглашение: 0 об обозначениях 611 0 об именовании предикатов 521 Создание бинарных термов 421 Сопоставление 241, 264 Сортировка на основе дерева 339 Сохранение фактов во время работы программы 373 Список 312, 345 0 грамматический разбор 361 0 длина 349 0 и рекурсия 345 0 использование 348 0 как составной объект 594 0 обработка 345, 347, 622 0 объединение 355 0 объявление 346 0 определение 345 0 передача в С 616 О подсчет элементов 349 0 принадлежность 354 0 скомпонованный 616 О смешанные типы 594 О составной 360 Сравнение 380 0 арифметических выражений 388 0 строк 391 0 идентификаторов 391 0 символов 391 Среда визуальной разработки 811 Ссылка объекта на себя 456 Сте к указател ь 612 Строка подсказки 814 Строки 300, 392 0 внутренний адрес 574 О возврат 508 0 длина 511 0 конкатенация 509, 510 0 построение 508 0 преобразование в: ° символы 514 ° терм 515 0 проверка 508 а длины 508 0 разделение 508 0 создание пустой 508 0 сравнение 391, 392 Структура: 0 PNT722 О RCT714, 771 0 внешних баз данных 521 0 данных 593 0 передача в С 615 0 предложения 310 0 программы 578 Счетчики 328 Считывание фактов 370 Таблица символов 431 Тангенс 385 Теорема: 0 Геделя 103 0 Черча 104 0 Эрбрана 115 Теория первого порядка 104 Термы 92, 100, 593 0 бесфункторные 616 0 бинарные преобразования 424 0 вставка в цепочки 529 0 выравнивание 594, 617 0 обработка 531 0 перемещение 531 О преобразование в строки 515
Предметный указатель 989 ? расположение в цепочке 524 О свободный 100 0 удаление 531 0 цепочки 522 0 число-указатель 524, 532 Тип преобразования 256, 514 Точка отката 267 У Удаление: 0 нескольких фактов сразу 372 0 фактов во время выполнения программы 371 Указатель, стек 612 Умножение 380 Универсум Эрбрана 114 Унификация 116, 264, 593 0 в списке аргументов 330 0 составных объектов 302 Управление: 0 доступом в производных классах 462 О памятью бОб Усечение 384, 387 Установка cuts 604 0 защищенные 462 0 как процедуры 291 0 раздел 257, 366 О сохранение 373 0 считывание 370 0 удаление 371, 372 0 унификация 266 Форматирование аргументов 508 Формулы: 0 атомарные 93 0 пропозициональные 56 Функторы 301, 581 Функции: 0 sizeof 620 О объявление 588 0 трансцендентные 384 0 тригонометрические 384 Функциональные символы 92 X Ханойские башни 918 Хвостовая рекурсия 351 0 оптимизация 322 ф Файл: 0 BMP 685, 789 0 CON 758, 783 0 CUR778 0 DLL 751 0 EXE 750 0 Н 783 0 INI 823 0 LIB 750 0 module.DOM 842 0 module.PRE 842 0 project.INC 842 0 RES 752, 759, 783, 863 О VIP.INI 823 0 VPR 783, 814, 863 О атрибуты 494 0 закрытие 486 v объектный 609 0 открытие 486 Факториал, вычисление 320 Факты 121, 224, 591 0 занесение во время исполнения 369 ц Целевое дерево 272 Цели 237 0 раздел 249 0 унификация 265 Целые, преобразование в символы 514 Цель: 0 внутренняя 262 0 логическая 917 Цепочка 520, 522 0 вставка термов 529 0 имена 528 ° термов 530 0 обработка 529 0 удаление 530 ч Числа 299 0 восьмеричные 380 0 преобразования 387 0 шестнадцатеричные 380 0 с плавающей точкой 591
990 Предметный указатель III Шаблон потока параметров 242 э Экономия ресурсов памяти 608 Эксперт: О окон и диалоговых окон 639, 643, 867 О приложений 633, 827 Элемент управления 708 0 класс 801 0 кнопка 740 0 переключатель 742 0 пиктограмма 739 0 подкласс 801 0 раскрывающийся список 746 0 список 742 0 статический текст 739 0 элемент группировки 739 0 VBX752 0 расширение 751 0 флажок 741 Элементы языка 576
Приложения Приложение 1. Описание прикладных пакетов, облегчающих создание VPI-программ Приложение 2. Примеры программ на языке Пролог Приложение 3. Приложение 4. Medication Assistant — медицина, основанная на доказательствах В. П. Оревков. Обратный метод поиска вывода Приложение 5. Описание компакт-диска
ПРИЛОЖЕНИЕ 1 Описание прикладных пакетов, облегчающих создание VPI-программ Пакет диалоговых окон Visual Prolog включает в себя пакет диалоговых окон, который значительно упроща- ет работу с диалоговыми окнами (хотя диалоговые окна, конечно, можно создавать л без него). Чтобы использовать пакет диалоговых окон в проекте, нужно включить в проект все необходимые файлы, установив флажок Project | Application Expert | Options | VPI Options | Dialog Package. Пакет диалоговых окон полностью написан на языке Пролог и определен в файлах DIALOG.PRO, DIALOG.PRE и DIALOG.DOM на прилагаемом CD-диске в каталоге <CDROM>:\RUN\VP1\INCLUDE\D1ALOG. Все глобальные предикаты в пакете диалоговых окон имеют префикс dialog . Для поддержки пакета диалоговых окон может быть использован специальный инструмент — эксперт пакета диалоговых окон. Чтобы его активизировать для конкретного окна, необходимо его вызвать и затем выбрать предикат dialog_Create (...) из списка Code Style. После этого эксперт пакета диалоговых окон может быть вызван при помощи командной кнопки Dialog Pack. Преимущества использования пакета диалоговых окон заключаются в следующем: □ легкая инициализация элементов управления при создании диалогового окна; □ легкий возврат значений при закрытии диалогового окна; □ автоматическая обработка клавиши перехода (Escape Key); □ выполнение.различных проверок и подтверждений правильности: проверка обя- зательных полей ввода, диапазона, значений по умолчанию для полей, которые не были введены, проверка регистра поля ввода, проверка того, что длина вво- димой строки не превышает заданного значения, преобразование значений эле- ментов управления к значениям целого, длинного целого или вещественного ти- па и т. д.; □ автоматическая обработка групп переключателей на платформах, которые не поддерживают стиль Auto Button.
896 Часть VII. Приложения Создание и инициализация нового диалогового окна Эксперт окон и диалоговых окон генерирует для диалогового окна обычный преди- кат dlg_<name_of_dialog>_create, который может создавать диалоговое окно с опре- деленной компоновкой: dlg_mymodal_Create (Parent) :- CHECKBOX - b_false,+ AGE = void, %MARK mymodal, new variables dialog_CreateModal (Parent, dlg__mymodal_ResID, "", %BEGIN mymodal, ControlList, 20:45:09-5.9.1995, Code automatically updated! df(idc_checkbox,checkbox(CHECKBOX),nopr), df(idc_age,editint(AGE,[mandatory,range(5,99)]), str_prompt("Wrong !")) %END mymodal, ControlList ] r dlg_mymodal_eh,0,VALLIST,ANSWER) , ANSWER = idc_ok, dlg_mymodal_update(VALLIST),!. Предикат для создания диалогового окна будет использовать два различных вызова из пакета диалоговых окон в зависимости от типа создаваемого диалогового окна (модальное или немодальное): window dialog_CreateModal (window Parentwindow, resid ResID, string Title, dialog_field_list InitialFields, ehandler, long CreationData, dialog_val_list Outputvalues, integer ReturnButton) window dialog_CreateModeless (window Parentwindow, resid ResID, string Title, dialog_field_list InitialFields, ehandler, long CreationData) Различие состоит в том, что предикат dialog_CreateModal не закончит свое выпол- нение, пока диалоговое окно не будет закрыто. В этом случае возвращаемые значе- ния будут присвоены двум переменным Outputvalues и ReturnButton. Предикат dialog_CreateModeless завершится, как только будет создано диалоговое окно, т. е. в тот момент, когда оно отображается на экране. Получение любых воз- вращаемых значений от немодального диалогового окна обычно выполняется после нажатия кнопки ОК (или любой другой кнопки, которая закрывает диалоговое окно).
Приложение 1. Описание прикладных пакетов для создание VPI-программ 897 Параметр initialFields домена dialog_field_list содержит описание элементов управления для пакета диалоговых окон. Это список функторов, каждый из которых имеет следующую структуру: df(Contгolid, Properties/ ErrorHandling) где control id — это целочисленная константа, которая определяет элемент управле- ния в диалоговом окне, a ErrorHandling — выполняемое действие в случае неуспеш- ного завершения проверки правильности. Параметр Outputvalues домена dialog_val_list для предиката dialog_CreateModal возвращает список результирующих значений элементов управления при закрытии диалогового окна. Каждый элемент в списке имеет следующую структуру: dv(dialog_control_id, dialog_item_val) Домен dialog_item_val отличается для каждого типа элементов управления. Для доступа к этим результирующим значениям генерируется предикат, что подробно описано ниже. Обратите внимание, как эксперт пакета диалоговых окон вставляет новые пере- менные. При определении новой переменной в эксперте пакета диалоговых окон объявления новых переменных будут вставлены после метки "%mark mymodal, new variables". Эти объявления переменных не будут изменяться/удаляться при выпол- нении вами любых изменений в редакторе диалоговых окон или в эксперте пакета диалоговых окон. Обработчик событий для нового диалогового окна Если вы используете пакет диалоговых окон, то не нужно заботиться и об обработ- чике событий для диалоговых окон. По умолчанию обработка событий от элементов управления выполняется пакетом диалоговых окон, и во многих случаях этого дос- таточно. Однако эксперт окон и диалоговых окон генерирует обычный предикат обратного вызова обработчика событий, где возможна работа с командными кнопками (изме- нение выбора в списке и т. д.). При помощи эксперта можно добавить и отредакти- ровать предложения для событий диалогового окна. Если вы вставляете какие-либо предложения обработки событий, то необходимо помнить общее правило: если предикат завершится неуспешно, пакет диалоговых окон выполнит обработку события по умолчанию', а если предикат завершится успешно, то пакет диалоговых окон не будет выполнять никакой дальнейшей обработки этого со- бытия. Получение возвращаемых значений от диалогового окна Эксперт окон и диалоговых окон генерирует для диалогового окна предикат dlg_<dlgName>_update, который может применяться для доступа к значениям эле- ментов управления при закрытии диалогового окна. Пакет диалоговых окон форми-
898 Часть VII. Приложения рует список всех значений после того, как диалоговое окно было закрыто. Для полу- чения фактических значений используется набор предикатов dialog_VLGet*. Здесь VL означает Value List (Список значений). Рассмотрим листинг П1.1. .....г-г-.-л»:...*.V'..•••; ^Листинг !<*** 4***1 I **»'»***-*4 »4el*4»i»4V»*»'HT4 »•»(» Ч<» »»Г»4 Hjltu ч , ««« • ••*••*•••••* v * V *• v * «<««•« <» М в <4 »»4 , *-*4 , 4 4 «•««<••««•• ТТЬ *«<««««* «Ь т Ь «««« Ц I *.. * v 4 «4 »«***-**« »! dlg_mymodaljipdate (_VALLIST) : -!, %BEGIN mymodal, Update controls, 5.9.1995, Code automatically updated! -CHECKBOX = dialog_VLGetCheck(idc_checkbox,—VALLIST), _AGE = dialog_VLGetint(idc_age,_VALLIST), %END mymodal, Update controls t Внутри автоматически обновляемого блока кода переменные, указанные в эксперте пакета диалоговых окон, будут привязаны к значениям элементов управления. Довольно просто изменить предикат dlg_<dlgName>_update так, чтобы он возвращал значения, необходимые в конкретном приложении. Но помните, что экспертами кода будет изменен только код внутри автоматически обновляемых блоков; таким образом, вы самостоятельно должны изменить остальную часть сгенерированного по умолчанию кода. Домены диалоговых окон Откройте файл DIALOG.DOM в каталоге <CDROM>:\RUN\VPI\INCLUDE\DIALOG на прилагаемом CD-диске, чтобы увидеть определенные в нем домены. Вы должны знать, что пакет диалоговых окон позволяет работать с пустыми значениями (нет значения) для числовых доменов. В действительности, для числовых доменов пакет диалоговых окон не использует значения непосредственно, но окружает их функто- рами, так что можно увидеть, является значение пустым или нет: dialog—int = void; i(integer) dialog_real = void; r(real) dialog—long = void; l(long) Включение/блокировка и отображение состояний элементов управления При помощи предиката dialog_Setstate можно включать/блокировать или скры- вать/отображать несколько элементов управления в одном вызове. Это свойство очень эффективно, поэтому советуем чаще им пользоваться. dialog—Setstate (window, dialog_enable_list) Домен блокировки диалогового окна имеет вид: dialog—enable_list = dialog—enable* dialog—enable = enable(dialog_control_id, boolean); show(dialog_control_id, boolean)
Приложение 1. Описание прикладных пакетов для создание VPI-программ 899 Получение значений отдельных элементов управления из списка значений Существует множество предикатов, которые могут использоваться для выбора зна- чений из списка, возвращаемого при закрытии диалогового окна. Каждый предикат принимает на вход идентификатор элемента управления и список значений, возвра- щая соответствующее значение: boolean dialog__VLGetCheck (dialog_control_id, dialog_val_list) dialog_control__id dialog_VLGetRadiobutton (dialog_control_id, dialog_val_list j string dialog_VLGetStr(dialog_control_id,dialog_val_list) dialog_int dialog_VLGet!nt(dialog_control_id,dialog_val__list) dialog_long dialog_VLGetLong(dialog__control_id,dialog_val_list) dialog__real dialog_VLGetReal (dialog_control_id, dialog__val_list) string dialog_VLGetListEdit(dialog_control_id,dialog_val_list) integer dialog_VLGetScrollBar(dialog_control_id,dialog_val_list) dialog_VLGetListBox(dialog_control_idf dialog__val_list tslisttilist) dialog_VLGetListButton (dialog_control__id, dialog_val_list, string, integer) dialog_VLGetCustom(dialog_control__id,dialog_val_list,string,long) string dialog__VLGetDataField (dialog_control__id, dialog_val_list) Установка/получение значений элементов управления в диалоговом окне Некоторые диалоговые окна позволяют выполнять множество действий при откры- том диалоговом окне. Эти действия могут быть вызваны при помощи командной кнопки, после чего изменятся значения других элементов управления, или они мо- гут быть вызваны изменением текущего элемента в списке, при котором изменятся другие значения. Для упрощения этих действий используется набор предикатов, ко- торые принимают на вход дескриптор диалогового окна и идентификатор элемента управления. Получение значений boolean dialog_GetCheck(window,dialog_control_id) dialog_control_id dialog_GetRadiobutton(window, dialog_control_id FirstFromList) string dialog_GetStr(window,dialog_control_id) dialog_int dialog__Get!nt (window,dialog_control_id) dialog_long dialog_GetLong(window,dialog_control_id) dialog_real dialog_GetReal (window, dialog__control_id) string dialog_GetListEdit (window, dialog_control__id) integer dialog_GetScrollBar(window,dialog_control_id)
900 Часть VII. Приложения dialog_GetListBox (window, dialog_control—id, slist, ilist) dialog—GetListButton(window,dialog_control_id,string,integer) dialog_GetCustom(window,dialog—control—id,string,long) string dialog_GetDataField(window,dialog_control_id) Установка новых значений dialog_SetCheck(window,dialog_control_id,boolean) dialog_SetRadiobutton(window,dialog_control—id FirstFromList, dialog—control—id Pushed) dialog_SetStr (window, dialog—control—id, string) dialog_Set!nt (window, dialog_control—id, dialog_int) dialog—SetLong (window, dialog_control—id, dialog_long) dialog_SetReal (window,dialog_control—id, dialog_real) dialog_SetListEdit(window,dialog—control—id,string) dialog_SetScrollBar(window,dialog_control—id,integer) dialog_SetListBox(window,dialog_control—id,ilist) dialog_SetListButton(window,dialog_control—id,integer) dialog_SetCustom(window,dialog_control_id,string,long) dialog_SetDataField(window,dialog—control_id, string) dialog—CheckEditProperties(window,dialog_control—id) dialog_CheckEditProperties(window) dialog_SetControlTitle(window,dialog_control—id, string Title) Программный интерфейс редактора Visual Prolog В создаваемых вами приложениях можно использовать редактор Visual Prolog. Файл EDIT.PRE в каталоге <CDROM>:\RUN\VPI\INCLUDE содержит объявления пре- дикатов, которые применяются для выполнения операций в окне редактора. Пример <CDROM>:\RUN\VPI\EXAMPLES\EDITOR на прилагаемом CD-диске показывает, как работать с редактором, который поддерживает загрузку, сохранение, операции вырезания, вставки и т. д. Создание окна редактирования Для создания окна редактора можно использовать три предиката, которые опреде- ляют такие факторы, как: должен ли редактор поддерживать автоматический пере- нос слов или гипертекстовые поля. Следующие предикаты создают новое окно: window edit—Create(wintype WinType, ret Ret, string FileName, menu Menu, window Parentwin, wsflags WinFlags, font Font, boolean Readonly, boolean Indent, string InputStr, ulong InitPos,ehandler Handler)
Приложение 1. Описание прикладных пакетов для создание УР1-программ 901 window edit_CreateWrap(wintype WinType, ret Ret, string FileName, menu Menu, window ParentWin, wsflags WinFlags, font Font, boolean Readonly, boolean Indent, boolean Wrap, string InputStr, ulong InitPos, ehandler Handler) window edit_CreateHyper(wintype WinType, ret Ret, string FileName, menu Menu, window ParentWin, wsflags WinFlags, font Font, boolean Readonly, boolean Indent, boolean Wrap, string InputStr, ulong InitPos, ehandler Handler) Для работы с редактором в окне, которое уже создано, можно использовать: edit__CreateSubClass (window Win, string FileName, font Font, boolean Readonly, boolean Indent, boolean Wrap, string InputStr, ulong InitPos, ehandler Handler) Типичное применение предиката edit__CreateSubciass — создание специальных элементов управления, основанных на редакторе. Получение текста из окна редактора Следующие два предиката позволяют получать весь текст или его часть из окна редактора. string edit_GetText(window) string edit_GetText(window, unsigned StartPos, unsigned EndPos) Добавление нового текста Для добавления нового текста используются три предиката. Строка может быть вставлена в текущую позицию, в заданную позицию или в конец текста в окне ре- дактора. edit_PasteStr(window Win, string NewStr) boolean edit_PasteStr(window, ulong Pos, string Str) edit_AppendStr(window, string Str) Функции редактирования Предикаты редактирования применяются для моделирования нажатия заданной кла- виши или для удаления части текста относительно текущей позиции. edit_Char(window, char) edit_Enter(window) edit Del(window)
902 Часть VII. Приложения edit_Backspace(window) edit_DelLeftWord(window) edit—DelRightWord(window) edit_DelToStartOfLine(window) edit—DelToEndOfLine (window) Установка/получение выделенного текста Эта группа предикатов используется для установки или получения выделенного тек- ста в окне редактора. boolean edit_SetSelection(window Win, ulong Posl, ulong Pos2) edit_GetSelection(window Win,ulong Posl, ulong Pos2) edit—SelectCurLine (window) boolean edit—SelectWord(window Win) Возвращение информации позиционирования Эта группа предикатов используется для получения текущей позиции знака вставки в редакторе в виде смещения или значения "строка + колонка". ulong edit_GetPos (window Win) edit_GetPos(window,integer Line,integer Column) integer edit_GetNumberOfLines(window) Перемещение знака вставки Предикаты этой группы используются для изменения текущей позиции в редак- торе. edit__Left (Window) edit_Right(Window) edit_Up(Window) edi t_Down(Wi ndow) edit—PgUp(Window) edit—PgDn(Window) edit_WordLeft(Window) edit_WordRight(Window) edit—LineHome(Window) edit—LineEnd(Window) edit—FileHome(Window) edit_FileEnd(Window) Boolean edit—GotoPos(window win, ulong Pos) edit—GotoLine(Window,integer Line)
Приложение 1, Описание прикладных пакетов для создание УР1-программ 903 Отмена/восстановление Функции отмены и восстановления реализуются при помощи следующих пре- дикатов: edit Undo(Window) edit_Redo (Window) Предикат edit_PossibleUnDoReDo возвращает два булевых значения, которые опреде- ляют, возможно или нет выполнение операций отмены/восстановления в текущий момент: edit_PossibleUnDoReDo(Window, boolean Undo, boolean Redo) Работа с буфером обмена Для выполнения операций работы с буфером обмена используются следующие пре- дикаты: edit_Cut (Window) edit_Copy (Window) edit_Paste (Window) edit_Delete (Window) Изменение регистра Для изменения регистра выбранного текста используются следующие предикаты: edit__ReverseCase (Window) edit_UpperCase(Window) edit_LowerCase(Window) Вызов диалоговых окон редактора Редактор имеет несколько внутренних диалоговых окон, которые могут быть вызва- ны следующими предикатами: edit_OptionsDlg() edit_SearchDlg(Window) edit_SearchAgain(Window) % Позиция функции edit_ReplaceDlg(Window) edit_GotoLineDlg(Window) edit_GotoPosDlg(Window) edit_SetFontDlg(Window) Г ипертекстовые ссылки Если вы работаете с гипертекстовым редактором, то для создания и удаления гипер- текстовых ссылок могут использоваться следующие предикаты:
904 Часть VII. Приложения edit_CreateHyperField(Window) edit_DeleteHyperField(Window) Чтобы понять, как работает гипертекстовый редактор, изучите пример, находящийся на прилагаемом CD-диске (<CDROM>:\RUN\VP1\EXAMPLES\HYPDEMO). Гипер- текстовые ссылки вставляются в редакторе как следующая текстовая последователь- ность: <hyper_begin><Highlighted textxhyperjnniddleXHidden textxhyper_end> где hyper_begin, hyper_middlе и hyper end — это специальные символы, определен- ные в файле ED1T.D0M. Если вам нужно вставить новое поле, то используйте примерно такой код: format(HyperField/"%с%с%с%с%с”, hyper_begin,HighLight,hyper_middle,Hidden,hyper_end), edit_PasteStr(Win,HyperField), Остановка/продолжение обновления редактора Если вам необходимо выполнять много операций в редакторе, то можно отключить обновление, пока не выполните все эти операции. Приостановить и затем продол- жить обновление можно при помощи следующих предикатов: editjSuspend(Window) edit_Resume(Window) Отображение/скрытие знака вставки При помощи следующего предиката можно скрыть или отобразить знак вставки в редакторе: edit_CaretShow(Window, boolean DoShow) Скрытие/отображение строки состояния При помощи следующего предиката можно скрыть верхнюю строку состояния в ре- дакторе: edit_StatusLineShow(Window, boolean Hide/Show) Установка цвета элементов языка Пролог Цвет элементов языка Пролог в редакторе может быть установлен при помощи сле- дующих предикатов: editjSetColoring (Window, integer TypeOfColoring) edit_SetAssociations (edit__asslist Associations) edit asslist = edit GetAssociations()
Приложение 1. Описание прикладных пакетов для создание VPI-программ 905 Свойства редактирования Различные свойства редактора могут быть установлены следующими предикатами: integer edit_GetTabSize() edit_SetTabSize(Window, integer Tabsize) boolean edit_GetIndent(} edit_SetIndent(Window, boolean On/Off) boolean edit_GetInsert() edit_SetInsert(Window, boolean On/Off) % Возврат / установка всех опций edit_GetOptions(ilist ReturnedOptionList) edit_SetOptions(ilist OptionList) Пакет окна сообщений Пакет окна сообщений (информационных окон) обеспечивает обработку окон, вы- водящих короткие сообщения. Кроме того, при помощи задания предложений для предиката lO WrStrCon, определенного в файле IODECEL.PRE каталога INCLUDE, информационное окно может перехватывать вывод предикатов write и writef. Для создания информационного окна в эксперте приложений необходимо устано- вить флажок Message Window, после чего заголовочные файлы для этого окна авто- матически будут включены в приложение. Пакет окна сообщений определен в файлах MESSAGE.PRO и MESSAGE.PRE ката- лога <CDROM>:\RUN\VPI\INCLUDE\MESSAGES на прилагаемом CD-диске. Предикаты, определяемые пакетом окна сообщений, приведены в табл. П1.1. Таблица П1.1. Предикаты, определяемые пакетом окна сообщений Предикат msg_create(integer NoOfLines) msg_close(} insg_SendMsg(string) msg_SetLines(integer NoofLines) msg_Clear(} insg_Resize (window Parent) insg_SetFocus (} Описание Создает окно сообщений с заданным числом строк Закрывает окно сообщений Добавляет новую строку Изменяет максимальное число строк Очищает окно Изменяет размер родительского окна Устанавливает фокус ввода для окна сообщений
906 Часть VII. Приложения Пакет панелей инструментов Пакет панелей инструментов для Visual Prolog определен в файлах TOOLBAR.DOM и TOOLBAR.PRE в каталоге <CDROM>:\RUN\VPI\INCLUDE\TOOLBAR на прила- гаемом CD-диске. Панель инструментов всегда ассоциирована с окном, независимо от того, где она расположена: слева, справа, наверху, внизу, внутри или она является перемещаемой. Размещение панели инструментов обычно обрабатывается в редакторе панелей ин- струментов, и сгенерированный код для создания панели инструментов следует об- рабатывать при помощи эксперта панелей инструментов. Если панель инструментов используется для дублирования команд меню кнопками панели, то дополнительного кодирования не требуется, т. к. кнопки панели инстру- ментов посылают событие ejenu родительскому окну. Создание панели инструментов Для создания панели инструментов необходимо вызвать предикат: window toolbar__create (toolbar_style, color, window Parent, toolbar_list) где toolbar list ~ это список описаний элементов управления, расположенных на панели инструментов. Изменение размеров панели инструментов Размер панели инструментов настраивается при ее создании. Важно помнить, что панели инструментов должны изменять размер вместе с изменением размера роди- тельского окна. Для события e_size эксперт окон и диалоговых окон вставит вызов предиката: toolbar__resize (window ParentWin) Удаление панели инструментов Для удаления панели инструментов следует вызвать предикат: toolbar_remove(window ToolbarWin) Изменение значений на панели инструментов Следующие два предиката могут использоваться для получения и установки значе- ний флажков, раскрывающихся списков и текстовых полей: toolbar_SetValue(window TbWin, menu_tag, toolbarevalue) toolbar_GetValue(window TbWin, menu_tag, toolbarevalue) где menu_tag определяет элемент управления, a toolbar value принадлежит следую- щему домену:
Приложение 1. Описание прикладных пакетов для создание VPl-программ 907 toolbar_value = ctrl_value(boolean, boolean); text_yalue(string); lbut_value(boolean, integer); % (включен, освобожден) % (текст) % (включен, позиция в списке) попе Создание переключателей на панели инструментов Так же, как и в диалоговых окнах, на панели инструментов можно определить спи- сок флажков, принадлежащих группе, в которой может быть включен только один флажок. Это свойство нельзя определить в редакторе панелей инструментов — нуж- но использовать предикат toolbar GroupControls: toolbar_GroupControls(window TbWin, integer GroupNo, toolbar_menu_tags) Пакет "собственных" элементов управления Если вы используете "собственные" элементы управления, то вам следует посмотреть на прилагаемом CD-диске в каталоге <CDROM>:\RUN\VPI\INCLUDE\OWNDRAW следующие файлы: OWNDRAW.DOM, OWNDRAW.PRE и OWNDRAW.PRO. Эти файлы значительно упростят процедуру создания таких элементов управления.
ПРИЛОЖЕНИЕ 2 Примеры программ на языке Пролог В этом приложении представлен ряд программ, предназначенных для стимулирова- ния ваших собственных идей и для углубления знаний, полученных при изучении предыдущих глав. Надеемся, приведенные примеры послужат хорошей основой для реализации ваших собственных замыслов и позволят создать необходимые вам при- ложения. Построение экспертной системы Воспользуемся Visual Prolog для построения небольшой экспертной системы, кото- рая будет угадывать одно из семи животных (если такое существует), задуманное пользователем. Система будет задавать вопросы и строить логические выводы на основе полученных ответов. Этот пример наглядно демонстрирует поиск с возвра- том, использование базы данных и эффективное применение предиката not. В качестве примера можно привести типичный диалог экспертной системы с поль- зователем (для большего понимания дан перевод): has it haxr? (Оно имеет шерсть?) yes (да) does it eat meat? (Оно ест мясо?) yes (да) has it a fawn color? (Оно желтовато-коричневой масти?) yes (да) has it dark spots? (Оно имеет темные пятна?) yes (да) Your animal may be a cheetah! (Ваше животное должно быть гепард!) Именно способность Visual Prolog проверять факты и правила обеспечивает про- грамме свойства, присущие экспертной системе. Первым шагом при построении такой системы является обеспечение ее знаниями, необходимыми для выполнения
Приложение 2. Примерыпрограмм на языке Пролог 909 рассуждений; это, напомним, носит название механизма логического вывода. Меха- низм логического вывода рассмотрен на примере программы сЫбеОЬрго (лис- тинг П2.1). ч* *£**.••*> ** V t *.• *.! *.* * * JA* *.**.*:* фН I »»»»»*• »» <» * *.* * * •Л*Л»*:• *;*.* * М-* * • •*• ***♦,♦ • *•* * •• »• » »Ц» *.»*.**.* * » •*** < ******* •_* *<* ****** *»*'**:.**** * * ^* **•* * * *•*;**?•**** ***** **? * * ** *W* *** * ' * -* ’ * •* •*J * •*. Листинг П2.1. Программа ch16e01.prp-k<-^|iJ ***»»****»'»**«**'*»»>*ъ»»««*»»'ъ***к*»**»*****»*> ************< »»»»»»»»»»»»»*»»*» » »*'*«*«*»*»**«**»* *******'*********** global facts xpositive(symbol,symbol) xnegative(symbol,symbol) predicates nondeterm animal_is(symbol) nondeterm it_is(symbol) ask(symbol,symbol,symbol) remember(symbol,symbol,symbol) positive(symbol,symbol) negative(symbol,symbol) clear_facts run clauses animal_is(cheetah) it_is (mammal) , it__is (carnivore) , positive (has, tawny__color), positive(has,dark_spots). animal_is(tiger) it_is (mammal) , it_is(carnivore), positive(has, tawny_color), positive(has, black_stripes). animal_is(giraffe) it_is(ungulate), positive(has,long_neck), positive(has,long_legs), positive(has, dark_spots). animal_is(zebra) it_is(ungulate), positive(has,black_stripes). animal_is(ostrich) it is(bird), negative(does,fly), positive(has,long_neck), positive(has,long_legs), positive(has, black_and_white_color).
910 Часть VII. Приложения animal__is (penguin) it_is (bird) , negative(does,fly), positive(does,swim) , positive(has,black_and_white_color). animal__is (albatross) it_is(bird),positive(does,fly_well). it__is (mammal) positive(has,hair). it is(mammal):- positive(does,give_milk) . it_is(bird):- positive(has,feathers). it_is(bird):- positive(does, fly) , positive(does,lay_eggs). it_is(carnivore) positive(does,eat_meat). it_is(carnivore) positive(has,pointed_teeth), positive(has, claws), positive(has,forward_eyes). it_is(ungulate) it_is (mammal) , positive(has,hooves). it_is(ungulate) it__is (mammal) , positive(does,chew_cud). positive(X, Y):- xpositive(X,Y) , !. positive(X, Y) :- not(xnegative(X,Y)), ask(X,Y,yes). negative(X,Y):- xnegative(X,Y),!. negative(X,Y):- not(xpositive(X, Y)) , ask(X,Y,no). ask(X,Y,yes) t * f write(X," it ",Y,’\n’), readln(Reply),nl,
Приложение 2. Примеры программ на языке Пролог 911 frontchar(Reply,'у',_), remember (X, У, yes) . ask(X,Y,no):- write(X," it ", Y,'\n'), readln(Reply),nl, frontchar(Reply,'n',_), remember(X,Y,no). remember(X,Y, yes) assertz(xpositive(X,Y)). remember(X,Y, no) assertz(xnegative(X,Y)). clear_facts:- write("\n\nPlease press the space bar to exit\n"), retractall(_,dbasedom),readchar(_). run: - animal_is(X),!, write("\nYour animal may be a (an) ",X), nl,nl,clear_facts. run : - write("\nUnable to determine what"), write("your animal is.\n\n"), clear_facts. goal run. Каждое животное описывается рядом признаков, которыми оно обладает (или нет). Ответы на вопросы пользователя описываются через предикаты positive (X,Y) и negative (X, Y). Следовательно, система может задать, например, такой вопрос: Does it have hair? (У него есть шерсть?) Получив ответ на этот вопрос, программа должна иметь возможность сохранить его в базе данных так, чтобы впоследствии использовать этот ответ в своих рассуж- дениях. Для простоты в пример были включены только положительные и отрицательные ответы. Для их использования в базу данных включены два предиката: facts xpositive(symbol, symbol) xnegative(symbol, symbol) Факт, что животное не имеет шерсти, будет записан так: xnegative(has, hair). Правила positive и negative используются для контроля ответов пользователя и задания новых вопросов.
912 Часть VII. Приложения positive(X,Y) :- xpositive(X,Y), !. positive(X,Y) ;- not(xnegative(X,Y)), ask(X,Y,yes). negative(X,Y) :- xnegative(X, Y), !. negative(X,Y) :- not(xpositive(X,Y)), ask(X,Y,no). Заметьте, что второе правило как для positive, так и для negative гарантирует, что при задании вопроса пользователю не возникнет противоречие. Предикат ask служит для "формулирования" вопросов, он же "запоминает" ответы. Если ответ начинается с буквы у, то система предполагает, что ответом является yes (да), а если с п — то по (нет). Проанализируем листинг П2.2. Г Листинг П2.2. Задание вопросов и анализ ответов ask(X, Y, yes) !, write(X, " it ", Y, '\n'), readln(Reply), frontchar(Reply, 'y', _) , remember(X, Y, yes). ask(X, Y, no) !, write(X, " it ", Y, '\n'), readln(Reply), frontchar(Reply, ’n’, _), remember(X, Y, no). remember(X, Y, yes) assertz(xpositive(X, Y)). remember(X, Y, no) assertz(xnegative(X, Y)). /* Уничтожение всех старых фактов */ clear_facts write("\n\nPlease press the space bar to exit\n"), retractall (_, dbasedom), readchar (__) . Для практики введите в компьютер приведенный выше механизм логического выво- да и базу знаний. Прибавьте к ним ряд объявлений для дополнения программы и затем проверьте, какой будет получен результат. Полный текст программы с экс- пертной системой по животным был приведен ранее (см. листинг П2.1). Пример оболочки экспертных систем (GENI.PRO), также разработанной на Visual Prolog, находится на прилагаемом CD-диске в каталоге <CDROM>:\RUN \TXTEXAMP\GENI. Эта оболочка базируется на технике, которая представлена в данном примере, вместе с дополнительными возможностями, позволяющими ди- намически изменять правила.
Приложение 2. Примеры программ на языке Пролог 913 Задача выбора кратчайшего пути Допустим, мы хотим создать систему, которая помогает выбрать оптимальный мар- шрут при поездке из одного города в другой. Ниже, с помощью Visual Prolog, по- строим небольшой прототип такой системы. Модель системы должна давать нам ответы на следующие вопросы: □ Есть ли дорога из данного города в другой город? П Какой город расположен менее чем в 10 милях от данного города? Программа chl6e02.pro, приведенная в листинге П2.3, является классическим при- мером использования поиска с возвратом и рекурсии для решения такой задачи. Листинг П2.3. Программа ch16e02.pro ; . .. ........ • ” ... ’ ..••••• • • •' г • • .г • •. .. Г/. «Г. •' • •. ?.«. ь г J .• ' .. • • . .'••••• • • ••••. 5-. . * • DOMAINS town = symbol distance - integer PREDICATES nondeterm road(town,town,distance) nondeterm route(town,town,distance) CLAUSES road(tampa,houston, 200). road(gordon,tampa,300). road(houston,gordon,100). road(houston,kansas_city,120). road(gordon,kansas_city,130). route(Townl,Town2,Distance):~ road(Townl,Town2,Distance). route(Townl,Town2,Distance) road(Townl,X,Distl) , route(X,Town2,Dist2) , Distance=Distl+Dist2, На рис. П2.1 представлена карта для прототипа системы. Каждое предложение для предиката road является фактом, который описывает доро- гу определенной длины (в милях) и ведет из одного города в другой. Предложение для предиката route указывает на то, что разрешается прокладывать маршрут из одного города в другой через несколько промежуточных пунктов. Следуя по маршруту, можно с помощью третьего параметра distance получить общую про- тяженность маршрута. Предикат route описан рекурсивно, маршрут может состоять из одного участка до- роги. Тогда общая протяженность маршрута равна длине участка дороги.
914 Часть VII. Приложения Gordon Tampa Рис. П2.1. Карта-прототип С другой стороны, можно проложить маршрут из города Townl в город Town2, доехав сначала из города Townl до города х, а затем проехать другим путем от города х до города Town2. Общая протяженность маршрута равна сумме расстояний от Townl до х и от х до Town2, как показано во втором предложении для route (дороги). Испытайте программу chl6e02.pro (см. листинг П2.3) с помощью целевого утверж- дения: goal route(tampa, kansas_city, X). Может ли программа проложить маршрут для любого исходного и конечного пунк- тов маршрута? Если нет, то как изменить программу, чтобы избежать любых неожи- данностей? В следующем примере вы получите представление о том, как составить список всех городов, лежащих на маршруте, чтобы гарантировать, что ни по какому из городов маршрут не пройдет дважды, и избежать попадания программы в бесконечный цикл. Когда вы разберетесь с этими вопросами, попробуйте расширить систему, добавив в нее другие города и дороги. Приключения в опасной пещере Допустим, вы путешественник, который слышал о том, что в подземелье зарыт клад с золотом. До вас многие пытались найти этот клад, но безуспешно. Подземелье состоит из лабиринта коридоров, которые связывают между собой отдельные пеще- ры, полные разных опасностей в виде чудовищ и грабителей. Условие: все золото спрятано в одной пещере. Какой маршрут позволит вам добыть золото и вернуться невредимым? Рассмотрим карту пещеры, показанную на рис. П2.2. С помощью Visual Prolog введем эту карту в программу и попытаемся с ее помощью проложить безопасный маршрут. Каждый коридор представлен в виде отдельного факта. Правила заданы с использованием предикатов до и route. Введем в програм- му целевое утверждение: go(entry, exit).
Приложение 2. Примеры программ на языке Пролог 915 Рис. П2.2. Лабиринт коридоров Ответ будет содержать список пещер, которые нужно пройти, чтобы добыть сокро- вище и вернуться невредимым. Важной особенностью этой программы является то, что пройденные пещеры зано- сятся в список с помощью предиката route, который определен рекурсивно. Третий параметр предиката содержит список всех пройденных пещер. Если в этот список входит пещера с золотом gold_treasure, то цель достигнута. В противном случае список пройденных пещер дополняется Next room, при условии, что перед этим вы не побывали ни в одной из опасных пещер. Проанализируем программу ch!6e03.pro (листинг П2.4). DOMAINS room = symbol roomlist = room* PREDICATES nondeterm gallery(room,room) % Между двумя пещерами есть коридор nondeterm neighborroom(room,room) % предикат необходим, т. к. неизвестно, % в каком направлении вы идете по коридору avoid(roomlist) nondeterm go(room,room) nondeterm route(room,room,roomlist) % Этим маршрутом надо идти. % roomlist содержит список посещенных пещер. nondeterm member(room,roomlist) CLAUSES gallery(entry,monsters). gallery(entry,fountain).
916 Часть VII. Приложения gallery(fountain,hell). gallery (exit, gold__treasure) . gallery(robbers,gold_treasure). gallery(food,gold—treasure). gallery(monsters,gold_treasure). neighborroom(X,Y):-gallery(X,Y). neighborroom(X,Y):-gallery(Y,X). avoid([monsters,robbers]). gallery(fountain,food). gallery(fountain,mermaid). gallery(fountain,robbers). gallery(mermaid,exit). gallery(gold—treasure,exit) go(Here,There):-route(Here,There,[Here]). go О _) • route(Room,Room,VisitedRooms):- member(gold_treasure,VisitedRooms), write(VisitedRooms),nl. route(Room,Way_out,VisitedRooms):- neighborroom(Room,Nextroom), avoid(DangerousRooms), not(member(NextRoom,DangerousRooms)) , not(member(NextRoom,VisitedRooms)), route(NextRoom,Way_out,[NextRoom|VisitedRooms]). member(X,[X|_]). member(X,[ IH]):-member (X,H). После того как найдено решение целевого утверждения go(entry, exit) вы, возможно, захотите добавить еще несколько коридоров, например, •ч gallery(mermaid, gold_treasure) или добавить опасности, которые необходимо избежать. Даже если имеется несколько решений данной задачи, наша программа выдаст нам только одно. Для того чтобы получить все возможные решения, мы должны исполь- зовать поиск с возвратом после того, как будет найдено первое решение. Добавим предикат fail в первое правило для предиката route: route(Room, Room, VisitedRooms) member(gold—treasure, VisitedRooms), write(VisitedRooms), nl, fail. Для аккуратного оформления результатов вы должны использовать предикат write_a_list, обеспечивающий вывод списка пещер без квадратных скобок и разде- ляющих запятых. Однако в списке пройденных пещер VisitedRooms пещеры содер- жатся в обратном порядке, т. е. Exit стоит первым, a Entry — последним. Поэтому необходимо доработать write_a_list таким образом, чтобы сначала выводился хвост, а затем голова списка.
Приложение 2. Примеры программ на языке Пролог 917 Моделирование элементов аппаратуры Любая логическая цепь может быть представлена в Visual Prolog при помощи преди- катов, где они описывают соотношения между входными и выходными сигналами. Основные элементы логики описаны при помощи таблицы истинности значений (см. листинг П2.5). Основные элементы логики могут быть описаны с помощью не только внешних, но и внутренних связей. В качестве примера сконструируем элемент исключающее или из элементов and, or и not, а затем проверим его работу с помощью полученной про- граммы (рис. П2.3). Output В программе сй!6е04.рго (листинг П2.5) этот элемент описан при помощи предика- та хог. " " * •* * *•*»•?•**» *’• **. * О *• *. » • • • • ' f 4 • • * • * • • ! * • * • • *4 - М М • • • •рЛ» • • М * М •-•М *•.**«' Листинг П2.5. Программа ch16e04.pro •• с / DOMAINS d = integer PREDICATES nondeterm not__(D,D) and_(D, D, D) or__ (D, D, D) nondeterm xor(D,D,D) CLAUSES not—(1,0). and—(0,0,0) . and—(1,0,0). or_(0,0,0). or-(l,0,l) . not—(0,1) . and_(0,1,0) . and—(1,1,1). or_(0,1,1) . or (1,1,1). xor(Inputl,Input2,Output) not (Input1,N1),
918 Часть VII. Приложения not_(Input2, N2) , and_(Input1,N2,N3), and_(Input2,Nl,N4), ОГ—(N3,N4,Output). GOAL xor(Inputl, Input2, Output). Запустив программу с утилитой Test Goal, мы получим следующий результат: Input1=1, Input2=1, Output=0 Inputl=l, Input2=0, Output=l Input1=0, Input2=l, Output=l Input1=0, Input2=0, Output=0 4 Solutions Проинтерпретировав этот результат как таблицу истинности, вы увидите, что ука- занный элемент действительно выполняет требуемую задачу. Ханойские башни Решение задачи "Ханойские башни" — классический пример рекурсии. Эта древняя игра, повторим, состоит из набора деревянных дисков и трех стержней, прикреп- ленных к основанию. Каждый диск имеет свой диаметр, а в середине отверстие, достаточное, чтобы диск можно было надеть на стержень. В начале игры все диски надеты на левый стержень в том порядке, как это показано на рис. П2.4. Рис. П2.4. Ханойская башня Цель игры заключается в том, чтобы перенести все диски с левого на правый стер- жень, по одному диску за раз, при этом ни один больший диск нельзя ставить свер- ху на меньший по диаметру. Эту задачу можно легко выполнить для одного или двух дисков, но, когда число дисков становится больше трех, задача усложняется. Ис- пользуем язык Пролог для ее решения. Для этой игры есть простая стратегия: □ один диск перемещается непосредственно; □ Nдисков переносятся в три этапа: • перенести N— 1 дисков на средний стержень; перенести последний диск на первый стержень; А перенести N — 1 дисков со среднего стержня на правый.
Приложение 2. Примеры программ на языке Пролог 919 В нашей программе есть три предиката: □ предикат hanoi с одним параметром, указывающим, со сколькими дисками мы играем; □ предикат move, с помощью которого мы описываем перенос N дисков с одного стержня на другой, используя третий стержень в качестве промежуточного места для дисков; □ предикат info зли, который указывает на действие, выполняемое с конкретным диском. Проанализируем программу сЫ6е05.рго (листинг Ш.6). *№*.*-* VГ * ' - *** 3^** J *3?-?^ ”****'*** * *’* * * ’* *' * -V ? ***У-*-**5-* - WT ***;’ ** Л** • *:*Л**;* **** ч *.!? V-g*'**? *!* ** *• * * МЛ* W * ?!» **• •**’’ «*ь*>*«« F*nt****<<***<<V?ir**nfa'*«n*****M*<M4-4*k«<4b<h‘*<k«*t4n>'****'****ra¥***tf************'4r****^tV*»*t4*¥kw**'«:«******i*k*4*V**ii-V*******4******V**<*<*<*<*<**4*k••**»•*•» •* DOMAINS loc =right;middle; left PREDICATES hanoi(integer) move(integer,loc,loc,loc) inform (loc,loc) CLAUSES hanoi(N):- move(N,left,middle,right). move(1,A,_,C):- inform(A, C) , !. move (N,A,B,C) :- N1=N-1, move (Nl,A,С,B) , inform(A,C),move(Nl,В,A,C). inform(Locl, Loc2):-nl, write("Move a disk from ", Loci, " to ", Loc2). Для решения задачи "Ханойские башни" с тремя дисками введем целевое утверж- дение: goal hanoi(3). В результате получим ответ: Move a disk from left to right Move a disk from left to middle Move a disk from right to middle Move a disk from left to right Move a disk from middle to left Move a disk from middle to right Move a disk from left to right yes
920 Часть VII. Приложения Деление слов на слоги С помощью очень простого алгоритма можно написать программу, которая будет делить слова на слоги, просматривая последовательность появления согласных и гласных букв в каждом слове. Рассмотрим, например, две следующие последова- тельности: 1. Гласная, согласная, гласная. В этом случае слово разделяется после первой гласной. Это правило можно, на- пример, применить к следующим словам: ruler prolog ru-ler pro-log 2. Гласная, согласная, согласная, гласная. В этом случае слово делится между двумя согласными. Например: number > num-ber panter > pan-ter console > con-sole Эти два правила годятся для большинства слов, но не подходят для слов handbook и hungry, которые не попадают под эти два шаблона. Для разделения таких слов в программу надо было бы включить словарь, содержащий все на свете слова. Напишем Visual Pro/og-программу, которая делит слова на слоги. Вначале она будет предлагать вам ввести какое-нибудь слово, а затем будет пытаться разделить его на слоги с помощью двух указанных правил. Как мы уже видели, это не всегда будет приводить к правильному результату. Во-первых, программа должна разделить слово на список из букв. Поэтому нам в разделе domains нужно объявить: DOMAINS letter = symbol word= letter* Нам также необходим предикат, который будет определять, какая это буква — глас- ная или согласная. К гласным относятся следующие буквы — а, е, i, о, и плюс бук- ва у. Следовательно, можем записать vocal(a). vocal(е). vocal(i). vocal(о). vocal(u). vocal(у). для предиката vocal. Согласные можно определить как буквы, не являющиеся гласными: consonant(L) not(vocal(L)). Нам также понадобятся еще два предиката. Первый — append, append(word, word, word) Второй предикат — для преобразования строки в список из букв: string__word(string, word)
Приложение 2. Примеры программ на языке Пролог 921 В этом предикате используется стандартный предикат front str (см. гл. 20), а также стандартные предикаты free и bound, где free (X) выполняется успешно, если X яв- ляется свободной переменной в момент обращения, a bound (У) выполняется успеш- но, если у — связанная переменная. Теперь мы можем приступить к решению основной задачи: описанию предиката divide, который делит слово на слоги. Предикат divide содержит четыре параметра и определяется рекурсивно. Первый и второй параметры содержат, соответственно, начало start и остальную часть слова Reminder во время выполнения рекурсии. По- следние два параметра возвращают, соответственно, первую и последнюю часть сло- ва после того, как слово разбито на слоги. Например, первое правило разбиения таково: divide(Start, [Tl, Т2, T3|Rest], D, [T2, T3|Rest]) vocal(Tl), consonant(T2), vocal(T3), append(Start, [Tl], D) где start представляет собой список первой группы букв в разделяемом слове. Сле- дующие три буквы в слове представлены переменными Tl, Т2 и тз соответственно, а Rest представляет остальные буквы в слове. В списке d буквы Т2 и тз, а также спи- сок Rest представляют последовательность всех букв, входящих в состав слова. Сло- во делится на слоги, у которых последние буквы содержатся в списке D. Это правило может быть проверено с помощью следующего целевого утверждения: divide ([р, г], [о, 1, о, g], Pl, Р2) Для того чтобы увидеть, как это происходит, введем соответствующие буквы в пред- ложение: divide ( [р, г], [о, 1, о I [д] ], [р, г, о], [1, о [ [д] ]) vocal(о), consonant(1), vocal(о), append([р, г], [о], [р, г, о]). append применяется для конкатенации первой гласной в начале слова, pi получает значение [p,r,o], а Р2 — значение [1,о,д]. Второе правило для предиката divide приведено в полном тексте программы chl6e06.pro листинга П2.7. «*•»**•* •*к*р»>1у** 4 •••••», Fj**»i*»*r*<1MnL»**jt.*F*»****»r*»»*t#»,*»»*»*»*»»**»**»»Fi ►•••’•ГЛ*»***-***’, ••“tl'******** •••••••< ••»*•₽»>»*•»•<< Г<< ,»»»»•<<<»*»< м К**•**»-*•,••-* ••****‘"’*,*'f’-’Л*/-***Г**** Я*,л*^**’*****|*; L Листинг П2.7. Программа ch16e06.pro ь » •*•»*****•*'•••***** tf*tf*****'*'h •»*•••* » if* ,»>•••» if tl I 4I4 H <1*1 4 |^**< •* О • P1**» ««« •* *♦"» >* * *<»• * « *•*•••**•*•••*•• 1 DOMAINS letter = char word_ = letter* PREDICATES nondeterm divide(word_,word_,word_,word_) vocal(letter) consonant(letter) nondeterm string_word(string,word_)
922 Часть VII. Приложения append(word_,word_,word_) nondeterm repeat CLAUSES divide(Start,(Tl,T2,T3|Rest],Dl,[T2,T3|Rest]) vocal(Tl) , consonant(T2),vocal(T3), append(Start,[Tl],Dl). divide(Start,(Tl,T2,ТЗ,T4|Rest],Dl,(T3,T4[Rest]) vocal(Tl),consonant(T2),consonant(T3),vocal(T4), append(Start,(Tl,T2],Dl). divide(Start,(TlI Rest],Dl,D2) append(Start,[Tl],S), divide (S, Rest, Dl, D2) . vocal('a’). vocal('e'). vocal(’i'). vocal('o'). vocal(' u'). vocal(' у'). consonant(B):- not(vocal(В)),В <= ' z', ' a' <= B. string_word("",[]): -1 . string_word(Str,[H|T]) bound(Str),frontchar(Str,H,S),string_word(S,T). string_word(Str,(HIT]) free(Str),bound(H),string_word(S,T),frontchar(Str,H,S). append((],L,L) :- I. append([XI LI],L2, [X|L3]):- append(LI, L2, L3) . repeat. repeat:-repeat. GOAL repeat, write("Write a multi-syllable word: "), readln(S),nl, string_word(S,Word), divide((],Word,Parti,Part2), string_word(Syllablel,Parti), string_word(Syllable2,Part2), write ("Division: ", Syllablel, , Syllable2) , nl, fail. Задача расстановки Л/ферзей В задаче расстановки N ферзей необходимо попытаться разместить на шахматной доске N ферзей таким образом, чтобы ни один из них не был под ударом другого. Следовательно, два ферзя не могут располагаться по одной вертикали, горизонтали или диагонали.
Приложение 2. Примеры программ на языке Пролог 923 Для решения задачи мы обозначим номерами ряды клеток, расположенные по гори- зонтали и вертикали от 1 до N. Для нумерации диагоналей мы поделим их на два типа таким образом, чтобы каждая диагональ однозначно задавалась типом и номе- ром, вычисленным на основе номеров рядов по горизонтали и вертикали: Diagonal = N + Column — Row (Type 1) Diagonal = Row + Column — 1 (Type 2) Если смотреть на доску из левого верхнего угла, то диагональ первого типа напоми- нает символ \, а второго типа — символ /. Нумерация диагоналей второго типа на доске размером 4x4 показана на рис. П2.5. Рис. П2.5. Шахматная доска для задачи расстановки N ферзей Для решения задачи расстановки ферзей программным путем мы должны составить список тех вертикалей, горизонталей и диагоналей, которые являются свободными, а также тех, на которых размешаются ферзи. Положение ферзя на доске определяется номером ряда по горизонтали и вертикали, для чего в раздел domains вводится следующее описание: queen = q(integer, integer) Это объявление указывает на позицию ферзя. Для описания множества позиций мы можем использовать список: queens ~ queen* Аналогичным образом необходимо ввести несколько списков для указания вертика- лей, горизонталей и диагоналей, которые не заняты ферзями. Эти списки можно описать как: freelist = integer* Будем рассматривать шахматную доску как один объект с помощью описания: board = board(queens, freelist, freelist, freelist, freelist)
924 Часть VII. Приложения Списки freelist содержат свободные горизонтали, вертикали и диагонали первого и второго типа соответственно. Теперь представим две позиции на шахматной доске размером 4x4: □ пустая доска; □ на доске один ферзь, расположенный в верхнем левом углу. Пустая доска: board([], [1,2,3,4], [1,2,3,4], [1,2,3, 4,5, 6, 7] , [1,2,3,4,5,6,7]) Доска с одним ферзем: board([q(l,1)], [2,3,4], [2,3,4], [1,2,3,5,6,7], [2,3,4,5,6,7]) Теперь задача может быть решена с помощью описания отношений между пустой доской и доской с N ферзями. Введем предикат placeN(integer, board, board) для которого ниже запишем два предложения. Ферзи размещаются по одному до тех пор, пока не будут заняты все вертикали и горизонтали. Это нашло отражение в первом предложении, где списки вертикалей и горизонталей пусты: placeN(_, board(D, [], [], X, Y), board(D, [], [], X, Y) ) !. placeN(N, Boardl, Result) place_a_queen(N, Boardl, Board2), placeN(N, Board2, Result). Во втором предложении предикат place__a_queen устанавливает связь между пози- циями Boardl и Board2. (В позиции Board2 на одного ферзя больше, чем в Boardl.) Используем следующее описание предиката: place_a_queen(integer, board, board). Ключевым вопросом при решении задачи расстановки ферзей является описание того, как нужно ставить очередного ферзя, начиная с пустой доски. При решении этой задачи мы добавляем нового ферзя в список тех ферзей, которые уже стоят на доске: [q(R, С)IQueens]. Среди оставшихся свободных горизонталей Rows нужно найти ту горизонталь R, на которую мы можем поместить ферзя. В то же время горизонталь R должна быть уда- лена из списка свободных горизонталей, в результате чего будет получен новый спи- сок свободных горизонталей NewR. Эта процедура записывается как findandremove(R, Rows, NewR). Соответственно мы должны найти незанятую вертикаль с. С помощью R и с могут быть вычислены диагонали, на которых помещается ферзь. Затем мы можем прове- рить, находятся ли диагонали D1 и D2 в списках незанятых диагоналей.
Приложение 2. Примеры программ на языке Пролог 925 Ниже приведено предложение предиката place_a_queen, реализующее эти функции: place_a_queen(N, board(Queens, Rows, Columns, Diagl, Diag2), board([q(R, C)[Queens], NewR, NewS, NewDl, NewD2)) findandremove(R, Rows, NewR), findandremove(C, Columns, NewC), Dl-N+S-R, findandremove(Dl, Diagl, NewDl), D2=R+S-1, findandremove(D2, Diag2, NewD2). Далее приведен полный текст этой программы (листинг П2.8). В нем содержатся небольшие дополнения для предиката ngueens, так что нам нужно всего лишь ввести целевое утверждение наподобие следующего: ngueens(5) для получения решения задачи (в данном случае для размещения пяти ферзей на доске размером 5x5). J 4 s v . • ’ ’ > >. 'с'' x ' - &£- -- . .Л- . "• • ' г'! '- " !! a! '"v j"; -A’.- : Г.q''!J:',! • ‘ ! c <5. J7" ! < ' , '•y LЛистингП2;8<Программаch16e07.pro чИИ? gp??-? *4^>* *««« Г* V« кГ«кГ«| «*«*Гк '«*>*** «Гай »Г|| к« к а к к Г*Т|«М к Гк к '* * ( , а | « | vj « * « * ?• fl « | « кГ« « « « « « «'кГ* • к кГ « кГ* « * « кГ«* « А «г« М » * * i domains queen = q(integer, integer) queens = queen* freelist = integer* board = board(queens, freelist, freelist, freelist, freelist) predicates nondeterm placeN(integer, board, board) nondeterm place_a_queen(integer, board, board) nondeterm nqueens(integer) nondeterm makelist(integer, freelist) nondeterm findandremove(integer, freelist, freelist) nextrow(integer, freelist, freelist) clauses nqueens(N):- makelist(N,L),Diagonal=N*2-l,makelist(Diagonal,LL), placeN(N,board([],L,L,LL,LL),Final), write(Final). placeN(_,board(D,[],[],Dl,D2),board(D,[],[],Dl,D2)):-!. placeN(N,Boardl,Result):- place_a_queen(N,Boardl,Board2), placeN(N,Board2,Result). place_a_queen(N,board(Queens,Rows,Columns,Diagl,Diag2), boardf[q(R,C)[Queens],NewR,NewC,NewDl,NewD2)) nextrow(R,Rows,NewR), findandremove(C,Columns,NewC), D1=N+C-R,findandremove(Dl,Diagl,NewDl), D2=R+C-1,findandremove(D2,Diag2,NewD2).
926 Часть VII. Приложения findandremove(X,[X|Rest],Rest). findandremove(X,[Y|Rest],[YlTail]):- findandremove(X,Rest,Tail). makelist(1,[1] ). makelist(N,[N[Rest]) :- N1==N-1,makelist (Nl, Rest) . nextrow(Row, [Row I Rest],Rest) . goal nqueens (5), nl,readchar(_).
ПРИЛОЖЕНИЕ 3 Medication Assistant — медицина, основанная на доказательствах Программа Medication Assistant — медицинская экспертная система поддержки реше- ний (МЭСПР). Система разработана с целью минимизации риска возникновения побочных эффек- тов терапии при назначении комбинированного лекарственного лечения конкретно- му пациенту с сочетанной патологией. Программа объединяет в себе решения большинства основных задач, возникающих при ведении больного, а именно: □ предоставляет возможность оценки и регистрации полного списка клинических признаков различных сочетаний заболеваний; □ помогает врачу прописывать в индивидуальных дозах комбинацию лекарствен- ных препаратов с проверкой на их совместимость, предупреждать опасность пе- редозировки; □ моделирует воздействие назначенной терапии на конкретного пациента, выявля- ет клинические признаки, недостающие для адекватного прогноза, предупреждая о противопоказаниях, возможных побочных эффектах и путях их преодоления; □ генерирует общепринятые медицинские документы на естественном языке. Таким образом, возможности данного проекта позволяют использовать его в повсе- дневной практике лечащего врача в качестве универсального инструмента. Общее описание проекта Данный проект является открытым программным продуктом, это прототип меди- цинской партнерской системы — системы поддержки решений врача (СПРВ) — де- монстрирующий базовые концепции и технические приемы их реализации. СПРВ представляет собой клиническую и фармакологическую базы знаний (БЗ), со- держащие основные признаки заболеваний сердечно-сосудистой системы и некото- рых сопутствующих патологий, а также описания свойств лекарственных препара- тов, применяемых в кардиологии.
928 Часть VII. Приложения В прототипе системы реализована основная идея — используя опыт квалифициро- ванного эксперта, обратить внимание врача на возможные противопоказания и по- мочь ему избежать опасных для жизни пациента побочных эффектов комбиниро- ванной лекарственной терапии. Главной целью разработки явилось создание программы-консультанта, работающей в режиме советчика/критика и не претендующей на роль высшего разума. Решение названной задачи при реализации проекта было бы невозможно без разра- ботки эффективного способа формализации знаний врача. Это, в свою очередь, по- требовало организации соответствующей концепции фреймовых баз знаний и созда- ния специального инструментария — редактора баз знаний. Последний позволяет формировать и модифицировать клиническую и фармакологиче- скую БЗ самому эксперту без помощи программиста. Открытая архитектура проекта и предлагаемая технология дают возможность развивать его в виде отдельных моду- лей. При этом над проектом одновременно может работать группа заинтересованных экспертов — специалистов в различных областях клинической медицины. Первоначально программа базировалась на платформе DOS. Были отработаны ос- новные принципы организации и функционирования системы. Представленный здесь прототип является Windows-версией продукта. Медицинская концепция и алгоритмы реализации задач СПРВ были предложены кандидатом медицинских наук, старшим научным сотрудником клинического отдела НИИ кардиологии (г. Санкт-Петербург) А. А. Темировым. Основой программной разработки явилась технология фреймового представления знаний, созданная кандидатом технических наук В. А. Юхтенко. Программное обеспечение реализовано И. Е. Беловым. Разработка данной системы, успешно опробованной в ряде кардиологических отде- лений клиник Санкт-Петербурга, осуществлена при поддержке компании Prolog Development Center А/S (Дания). Медицинская концепция Большинство существующих медицинских ЭС предназначено для поддержки реше- ний (ПР) врача при диагностике или лечении какой-либо группы заболеваний. Между тем, практикующий врач редко имеет дело с изолированной проблемой. У каждого конкретного больного, особенно в пожилом возрасте, имеется комплекс проблем, находящихся в тесном взаимодействии друг с другом. Любые, даже самые эффективные специализированные ЭС, базирующиеся на различных системах пред- ставления и обработки знаний, не могут быть интегрированы для решения реально- го набора диагностических и терапевтических задач, возникающих перед врачом в конкретной клинической ситуации. Это существенно ограничивает практическое применение ЭС. Данный проект базируется на нескольких исходных предпосылках: П нечеткая формализация и априорно существующая неполнота клинических дан- ных не позволяют строго алгоритмизировать процесс принятия врачебных реше- ний. Состояние каждого больного может представлять собой уникальное сочета-
Приложение 3. Medication Assistant — медицина, основанная на доказательствах 929 ние множества значимых факторов, что не позволяет принять строго детермини- рованное решение, основанное на ограниченном числе специальных правил; □ каждая отдельно взятая клиническая проблема может рассматриваться и решать- ся относительно независимо. В этом случае существуют общепризнанные типы врачебных решений, которые могут быть описаны правилами общего порядка, каждое из которых направлено на нормализацию (точнее, оптимизацию) кон- кретной дисфункции человеческого организма. В этом случае комплексное ре- шение может представлять собой синтез частных решений; □ постановка правильного диагноза является необходимым, но только промежу- точным этапом ведения больного. Главная задача — на основе правильного диаг- ноза эффективно лечить пациента. В идеале, СПРВ должна интегрировать функ- ции диагностики и лечения; □ врач чаще всего нуждается не в ’'автоматическом" принятии решения, а в под- держке решения. Эффективная система ПР должна позволять врачу применять собственную стратегию сбора и интерпретации клинических данных, генерации диагностических гипотез и планов терапии, относящихся к конкретной ситуации. Система ПР должна предоставлять врачу право выбирать и тестировать решения самостоятельно; □ нередко даже опытный врач нуждается в объективном анализе решений в форме контроль/критика, позволяющем прокомментировать преимущества и недостатки предложенных вариантов с позиций польза/вред. Целью работы явилось создание СПРВ, пригодной для использования в повседнев- ной клинической практике, с акцентом на одной из центральных проблем — задаче назначения нескольких лекарственных препаратов пациенту со сложной патологией. Важность этой задачи связана с наличием у большинства современных активных лекарственных средств множества "абсолютных" и "относительных" противопоказа- ний и ограничений, их способности вызывать различные побочные эффекты, осо- бенно в случаях одновременного применения нескольких мощных препаратов. Модель врачебных рассуждений Проектирование программы поддержки решений врача требует анализа методологии рассуждений, построения моделей и алгоритмов лечебно-диагностического про- цесса. Повседневная врачебная практика включает: □ сбор и регистрацию клинических данных; □ постановку диагноза; □ выбор лечения. Регистрация клинических данных является неотъемлемым элементом врачебной практики, предназначенным, в частности, для классификации признаков и состоя- ний, предсказания риска, обоснования адекватности выбранного способа лечения и оценки динамики состояния пациента, общения с коллегами для проведения консилиумов. Для каждого конкретного пациента заболевание или комплекс болезней может быть представлен списком диагностических терминов. Помимо классификационной руб-
930 Часть VIL Приложения рики, как правило, диагноз содержит характеристику наличия и степени нарушения функции органов и систем. Так, заболевания сердечной мышцы могут сопровож- даться (или на конкретном этапе развития не сопровождаться) недостаточностью насосной функции сердца, заболевания почек — почечной недостаточностью и т. д. Из этого следует, что при наличии патологии, отсутствие таких проявлений являет- ся важной характеристикой заболевания. С учетом перечисленных требований полный список необходимой клинической ин- формации состоит как из фактически имеющихся у пациента признаков, так и из "негативных данных", т. е. перечисления симптомов, отсутствие которых актуально для подтверждения правильности принятого диагностического и лечебного решения. Например, декларации "нарушения сердечного ритма отсутствуют” или "уровень глю- козы в крови нормальный" представляются важными для профессионала, поскольку говорят об отсутствии серьезных осложнений или о безопасности применения пре- паратов, влияющих на обмен глюкозы. У каждого конкретного больного, особенно в пожилом возрасте, как правило, на- блюдается комбинация проявлений нескольких острых и хронических заболеваний. Каждая дополнительная медицинская проблема умножает объем необходимой ин- формации. ЭС ПР врача должна служить проводником, ведущим доктора через джунгли медицинских проблем и обилие признаков, критичных для характеристики состояния пациента. Одно и то же (в соответствии с рубрикой классификатора) заболевание может ха- рактеризоваться различным списком синдромов, способных сочетаться в различных комбинациях у разных больных или у одного пациента в различные временные периоды. Например, при ишемической болезни сердца у больного может наблюдаться боль в грудной клетке, и/или аритмия, и/или недостаточность сердца в разных комбина- циях и различной выраженности. Таким образом, список синдромов является спи- ском второго порядка по отношению к диагностическим рубрикам. Каждый синдром, в свою очередь, "описывается иерархической системой призна- ков — симптомов, одной из обязательных характеристик которого является степень или выраженность. Врач постоянно использует термины "легкий", "умеренной си- лы", "тяжелый" для описания клинической картины заболевания. Иногда семанти- ческие значения терминов предусматривают ранжирование признака (например, "ноющая" или "пронизывающая" боль определенной локализации имеет различное диагностическое значение). Понятно, что любой метод лечения, консервативного или хирургического, влияет на целый ряд функций человеческого организма. Врач постоянно "взвешивает" баланс положительных и отрицательных эффектов программы, сравнивая с текущим со- стоянием пациента, в конечном счете, пытаясь оценить интегральную пользу вме- шательства в соотношении с риском нежелательных эффектов. Если оценка не в пользу конкретной программы, цикл выбора другого метода или варианта повторя- ется. В конечном итоге ключевой проблемой клинической практики, реализующей меди- цину, основанную на доказательствах, является оценка риска осложнений "естест- венного течения" заболевания по сравнению с результатами предлагаемой програм- мы ведения больного (включающей диагностические и лечебные процедуры, спо- собные вызывать нежелательные эффекты).
Приложение 3. Medication Assistant — медицина, основанная на доказательствах 931 Следует учесть, что современная врачебная практика имеет дело с одновременным назначением больному нескольких лекарственных препаратов широкого спектра действия на различные функции человеческого организма. Кроме того, препараты сложным образом взаимодействуют между собой. ЭС ПР должна обладать функцией запрета назначения несовместимых препаратов. Целесообразно также предотвращение дублирующего лечения, способного усилить вероятность развития нежелательных эффектов. Применительно к комбинированной лекарственной терапии предполагается необхо- димость построения модели суммарного воздействия списка лекарственных средств. При добавлении в этот список каждого нового компонента врач пытается усилить положительные и ослабить отрицательные стороны лечения. Назначив определен- ную комбинацию препаратов, врач в процессе наблюдения за больным отслеживает и регистрирует положительные и нежелательные эффекты. В зависимости от этого он увеличивает или уменьшает дозы, добавляет или исключает компоненты списка назначенных препаратов. Если цель лечения достигнута, при хронических заболева- ниях, требующих непрерывного контроля состояния, назначается поддерживающая терапия. Описанная схема позволяет утверждать, что в ходе решения задач диагностики, ле- чения и динамического наблюдения каждого конкретного пациента врачу приходит- ся иметь дело с огромным и нечетко очерченным пространством проблем и призна- ков. При этом важно подчеркнуть, что: □ пространство содержит избыточное количество признаков, которое каждый док- тор пытается уменьшить до ’’минимально необходимого и достаточного"; □ пространство весьма динамично, структура его меняется во времени как за счет "естественного течения" болезни, присоединения новых симптомов и осложне- ний, так и за счет установления верного диагноза и эффекта лечебных меро- приятий; □ пространство не имеет четко очерченных границ, поскольку один и тот же при- знак может породить несколько различных диагностических гипотез, относящих- ся к различным проблемным областям, что, в свою очередь, порождает необхо- димость расширения зоны поиска других признаков. Однако квалифицированный специалист с успехом решает проблему навигации по пространству признаков эвристически "в режиме реального времени". Описание клинической картины заболевания Клиническая информация представлена в соответствующем домене БЗ программы в виде дерева, содержащего, по возможности, полный спектр характеристик различ- ных проявлений множества заболеваний. Эта база способна динамически модифи- цировать свою структуру в зависимости от развития диалога, что позволяет пользо- вателю, описывающему клиническую картину заболевания конкретного пациента, выбирать необходимые термины, характеризующие симптомы и диагнозы из предва- рительно загруженного иерархически организованного домена. На верхнем уровне пациент характеризуется паспортными данными, анамнезом жизни, факторами риска, что актуально для любой патологии. Клинические данные
932 Часть VII. Приложения формируются набором признаков, относящихся к области кардиологии, пульмоно- логии, неврологии и т. д. Находясь в пространстве клинической БЗ, пользователь мо- жет выбрать необходимый в конкретном случае путь, при этом возможно пошаговое перемещение по какой-либо ветви дерева или прямое обращение к необходимой его части с помощью указателя. Пользователь-врач данной системы может также воз- вращаться к корневым ее элементам и далее проходить по другому пути, описывая соответствующую область с мельчайшими деталями. Варианты диалога с программой, "подсказка” альтернативных терминов и конкури- рующих решений способствуют более полному анализу клинических данных. Врач может закончить диалог на любом этапе, если считает, что он осуществил полное "минимально необходимое и достаточное” описание клинической картины заболева- ния. В этом смысле работа с программой имитирует повседневную интеллектуаль- ную деятельность врача у постели больного. Клиническая фармакология и лекарственные средства Правильно поставленный диагноз является первым этапом ведения больного. На втором этапе необходимо назначить ему адекватное лечение. Поиск наилучшей те- рапевтической программы осуществляется путем выбора возможного варианта из нескольких альтернативных, на основе сопоставления положительных и возможных побочных эффектов. Применительно к выбору медикаментозной терапии эти сооб- ражения можно проиллюстрировать следующим образом. Специалист мысленно следует по оглавлению справочника лекарственных средств, организованного по диагностическим категориям. Из классификатора средств, при- меняемых для лечения данного заболевания, он выбирает класс, группу (если имеет- ся, подгруппу) и собственно препарат, наилучщим образом способный, по его мне- нию, улучшить состояние пациента. Большинство зарегистрированных торговых названий препарата представляют собой лишь разновидность действующей субстан- ции. Клиническая фармакология препарата практически определяется химическим составом лекарства, лишь фармакокинетика препарата может зависеть от формы выпуска. Описание клинической фармакологии лекарственных средств, использованное в БЗ, имеет следующие особенности: □ представление о свойствах каждого торгового наименования препарата соответст- вует описанию клинической фармакологии действующего вещества; □ использование идентичных шкал, оценивающих степень отклонения от оптимума важнейших функций организма больного и силы корригирующего влияния сред- них терапевтических доз препаратов; □ описание свойств препаратов в формализованном виде может сочетаться с пред- ставлением аннотаций в традиционном текстовом виде. Верхний уровень фармакологической БЗ организован так, чтобы помочь пользовате- лю ориентироваться в классификации лекарственных средств, применяемых для ле- чения заболеваний, объединенных в соответствующие диагностические категории. При этом предъявляется полный список классов препаратов, применяемых в этом случае.
Приложение 3. Medication Assistant — медицина, основанная на доказательствах 933 Собственно описание свойств препаратов представлено в виде двух типов таблиц: □ данные по клинической фармакологии; □ информация о лекарственных формах, дозировках и способах назначения. Как уже упоминалось ранее, главной особенностью медицинской концепции явля- ется использование сопоставимых понятий и шкал для описания дисфункций орга- низма больного и эффектов лекарственных препаратов. Это позволяет включить полный список клинических воздействий, показаний, противопоказаний и ограни- чений, а также предусмотреть возможные побочные реакции на терапию данным препаратом. Поскольку большинство фармакологических эффектов зависит от дозы, важнейшей особенностью представления данных в БЗ является оценка силы эффекта препарата. Она проводится экспертом на основании его клинического опыта и данных литера- туры и нормируется по отношению к средней терапевтической дозе данного препа- рата, используемой на практике. Эти оценки могут модифицироваться в БЗ в про- цессе работы с системой функционирования без участия программиста. При одновременном назначении нескольких препаратов возникает риск несовмес- тимости лекарственных средств, а также назначение лекарств со сходными эффек- тами. Для предотвращения этого БЗ содержит специальные пометки о несовмести- мых и нерациональных комбинациях препаратов. В разделе информации о лекарственных формах, дозировках и способах назначения имеются специальные указания о верхнем пределе разрешенных дневных дозировок препаратов. Это позволяет проводить текущий контроль рекомендаций врача и пре- дупреждать о превышении дозы. Процедура назначения терапии При использовании изложенных принципов описания клинической фармакологии лекарственных средств врач может просмотреть всю информацию о клинической фармакологии препаратов, сравнить между собой активность различных классов и групп лекарств в форме таблиц, а также просмотреть аннотацию в текстовом виде (по требованию). Он должен быть обеспечен сервисом, предоставляющим полный список регистрированных торговых названий препаратов, форм выпуска, возмож- ность назначить индивидуальную дозировку и дать инструкцию по приему препара- та, включив его в лист назначений. При включении второго и последующих препаратов автоматически проверяется со- вместимость и рациональность одновременного применения каждой пары из списка назначенных средств и выдается предупреждение о несовместимости. Врач должен иметь право выбора и возможность отмены уже прописанного средства с заменой его на вновь назначенное. Лист назначений допускает возможность включения не- ограниченного количества совместимых лекарственных средств. Целесообразность такой комбинации может быть оценена с помощью процедуры моделирования дей- ствия комбинированной лекарственной терапии. Эта процедура является центральным звеном концепции проекта. По окончании заполнения листа назначений программа должна суммировать описанные в БЗ эф- фекты препаратов с поправкой на рекомендованные врачом дозы для лечения дан-
934 Часть VII. Приложения ного пациента. Список возможного воздействия на организм больного будет содер- жать все описанные воздействия назначенных лекарственных средств. Программа должна осуществлять сравнение эффектов предложенной терапии и опи- сания клинической картины заболевания в базе пациента (БП). Эта процедура по- зволяет выявить те элементы состояния больного, которые не описаны в истории болезни, но могут явиться противопоказанием к лечению, привлечь внимание леча- щего врача к существенным при назначении предложенной терапии клиническим признакам и учесть их. После пополнения БП необходимыми сведениями программа способна в единой шкале описать текущее состояние пациента, прогнозируемые на фоне терапии из- менения, предупредить о нежелательных эффектах и предложить пути их предот- вращения. Реализация этой задачи — центральная проблема концепции. Регистрация данных в истории болезни Врач нуждается в создании текстов истории болезни и других медицинских доку- ментов для регистрации клинических данных, диагноза, плана лечения, отражения динамики состояния пациента и регистрации результатов лечения. Медицинские документы необходимы также администрации медицинских учреждений и страхо- вым компаниям для контроля качества оказанной медицинской помощи, а самим врачам — для передачи данных о больном своим коллегам. Особенностью записей в истории болезни является необходимость регистрировать не только имеющиеся на больного данные, но и отмечать отсутствие определенных симптомов, что может быть актуальным для подтверждения правильности диагноза и отсутствия побочных эффектов лечения. Функция генерации текстов программы должна учитывать высказанные положения и удовлетворять требованиям, предъявляемым к медицинской документации. Архитектура МЭСПР Ядром программы является концепция структуры и обработки БЗ. Физически БЗ представлена набором фреймов, которые обрабатываются специальным фреймовым механизмом. Этот механизм базируется на стандартном инструментарии языка про- граммирования Visual Prolog. Архитектура программы упрощенно представлена на рис. П3.1. Программа взаимодействует с двумя видами БЗ и базой данных (БД): □ клиническая БЗ; □ фармакологическая БЗ; □ БД пациента. Все базы используют естественный язык. Клиническая база хранит в себе заранее заданные декларативные и процедурные знания, относящиеся к медицинской проблемной области. Она содержит клиниче- ские признаки и диагностические термины, объединенные в логические блоки, со- ответствующие основным врачебным специальностям.
Приложение 3. Medication Assistant — медицина, основанная на доказательствах 935 Рис. П3.1. Архитектура программы МЭСПР Фармакологическая база содержит списки групп лекарственных препаратов, систе- матизированных по диагностическим категориям. Главным ее свойством является специальная характеристика воздействия различных лекарственных препаратов на те или иные клинические показатели и функции человеческого организма. Это описа- ние в дальнейшем используется для моделирования эффекта лечения комбинацией лекарственных средств и предсказания реакции организма больного на назначаемую терапию. Кроме того, в базе отмечается несовместимость и нерациональность одно- временного применения различных препаратов. База данных пациента содержит информацию, относящуюся к конкретному больно- му, и представляет собой иерархически организованную сеть клинических призна- ков и диагностических терминов, а также блок, содержащий информацию о сделан- ных врачом лекарственных назначениях. База данных пациента в процессе работы активно взаимодействует с клинической и фармакологической базами знаний, ото- бражая активизированные врачом-пользователем элементы этих баз. Процедура генерации текстов позволяет получить грамматически и стилистически корректные медицинские документы. Она базируется на макрогенераторе текстов и лингвистически зависима от шаблона. Макрогенератор представляет собой контекстно-независимую программу, связанную с фреймовым механизмом. Фреймовый механизм обеспечивает выполнение всего комплекса задач, решаемых программой.
936 Часть VII. Приложения Экранное отображение различных функций программы реализуется с помощью пользовательского интерфейса. Технология работы с МЭСПР Важной частью технологии является проектирование медицинским экспертом — квалифицированным медиком, знающим принципы построения и функционирова- ния программы, базы знаний и шаблонов. Технология использует специальное приложение, фреймовый редактор, который яв- ляется частью фреймового механизма. При помощи этого инструментария медицинский эксперт последовательно выстраи- вает клиническую БЗ в виде иерархической сети фреймов, включая в нее все необ- ходимые клинические понятия и их формальные значения. На этой стадии меди- цинский эксперт должен предвидеть возможные направления диалога врача- пользователя с системой и варианты врачебных диагностических решений. Кроме того, уже на этом этапе экспертом должны быть внесены клинические признаки возможных противопоказаний и ограничений лекарственной терапии. В фармакологической БЗ медицинский эксперт организует иерархию групп и под- групп лекарственных препаратов в соответствующих диагностических категориях. Главной задачей эксперта на этом этапе является ввод данных о клинической фар- макологии лекарств, который выполняется в специальном формате. Клиническая и фармакологическая БЗ имеют различную структуру и представляют собой различные домены. Каждый их раздел может заполняться автономно. При условии корректного выполнения экспертом правил заполнения БЗ, их содер- жание может быть автоматически отражено интерфейсом пользователя и не требует специальной настройки. Эта особенность является одним из главных преимуществ СПРВ. Важной задачей программы являстся-прсдотврашсние недооценки клинических при- знаков с учетом возможных противопоказаний и побочных эффектов терапии. Для этого после выполнения процедуры назначения лечения программа сравнивает описание клинических признаков, содержащихся в базе данных пациента, и анали- зирует фармакодинамику (взаимовлияние) предложенных ему лекарственных средств и доз в назначенной комбинации. С помощью такого приема выявляются все необходимые клинические факторы, на которые может повлиять предложенное лечение, в т. ч. и те, которые не внесены пользователем в базу данных пациента. Если хотя бы один из выявленных элементов останется незаполненным, процедура прогнозирования эффекта терапии не реализу- ется. Эта контекстно-независимая процедура сопоставляет формальные значения клини- ческих признаков и фармакологических эффектов и выявляет степень отклонения клинических параметров, описывающих состояние пациента на фоне предложенной терапии, от оптимальных. Степень отклонения интерпретируется конкретно в зави- симости от содержания того или другого параметра путем вызова специального со- общения, подготовленного экспертом. Содержание сгенерированного текста истории болезни определяется объемом кли- нических данных, зарегистрированных пользователем в базе пациента. Подпрограм-
Приложение 3. Medication Assistant — медицина, основанная на доказательствах 937 ма макрогенератор, применяемая для построения текстов, поочередно обращается к фреймам, используя шаблон соответствующего раздела документа. Таким образом, форма и содержание персонального медицинского досье зависит от структуры шаб- лона, взаимодействующего с содержанием элементов базы данных пациента. При отсутствии в индивидуальной базе ссылок на какие-либо сведения программа опус- кает соответствующий раздел, а при наличии подключенных разветвлений дерева происходит пополнение текста истории болезни необходимыми деталями. Этим обеспечивается гибкость процедуры генерации. Создание и отладка шаблона могут быть реализованы с помощью любого текстового редактора. Следует еще раз отметить, что большинство процедур программы контекстно- независимы. Разработчик программных средств реализует весь комплекс задач (эк- ранное представление содержания фреймовых БЗ, работу с базой пациента, правила обработки данных и их отображения), заданных экспертом. Эксперт, моделируя варианты диалога пользователя, работает с редактором фреймо- вых баз, обеспечивая заполнение и расширение клинической и фармакологической БЗ без вмешательства программиста. Участие разработчика на этапе развития проекта необходимо только для добавления новых функций системы. Следует отметить, что большинство процедур, заложенных в данную версию программы, удовлетворяют основным запросам лечащего врача. Таким образом, архитектура всего проекта, представляющая собой открытую систе- му, предоставляет возможность реализовать концепцию поддержки решений врача при медикаментозном лечении различных больных с сочетанной патологией. Даль- нейшее развитие проекта обеспечит возможность поддержки не только врачей- кардиологов, но и специалистов в других областях медицины. Основными особенностями архитектуры и технологии программы являются: □ ведение БЗ реализуется медицинским экспертом без или при минимальном уча- стии программиста; □ БЗ разбиты на отдельные модули, которые могут редактироваться независимо, что дает возможность одновременного участия в работе многих экспертов, и при этом каждый из них может обеспечивать ввод собственной части клинической или фармакологической информации; □ наличие многоязыковой поддержки интерфейса пользователя при редактирова- нии БЗ и создании шаблонов. Технология программирования, примененная в проекте, позволила реализовать мно- гоязыковую поддержку СПРВ (английский, русский, норвежский и другие языки). В настоящий момент программа работает в русской и английской версиях интер- фейса, баз знаний и баз данных пациента. Язык интерфейса определяется при проектировании программы. Язык баз знаний обусловлен языком, использованным экспертом при их создании. Для перехода к приложениям на других языках каждая база знаний, а также шаблоны должны пере- водиться специалистом — носителем языка. Возможность выбора языка интерфейса предоставляется при первом запуске про- граммы, Пользователь выбирает также язык заполнения индивидуальной истории болезни, активизируя соответствующий пункт меню Опции. Это действие подключа- ет нужную базу знаний, формирующую базу данных пациента.
938 Часть VI/. Приложения Функции прототипа программы (MedAssist) Прототип может использоваться в качестве загружаемой программы изолированно или в локальной сети. Проект реализован на языке программирования Visual Prolog (версия 5.1). Интерфейс пользователя Структура интерфейса пользователя показана на рис. П3.2. Экран состоит из четы- рех секторов. Стрелки на рисунке показывают возможность изменения размеров секторов. Пример реального интерфейса приведен на рис. ПЗ.З. Главное меню Панель инструментов Сектор отображения пути Сектор редактирования свойств Сектор лекарственной терапии Сектор просмотра Рис. П3.2. Структура интерфейса пользователя MedAstiti - Папка 1Пронин М И) - Баэа|Пронин М И.ГКЬ) Файл Редактор Сервис Записи Опции Помощь Путь Возврат Паспортные данные Клиника Паспортные данные Лекарства ;• Убрать Номер истории болезни,........! 462 Отделение..,.;.. Время осмотра Ф.И.О. врача..........................Алексам дрова В.И фамилия И.О. больного.........Пронин М И Дата рождения.......................14 марта 1956 г. Возраст................................... 45 Пол t I ...кардиологическое Адрес . 1. мужской ..Невский пр. 22, кв. 17 Справка I Рецепт Клиника М Только чтение } |>) Открыть ’р) Править Рис. ПЗ.З. Пример реального интерфейса пользователя
Приложение 3. Medication Assistant — медицина, основанная на доказательствах 939 Главное меню и панель инструментов Главное меню позволяет выбрать основную подзадачу (Клиника, Терапия или Про- гноз эффекта), из него можно вызвать генерацию текста, редактирование и т. п. ♦ Панель инструментов дублирует основные подзадачи системы. Сектор отображения пути В упрощенном виде организация данных может быть представлена в виде дерева. Опция Сектор отображения пути позволяет просмотреть структуру конкретной части этого дерева. При регистрации клинической информации о пациенте структура де- рева динамична. Сначала она содержит только корневой элемент и несколько глав- ных, примыкающих к корню узлов. Затем в процессе диалога с системой в соответ- ствии с логикой пользователя дерево разрастается. Упрощенная схема дерева пред- ставлена на рис. П3.4. Рис. <13.4. Схема дерева организации данных В каждый конкретный момент времени пользователь рассматривает и модифицирует состояние только одного узла. Содержание этого узла представлено в Секторе редак- тирования свойств (правая верхняя часть экрана). Список узлов, отражающий путь от корня до текущего (обрабатываемого) узла — это Текущий путь. Так, например, если обрабатывается узел 5, путь к нему представлен списком узлов: 1, 3, 5. Именно этот, текущий путь будет отражен в Секторе отображения пути. Сектор редактирования свойств Содержание редактируемого узла представлено в Секторе редактирования свойств. Врач, применяющий систему СПРВ на практике, может заполнять базу данных па- циента, выбирая нужный термин из предоставленного ему списка, или вносить не- обходимое значение (слова или цифры) самостоятельно. В зависимости от выбран- ного значения, диалог может получать различное развитие путем подключения дру- гих узлов (варианты возможных путей заранее продуманы экспертом и введены в БЗ). Данные по клинической фармакологии лекарственных средств в Секторе редактиро- вания свойств, если к ним обращается пользователь, представлены только в режиме
940 Часть VII. Приложения Просмотр. Врач может оценить свойства выбранного им лекарственного препарата или подгруппы лекарств, представленные в фармакологической БЗ, которая для него имеет статический характер. Описание клинической фармакологии препаратов, раз- деленных по соответствующим диагностическим категориям, классам и подклассам, может проводиться только экспертом. Сектор просмотра Во время текущего редактирования содержания некоторых узлов может возникнуть необходимость просмотра ранее активизированных и частично заполненных элемен- тов дерева. Выбрав любое имя из списка, содержащегося в секторе пути, пользова- тель может оценить содержание ранее заполненных узлов — Таблиц свойств. Редак- тирование данных в этой зоне невозможно. Сектор просмотра может быть минимизирован или восстановлен по желанию поль- зователя. Сектор лекарственной терапии В этом секторе содержится список лекарственных препаратов, прописанных данно- му пациенту в текущий момент. Выделив необходимое название, врач-пользователь может получить информацию о препарате в текстовом виде (аннотацию) или от- крыть Окно рецепта для прописывания конкретной дозировки, выбора формы на- значения и инструкции по приему препарата. Поддержка основных элементов работы врача Активизируя эту опцию, вы обращаетесь к структуре истории болезни, подготовлен- ной для внесения необходимой клинической информации. Главное окно реализации этой функции состоит из четырех секторов: Путь (левый верхний), Лекарства (левый нижний), Таблица свойств (правый верхний), Нередактируемый просмотр (правый нижний). Таблица свойств состоит из строк. Каждая строка состоит из заголовка (слева) и ак- тивной части (справа). После запуска опции Клиника (пункт меню Сервис) можно заносить клинические данные в активную часть любой строки таблицы свойств. На рис. П3.5 представлен образец заполнения Таблицы свойств. Список имен активизированных Таблиц свойств отражается в порядке очереди в секторе Путь. А в секторе Нередактируемый просмотр отражена одна из неакти визи- рованных таблиц. При обращении к разделу Диагноз вы входите в классификатор болезней, организо- ванный по синдромальному и анатомическому принципам. Для того чтобы выбрать соответствующую нозологическую форму, нужно отметить необходимую группу в дереве классификатора. Остановившись на определенном диагнозе, вы можете отредактировать запись. В случае отсутствия подходящей рубрики вы можете сфор- мулировать диагноз самостоятельно в строке Иной. При генерации текста истории болезни все активизированные вами диагнозы, в т. ч. и относящиеся к различным медицинским проблемам, будут сведены в единый список.
Приложение 3. Medication Assistant — медицина, основанная на доказательствах 941 Is MedAssist - Папка [Пронин М И[ - База [Пронин М H.fkb] ФайлРедактор ; Сервис ; Записи Опции : Помощь i |aW* ;S Г*® ,я- .Путь Возврат ш ‘л ViTrVR дммДмЕаамд Сердечно-сосудистая система ймЙймМй Клиника Пульс - частота... Клинические проявления Данные физикального обследовг Сердечно-сосудистая системе P HTM^f J * r itni.n4n*»»k».t*t hit-л*^, к*Н1**> r Rk »• :? =>. ?!"<•_.. ' $c \ '< x ? ; \ й двйо лнйге дьные характе рн ст и ки .- ^<5X 7 ; С : - j: < ' ' ' 1' ;'^|^еТрйч^4Й^^^ьс»<<х ':-\ ; / - ? '-Ь- г v : ;17 Л Систо дическо е АД ' ' ’s\:,s *’г'''1 i':,-1 ДиастолнческоеДД Щй; м'£к?\- zc-. • ь. Пуа^ацвя лерйфё^ичесюрс сосудов Патояогически епул ьсаци и.... ,г. ....а...... .. Границы ртаосительнои сердечной тупости, 1 Г|га йм цыаёсЬ л ютиой сердечйой?тупрстй 7=^ •':> ::5- V ' "’': :L: . м*м^н*лм*«м>мтмимм1Ммй*АЯИ1МммйМм«Ммч1Ажммм*чй*мм4^^ ..»»,6. (*) 88 в мин ....•'|-iiLriLi~m,ii’rariimftiiM*aiMiiifi,i k i г umii Г Tnri iriVi j 1. регулярный г] 1. регулярный 2. (*| зллоритмия 3. (*| неправильный 4. И иной * {*) 106 не нарушена в пределах нормы Клинмческиепрояолення ? { То лыко чтение ] Справка ..,>•» Рецепт, J 11 !• АД'. А 1 ЦП*1 r,‘ 1 п “ 1 Л <г.У|‘' и j :i:uii: .. । "i । , J.i । U ,j : : wy* Г w* « w ** । |« 11 HJ.1 tf. I ll. Ill I I » wth.i Ml Анамнез 4*$ £iipi » « « * 4 4** *•» » *• * r 4 к • •• * n • i4« 4^4 • * •*•*’?** * ** Открыть Л О > 4 «-:* « •'« , 4*4* ^4* Jt ** 4»*я 4*414 * »i * ; ***** * ** t» • * >*•***** it * **"Открыть . ^ ! .' ।! • : r,,; rЛ’’''” -: i: - " л*.- я\. V, c^. - :>' . . _ ’.s-J-rtz’. -:.'л j< ’% sx£-- Данные физикального обследования....,......Открыть ^Обслй ДО®®к* **V* • ** »'•* k »**k л k* 4$<Ц. Открыть * Z-'З- Six's ' Ji. ' SA .0 ', , » -11/’ j_iJJ i~i c >i i *C*-.'<-. fc;%• C ';•:• г' *tt .;.•.. .• jp<sЛ&-: 111 S'l.tteeWib*i4c k 4»44*;к <«»*«• 6 к I 1 Рис. П3.5. Образец заполнения таблицы свойств Принцип открытой архитектуры позволяет развивать диагностическую ветвь клини- ческой БЗ с учетом требований любого медицинского специалиста без дублирования информации. На рис. П3.6 представлен пример использования классификатора для выбора необ- ходимого диагноза. Приведен пример рубрикатора почечных и вазоренальных заболеваний. Выбран ди- агноз: Фибромускулярная дисплазия почечной артерии. Путь, отраженный в левом верхнем секторе, показывает, что к этому диагнозу вы пришли, активизируя разделы сердечно-сосудистых заболеваний, через классификатор симптоматических артери- альных гипертензий, а именно реноваскулярных заболеваний. После выполнения задачи описания клинической картины заболевания и постановки диагноза возмо- жен переход к опции Терапия (пункт меню Сервис) и генерация Истории болезни (пункт меню Записи). Процесс назначения лекарственной терапии с помощью программы методически не отличается от обычной практики врача. Использование программы на практике мо- жет существенным образом способствовать предотвращению ошибок, связанных с неправильной дозировкой или несовместимостью лекарственных препаратов. Кроме того, программа снабжена сведениями о "нерациональных комбинациях" лекарст- венных средств, применение которых допустимо, но не оправдано. Облегчая процесс назначения препаратов, программа обладает уникальными свойст- вами контроля возможных противопоказаний и предупреждения о побочных эффек- тах сочетанной терапии у конкретного больного со сложной патологией (прогнози- рование эффекта).
942 Часть VII. Приложения Клиника Клинические проявления Диагноз Ссраечно сосупистыс синдромы Поражения почечных артерии..,....................|з Поражения клубочкового аппарата•••к*»•***•*»• [*) Дтеросклеротич ...| Поражения канальцевогоаппарата........ Артериальная гипертензия Почечные заболевания Инфекции мочевывоцящих пуген................. Лекарства; д/ Чбрать МКБ и обструкции мочевыводящих путей.... Опухоли мочеаыделительной системы......... Почечная недостаточность.................. Другие.............. Редактор . $ атеросклеротический стеноз правой ПА [?i ок £апсе! Й Справка | < Рецепт 1--------;----—’---------:---------------- II»» I |ГП ЙИI». Г1|11Й1»'т-| II..I.||»||| IW Illi*** ' 1ГПТГ -| » » Ч IИ г >ГГ > VI III III Г, Ли,! <|-| >4.И». »Ч1»*** <»»« Ilr**'i'i О » »П **>» «» ч»ш...у» lit hl I- TiTi ii'll I I ГГГ1 I I- IЦ1Й |Ч|'ГГ Сердечно-сосудистые синдромы - {Только чтение ] <(>) Открыть :(*] Править Рис. П3.6. Классификатор для выбора диагноза Главное окно реализации этой функции состоит из четырех секторов: Путь (левый верхний), Лекарства (левый нижний), Таблица свойств (правый верхний), Нередак- тируемый просмотр (правый нижний). После запуска опции Терапия (пункт меню Сервис) активизируется Таблица свойств. В ней отражены диагностические категории лекарственных препаратов. Для заполнения листа назначений Нужно выбрать группу и подгруппу препарата, пользуясь классификатором диагностических категорий, организованным по син- дромальному принципу. Выбрав наиболее подходящий препарат или подгруппу препаратов со сходными свойствами (например, селективные бета-адреноблокаторы), есть возможность бегло ознакомиться с характеристиками их клинической фармакологии, отраженными в Таблице свойств в режиме "только чтение". Затем открывается список Родовых (химических или действующих субстанций) на- званий лекарств, относящийся к каждой из подгрупп. После активизации одного из них будет представлено окно с собственными (фирменными) названиями препара- тов, формами выпуска, дозировкой и способами применения. В нем осуществляется назначение конкретной дозировки препарата с инструкцией по его применению. Для примера выберем группу бета-блокаторов как антигипертензивных препаратов. Перемещаясь по списку подгрупп, можно выбрать одну из них (рис. П3.7). Выбор подгруппы из списка осуществляется посредством клавиши <Стрелка вниз>. В этом случае в основной части таблицы будут отражены все возможные эффекты препаратов выбранной подгруппы на организм человека. Данные предназначены только для чтения.
Приложение 3. Medication Assistant — медицина, основанная на доказательствах 943 tai MedAxsist - Папка (Пронин МИ} - База [Пронин М M.fkb] TBTxl Файл Редактор Сервис Записи ОлцийПо^-ющь ? м<|>Мм«й«*мн* 4^**4й* Мед. терапия Возврат! Антигипертензивные средства маммйшм <=>Назад шшшмжмшммам Сердечнососудистая система Антигипертензивные средства Лекарства Чбрать Цнтральные снмпатолнтнкн........ Альфа-блокаторы,........................ Бета-б ло катер ы........................... Блокаторы кальциевых каналов Вазодилататоры.......................... Мочегонные средства......;.......... Ингибиторы ПФ........................... „.^Открыть ...Открыть ....Открыть .....Открыть .....Открыть ...„Открыть ..^.Открыть Сердечно-сосудистая система - [ Только чтение ] * NMM*W*MflV*4b*********.vh*4* МЦММЙ1ЙЬ^МШЙ1 1й Справка ' Н-!“.. Рецепт Антигипертензивные средства........ Анти актинальные средства............ Гнполипидемические средства.....................Открыть Средства влияющие на систему сеертываниОткрыть ;: 3’> . ?з< <:./-—............................ Антиаритники.........................................Открыть 1>) Открыть ; П Править АА/А ......Открыть ......Открыть :М. шй Рис. П3.7. Назначение лечения: группы и подгруппы лекарств На рис. П3.8 отражены некоторые свойства неселективных бета-блокаторов. Список ранее назначенных препаратов приведен в секторе Лекарства. мз MedAssist- Папка [Пронин М И] - База [Пронин М И.fkb] ^^::?:R«aK^rf:.Cep»«r^:3arwqi^0n^' Помощь мйммммммякнмнмм * ммммммйммЛь 4. " Путь ' Возврат Мед. терапия Сердечно-сосудистая система Антигипертензивные спедства Бетя-б локаторы м*ммшмкм*мммйм>ммшимйн1мж<ш«шнммймммм*мммммтчм«1ййя№ < Лекарства . • 9брать Verapamil Hydrochlo roth ia z I d е Enalapril Бета-блокаторы Неселективные бета-блокаторы Неселективные бета-блокаторы Запись I Назад^И .....отрицательное влияние ...случаи тромбоцитопении •!?- •<. Справка Рецепт Селективные бета-блокаторы ’ бета-блокаторы с ВСЭ Водорастворимые бета блокаторы Сократимость миокарда.....................отрицательное влияние Периферическое кровообращение .^.отрицательное влияние Нервная система.................................нарушения сна Дыхательная система...... Система крови................... ЛНгКС МЙЯ.« *•>%***:* ••••*-*:•-« * * » »** »*• » >....может маскировать гипогликемию Функция щитовидной железы.......... маскируст симптомы гипертиреоза Желудочно-кишечный тракт..............легкий отрицательный эффект МШМЧ1! ^*>4***'*< Illi JllftOft 1~1****|МЧЧЧ.'1 П1111 | Гц tO'l 11 **^«<И1ЙМ**—****Ч ЙИ ч ' Бета-блокаторы -г : . ';- А'. • ?.А-! П| (>) Открыть (Г). Править ' Рис. П3.8. Эффекты от препарата выбранной подгруппы на организм человека
944 Часть VII. Приложения Рис. П3.9. Свойства выбранного лекарства в окне справки В секторе Таблицы свойств имеется кнопка Применить. После нажатия этой кнопки появится список всех родовых названий препаратов, относящихся к выбранной под- группе. Выделив один из них, можно просмотреть его описание в текстовом режиме при помощи кнопки Справка (рис. П3.9). В случае выбора несовместимых препаратов или при наличии данных о нерацио- нальной комбинации с ранее назначенными лекарствами, в секторе Лекарства по- явится окно с предупреждением и соответствующими списками (рис. П3.10). При подтверждении назначения посредством нажатия кнопки ОК в случае несо- вместимости препаратов (но не при комбинациях, признанных только нерациональ- ными) ранее назначенные несовместимые препараты будут удалены из списка Лекарства. Далее появится окно Назначения. Используя соответствующие кнопки, можно выбрать Собственное (фирменное) на- звание лекарства, форму выпуска, дозировку и инструкцию по применению. Про- грамма позволяет также добавлять дополнительные данные (рис. П3.11). После назначения суточной дозы конкретного препарата, при подтверждении кноп- кой ОК, программа автоматически сравнивает ее с верхним пределом разрешенной среднесуточной дозы. В случае передозировки возникнет двойное предупреждение об опасности применения лекарства в указанной дозировке. Вы можете подтвердить это назначение или изменить его. Только после выполнения данной процедуры пре- парат, доза и способ применения будут добавлены в лист назначений. При назначении каждого последующего препарата процедура повторяется. Если вы считаете лист назначений заполненным, можно воспользоваться процедурой Про- гнозирование эффекта терапии (команда Сравнение эффектов в меню Сервис).
Приложение 3. Medication Assistant — медицина, основанная на доказательствах 945 <4i я,’ j Ч*МЙ*ЬМчЬй4; М£ММ*и4м1мМмМЫ|й*ммймй>лйШ|М4т4Й|*Шйй«1мамМа4мт*1ММ^^ ИИЙкй Мед.терапия Сердечно-сосудистая система Антигипертензивные средства Л Бета-блокаторы Неселектновые бетаб локаторы 1 irKin.wurjuusjuuMu^j^aaB^w^niy unw—mumi uytumwinM Nadolol J8B..... ВЗШись |МЙЙМММИ1Мш Бета-блокаторы Propranololhydrochloride Sotalol Timolol maleate лЛекарстоа J. Nadolol Verapamil Hydrochlorothiazide г| Enalapril Verapamil 1 L '',J^ - <*.>.-- ’!: j . --и <'з' >"i S, ймйм ?^SiSF ^Verapamil Л. il^igelg 15 г Помощь J фВИТЬ Рис. П3.10. Окно с несовместимыми и нерациональными лекарствами -Z .о. -3 Nadolol «ммымыь ; Мед. терапия 4 > Сердечно-сосудистая сиси * Антигипертензивные средс|| Бета-блокаторы Неселектигшые бетабяокаторы 11 iin>\>4ri/,"r лц । .Ь1-Ь1*ГПГМ |ГЖ»^ППй1Г7.,ЛП(ППГ<^ГЛТЙУ^ПЛТа№СИЙЦП'П'[И1>*О!1МЖТ2Г-Гй~П Nadolol Kfe«®8itS^ ЙВйЙО >15: <^деЭ£1йй£1м^йймййамйм£ЁАиййвЬ||й«1пй^^ j (CORGARD fSWlO^SS^MSi Hydrochlorothiazide y||tabl.0.08 ::-ч ед J • /У- ... .!... .-^Jp у Wr утром однократно Enalapril глотать кс разжевывая мший BSr* Л|И| натощак 10.32 «Question <«* •1W we# жн faj^ *• e l$#i -<,1 Й111ЙН »S®8« И мМ |1Й’й*мм<!тй^ ««wepi W tottaSi № £ ^•**«r«*!jiM*WM»W*«M*A***^*»*****i*itaii*i^ ? I Я5Ж»вка^Я| Ж £ к* Strife ЫИйГ :' '>\'!::Ч !*' Т . х - & 1| X И । т>р(м^pjjffi о й & • - ЙЖШтШи Рис. П3.11. Окно назначения суточной дозы лекарства
946 Часть VII. Приложения (Замечание J Если лист назначений пуст, прогнозирование эффекта терапии не может быть вы- полнено. Опция Прогнозирование эффекта терапии вызывается посредством активизации пункта Сравнение эффектов (меню Сервис). Эта функция может быть вызвана после назначения лечения, включающего один или несколько препаратов. Программа спо- собна прогнозировать влияние комплексной терапии на организм человека и пред- сказать положительные и отрицательные эффекты. Компьютер сопоставляет фор- мальные значения степени различных клинических проявлений и силы фармаколо- гических эффектов комбинаций лекарственных препаратов. Поскольку регистрация проявлений заболевания и назначение лекарственной тера- пии проходят независимо, врач может недооценить ряд существенных противопока- заний и ограничений применения лекарственных средств. Чтобы избежать этого, программа проводит сравнение полного списка фармакологических эффектов назна- ченной комбинированной терапии и списка клинических данных, занесенных в личную базу данных пациента в режиме Клиника. Сравнивая терапевтические эф- фекты и зарегистрированные клинические данные, программа выводит на монитор полный список, содержащий все существенные признаки заболевания и физиологи- ческие параметры, на которые может повлиять предлагаемая терапия. Рис. П3.12. Эффект предлагаемой терапии На рис. П3.12 демонстрируется подобный список. Его правая часть содержит харак- теристику клинических признаков, перечисленных в левой части таблицы. Следует подчеркнуть, что список содержит только актуальные для оценки эффекта предла- гаемой терапии факторы. Некоторые поля могут оказаться незаполненными. Для продолжения процедуры оценки эффективности терапии и предсказания возможных
Приложение 3. Medication Assistant — медицина, основанная на доказательствах 947 негативных реакций все пустые поля нужно дополнить необходимой информацией, иначе выполнение процедуры прогнозирования эффекта терапии невозможно. Текст предупреждения содержит список незаполненных элементов базы. На рис. П3.13 показан процесс ввода необходимых сведений. При этом обращаясь к пустой строке, данные можно выбирать из предлагаемого меню. Описание клиниче- ских проявлений может быть углублено за счет автоматического подключения соот- ветствующего раздела базы данных пациента. MedAssist - Папка [Пронин М И] - База [Пронин М И.1кЬ| IB!*] Файл Редактор Сервис Записи Огщии Помощь Возврат Сердечно-сосудистые синдромы О Назад I. Сердечно-сосудистые сннд ' Лекарства Нбрать Hydrochlorothiazide Enalapril______ Артериальная гипертензия Б о л и в груд но й к л е тке...... Сердечный ритм..... Недостаточность кровообращения...Открыть .............2, (>] Вторичная гипертензия ; 2. (>) ОКН т Открыть Nadolol Артериальное давление....... Анти а н ги н ал ьн ы й э ф ф ект..... AV блокада и брадикардии.. SV тахикардии Сократимость миокарда.. Периферическое кровообращение...2. {>) имеются нарушения Сум. эф. ; 6. [*) 106 2. [>1 ОКН ЯП: .3. [>) другие брадикардии или блокаде .1 нет 1. ФК 0.~....•7“” Cancel Помощь йийЫммма» Рецепт Сердечно-сосудистые синдромы - (Только чтение } Л Справка [>) „Открыть" : 7^ ДВДЙНТЬ ^ •• 4“.Ч-.Ч-.% П 11 .Ч1 hМ• .’i'I-Л-.-л-ЧЧ ।• I•• ЬЧ‘ V "ГЧ"» Ч• • ЧЧ»1 * "“^ЧП^4 * > Ч^чч^^л-^л-А-лЧ?ЧЧЧ4<$£Ч'к•• S44v А$иЧ>ЧЧ"чччччч^. .*• MW4 Рис. П3.13. Заполнение базы данных На рис. П3.13 показан пример обращения к описанию нарушений периферического кровообращения. В искусственно созданной ситуации, когда выполняются назначения больному при незаполненной "истории болезни", программа выведет список всех клинических синдромов и показателей, на которые может повлиять предложенная терапия. Если все необходимые клинические данные наличествуют в базе данных пациента, кнопкой Сум. эф. (Суммарный эффект) вызывается соответствующее окно (рис. П3.14). Программа выводит на монитор список всех состояний, синдромов и показателей (артериальная гипертония, боли в груди, уровень гликемии, наличие и степень по- чечной недостаточности и т. д.), характеризующих клинические проявления заболе- вания в данный момент в терминах отклонения от оптимума. Кроме того, процедура демонстрирует прогнозируемое состояние соответствующей функции в результате
948 Часть VII. Приложения применения предлагаемого лечения комбинацией препаратов в назначенных вра- чом дозах. (Ч 106 MNWM» 2 (>} О КН Задействованные клинические даные йммимйййййй 4' Ч- ; -.’-7,7.777:’-;Г’ -7.. гЛ:: Путь • ЭДуИангайй.т^Й^Дфф# jfcj : Клиника u n f АШмнзда ибр а д и ка р д и и... Клинические (>) другие брадикардии или блокады Сравнение эффектов ПХЙйДрОЙЫ<ЖИ:И|-|:^^1|Йу|^ц^ . / Артериальное давление "мягкая" АГ Антиангинальный эффект стенокардия 4 ФК || / AV блокада и брадикар. .1 / SV тахикардии || / Сократимость миокарда Il V Периферическое крово... умеренная брадикардия или ... нормальная ЧСС отсутствуют признаки недост... незначительное ухудшение выраженное снижение АД нет резкая брадикардия или риск АВ 6 выраженная брадикардия высокий риск нарастания НК значительное ухудшение / Нервная система отсутствие симптомов высокий риск выраженных проявл) Рис. П3.14. Клинические проявления заболевания Программа отмечает строки, в которых прогнозируются нежелательные эффекты специальной меткой, при активизации такой строки можно получить предупрежде- ние о возможных реакциях на терапию, способах контроля за состоянием данной функции и путях профилактики осложнений. В соответствии с этими предупреждениями врач может изменить список предлагае- мых препаратов, дозировку и на модели лечения оценить риск нежелательных эф- фектов. Так реализуется основная функция программы. Навигация по базе данных пациента Опция Навигация помогает передвигаться по иерархической сети Таблиц свойств разделов Клиника или Терапия в любом направлении. Вы можете просмотреть (в режиме "только чтение") состояние любой Таблицы свойств, содержащейся в ак- тивизированной базе данных пациента (рис. П3.15). К некоторым таблицам свойств можно выйти разными путями. В этом случае они отражаются в поле Список путей (расположенном между списком Таблицы свойств и полем Путь).
Приложение 3. Medication Assistant — медицина, основанная на доказательствах 949 Рис. П3.15. Навигация по базе данных пациента После выбора одного из вариантов пути его содержание появится в одноименном поле. Выделив одно из названий в Таблице свойств, вы можете: О при помощи кнопки Просмотр увидеть содержимое данной таблицы; □ при помощи кнопки ОК поместить данную таблицу в сектор Таблица свойств для работы с нею. Генерация медицинских документов Функция генерации медицинских документов в текстовом виде является дополни- тельной, облегчая повседневную деятельность врача. Генерация текстов позволяет автоматически создавать на основе базы данных паци- ента разнообразные лингвистически корректные медицинские документы: История болезни, Лист назначений и пр. Опции активизируются из меню Записи. Генерацию текстов рекомендуется производить после сеансов Клиника и Прогнози- рование эффекта терапии. История болезни представляет собой текст традиционной структуры, содержащий: заголовок, субъективные данные, историю болезни пациента, данные медицинского осмотра и диагноз. Лист назначений содержит названия, дозировки, способ применения и формы вы- пуска прописанных препаратов. Области применения Описанная программа является прототипом СПРВ или партнерской системы. Она может настраиваться на работу и узкоспециализированного врача, и общепракти- кующего врача. В существующей версии основной акцент сделан на решение задач диагностики и лечения сердечно-сосудистых заболеваний у больных с различной сопутствующей патологией. Замысел всего проекта, организация БЗ и использованная технология позволяют расширить возможности прототипа путем добавления новых доменов (специализи-
950 Часть VII. Приложения рованных разделов БЗ). Кроме того, дальнейшее развитие системы возможно за счет добавления новых функций. Основная концепция программы позволяет применять ее в: О клинической практике врачей любой специализации, при амбулаторном и ста- ционарном лечении больных; О химиотерапии опухолей; □ медицинском образовании; □ телемедицине; О медицинском страховании. Клиническая практика Клиническая практика является основной областью применения программы и слу- жит для практикующего врача надежным помощником, непредвзятым критиком и доброжелательным советчиком. Поскольку для людей пожилого возраста особенно характерно наличие нескольких патологических процессов, повышенная чувствительность к обычным терапевтиче- ским дозам лекарственных препаратов, риск развития побочных эффектов лечения, работа с СПРВ может быть особенно эффективна в гериатрической практике. Использование программы в распределенных сетях и встроенная функция логиче- ского контроля ввода данных открывают возможность применения программы для сбора информации в клинических объединениях. Привлекательной стороной при- менения СПРВ в этом случае является автоматическая генерация текста истории болезни, полностью дублирующая содержание базы данных, а также возможность преобразования данных для хранения и последующей обработки. Химиотерапия опухолей Данная задача является частным случаем оценки относительного риска естественно- го течения заболевания, опасности осложнений и возникновения побочных эффек- тов от активной лекарственной терапии больных с локальными или генерализован- ными опухолевыми процессами. Различные виды опухолей, их локализация, харак- тер фоновой патологии и многообразие видов химиотерапии делают актуальным применение программы к решению этих задач. Медицинское образование С помощью программы возможна передача знаний специалистов высокой квалифи- кации своим менее опытным коллегам. Кроме того, можно создавать и решать кли- нические задачи и примеры диагностики, а также лечения любых сложных и редких патологий, включая такие задачи в методики обучения студентов и в программы повышения квалификации. Обучение на примере реальных или моделированных клинических случаев позволит избежать повторения типичных врачебных ошибок.
Приложение 3. Medication Assistant — медицина, основанная на доказательствах 951 Телемедицина Поскольку понятие телемедицины предполагает передачу всего объема клинических данных, представляется, что реализация концепции должна предусматривать не только протоколы передачи результатов инструментальных исследований и средства телеконференций, но и актуальные системы логического контроля сбора данных, ориентированные на различные клинические проблемы. Медицинское страхование Для применения в области медицинского страхования могут быть использованы следующие функциональные свойства программы: О генерация текста истории болезни и других медицинских документов облегчает экспертизу качества оказания медицинской помощи в системе обязательного и добровольного медицинского страхования; О специальные функции позволяют автоматически проводить контроль соответствия объема и стоимости медицинского обслуживания пациента со сложной патологи- ей в соответствии с популярной в развитых странах концепцией "управляемого медицинского обслуживания" (Managed Care Approach).
Светлой памяти Сергея Юрьевича и Нины Борисовны Масловых посвящаю ПРИЛОЖЕНИЕ 4 В. П. Оревков. Обратный метод поиска вывода Задача поиска доказательства в дедуктивной системе (аксиоматической теории) ста- вится следующим образом. Пусть 91 — дедуктивная система. Это означает, что зада- ны язык для описания предложений системы, исходные истинные предложения (ак- сиомы) и правила получения новых истинных предложений из уже построенных предложений (правила вывода). Чаще дедуктивные системы строятся на базе исчис- ления предикатов. В этом случае язык системы задается списком символов функций и предикатов (сигнатурой системы). Предложениями системы являются замкнутые формулы исчисления предикатов, в которые могут входить только символы функций и предикатов из сигнатуры системы. Специфическими аксиомами системы — кон- кретные предложения системы. Логическими аксиомами и правилами вывода — ак- сиомы и правила вывода какого-нибудь варианта исчисления предикатов. Доказа- тельством предложения А в системе Л называется конечный список предложений 2?1, ..., Вп, который заканчивается предложением А и в котором каждое предло- жение является аксиомой или получается из предыдущих предложений с помощью одного из правил вывода. Предложение А называется выводимым (доказуемым) в сис- теме 91, если существует доказательство предложения А. Определение П4.1} Алгорифмом поиска доказательств в дедуктивной системе 91 называется любой алгорифм, который заканчивает работу над предложением системы 91 и выдает его доказательство, если оно выводимо, и в противном случае выдает слово НЕТ или не заканчивает работу. Разрешающим алгорифмом в дедуктивной системе 91 называется любой алгорифм, который заканчивает работу над любым предложением системы 91 и выдает слово ДА, если предложение выводимо, и НЕТ — в противном случае. ”6’ 1 Здесь и далее в авторской работе В. П. Оревкова используется термин алгорифм вместо алго- ритм. Эта особенность присуща Ленинградской/Санкт-Петербургской школе математической логики. — Ред.
Приложение 4. В. П. Оревков. Обратный метод поиска вывода 953 Построение разрешающего алгорифма для той или иной дедуктивной системы явля- ется важной задачей. К сожалению, для наиболее интересных систем разрешающий алгорифм невозможен (например, для исчисления предикатов). В этом случае задачу построения разрешающего алгорифма можно заменить следующими задачами: □ поиск разрешающего алгорифма не для всего класса предложений системы, а только для некоторого подкласса; □ построение алгорифма поиска доказательств, который над не выводимыми в сис- теме предложениями может работать бесконечно долго. Для любой дедуктивной системы существует тривиальный алгорифм поиска доказа- тельств в этой системе. Работает этот алгорифм следующим образом: перебираем, начиная с аксиом и при- меняя правила вывода, все доказуемые в рассматриваемой системе предложения и ждем, когда получим заданное предложение. Очевидно, что тривиальный алгорифм не может учитывать особенности заданного для поиска доказательства предложения и перебирает излишне много доказательств, которые никак не связаны с заданным предложением. Тривиальный алгорифм поиска доказательств иногда называют алгорифмом Британ- ского музея. Возможно, имеется в виду, что в Британском музее можно найти все (в том числе и доказательства в дедуктивных системах). Неэффективность тривиаль- ного алгорифма очевидна. Однако можно привести примеры, когда алгорифм Бри- танского музея оказывается эффективнее метода резолюций [61]. Общая схема обратного метода В алгорифме Британского музея поиск доказательств начинается от аксиом и ведет- ся сверху вниз. Можно организовать поиск доказательства, начиная от доказываемо- го предложения, чтобы учесть его особенности. При таком поиске снизу вверх по доказываемому предложению ищутся предложения, из которых можно его получить по одному из правил вывода. Затем по найденным предложениям в свою очередь ищутся предложения, из которых они могут быть получены, и т. д., пока не получим аксиомы. Традиционные варианты исчисления предикатов не позволяют организо- вать поиск снизу вверх достаточно эффективно. Генценовские секвенциальные исчисления без сечения обладают так называемым свойством подформульности, которое означает, что в доказательстве могут использо- ваться только формулы, близкие по своей логической структуре к подформулам до- казываемой формулы. Это обстоятельство позволяет достаточно легко организовать поиск доказательства в секвенциальном исчислении снизу вверх. В статье С. Кан- тера в сборнике [91] описан метод поиска снизу вверх для исчисления предикатов с равенством, использующий генценовское исчисление. Близкий к генценовскому исчислению метод семантических таблиц приведен в работе Э. Бета в том же сбор- нике. Метод поиска снизу вверх достаточно полно использует специфику доказываемой формулы, но в процессе поиска приходится обрабатывать очень громоздкую ’заго- товку" доказательства и, чтобы превратить ее в настоящее доказательство, необходи-
954 Часть V/1. Приложения мо обрабатывать ее целиком и согласовывать участки, расположенные далеко друг от друга. В силу этого эффективность алгорифмов, основанных на поиске снизу вверх, довольно низкая. В 1961 г. в Ленинградском отделении Математического института им. В. А. Стеклова (ЛОМИ) Н. А. Шанин организовал группу математической логики. Работа группы началась с поставленной Н. А. Шаниным задачи построения алгорифма, который выдавал бы "достаточно хороший" и "естественный" вывод (данного утверждения из данных аксиом). Первоначально рассматривался вывод в классическом исчислении высказываний, для которого исследования закончились разработкой алгорифма АЛПЕВ (Алгорифм Поиска Естественного Вывода). Поиск доказательства в АЛПЕВ был организован снизу вверх в некотором секвенциальном исчислении [82]. Для принципиально более трудной задачи поиска вывода в исчислении предикатов сначала также предполагалось использовать метод поиска снизу вверх в подходящем секвенциальном исчислении (такой метод в группе математической логики называл- ся прямым методом). Осенью 1964 года С. Ю. Маслов предложил отказаться от по- пыток разрабатывать алгорифмы поиска на основе прямого метода и выдвинул но- вый подход к построению алгорифмов поиска логического вывода — обратный ме- тод поиска вывода. Само название "обратный метод" подчеркивает его основное отличие от прямого метода поиска снизу вверх. Общая схема обратного метода для дедуктивной системы Л состоит в следующем. Зафиксируем какой-нибудь прямой метод поиска доказательства в 9} снизу вверх и предложение В, доказательство которого в системе 91 требуется найти. В прямом методе поиска доказательство предложения В ищется снизу вверх от В к аксиомам. В обратном методе сначала ищется структура аксиом, которыми может заканчивать- ся поиск в прямом методе. Затем сверху вниз устанавливается структура промежу- точных утверждений. С этой целью по предложению В строится исчисление благопри- ятных наборов. Основными правилами этого исчисления (могут быть добавлены с техническими целями и вспомогательные правила) являются следующие два пра- вила: 1. Правило А порождает исходные благоприятные наборы — замкнутые наборы, описывающие структуру аксиом, которые могут встретиться в доказательстве предложения В. 2. Правило Б строит по уже построенным благоприятным наборам новые благопри- ятные наборы. Свойство Основное свойство исчисления благоприятных наборов состоит в том, что пред- ложение В тогда и только тогда доказуемо в системе 91, когда в исчислении бла- гоприятных наборов выводим специальный набор — пустой набор О. О Следовательно, задача поиска доказательства предложения в дедуктивной системе сводится к задаче поиска доказательства пустого набора □ в исчислении благоприят- ных наборов. Решение второй задачи можно организовать по схеме тривиального алгорифма поиска — перебирать все благоприятные наборы, начиная с замкнутых, применяя правило Б для получения новых наборов до тех пор, пока не встретится
Приложение 4. В. П. Оревков. Обратный метод поиска вывода 955 пустой набор [j. Таким образом, мы вернулись к алгорифму Британского музея. Од- нако он применяется в условиях, резко отличающихся как от исходной постановки задачи поиска доказательства в дедуктивной системе, так и от поиска снизу вверх. С одной стороны, специфические особенности доказываемого предложения доста- точно полно учитываются в исчислении благоприятных наборов. С другой стороны, все промежуточные преобразования применяются не к громадной "заготовке" доказательства, а только к небольшому числу ранее построенных благо- приятных наборов. Метод резолюций для исчисления предикатов можно вложить в общую схему обрат- ного метода, если в качестве прямого метода поиска рассматривать построение за- крытого семантического дерева (см. § 4.2 в [81]). Следует заметить, что С. Ю. Мас- лов согласился считать это утверждение допустимым только после длинной дискуссии. Краткое описание обратного метода для исчисления предикатов было впервые опуб- ликовано С. Ю. Масловым в работе [43], а подробное — в работе [44]. Другие вариан- ты изложения метода С. Маслова можно найти в дополнениях А и В книги [81], а также в работе В. А. Лифшица [94]. Последней публикацией, посвященной обрат- ному методу, является работа [93]. В конце 1960-х годов группа математической ло- гики Ленинградского Отделения Математического Института АН СССР (ЛОМИ) раз- работала алгорифм установления выводимости в исчислении предикатов с функцио- нальными знаками. Теоретической основой этого алгорифма являлся обратный метод С. Ю. Маслова. Краткое описание опубликовано в работе [52]. Отметим черты обратного метода, которые отличают его от других способов поиска доказательств. □ Переход к скулемовской форме предложений, когда кванторы заменяются функ- циями, допустим, но его можно и не делать. В скулемовской форме труднее учи- тывать особенности кванторных префиксов при построении разрешающих алго- рифмов для некоторых подклассов формул. П Требования к структуре исходных формул не являются жесткими. Можно рас- сматривать как предваренные, так и непредваренные формулы. □ Совокупность замкнутых наборов полностью определяет дедуктивные свойства формулы. Анализ всей совокупности позволяет выбрать наиболее эффективную тактику порождения благоприятных наборов. Основной целью настоящего приложения является описание обратного метода по- иска доказательства для исчисления предикатов. Подробно изложить этот метод да- же для произвольных предваренных формул без технических деталей, мешающих заметить существенные достоинства метода, очень трудно. Еще труднее изложить случай формул произвольного вида. Поэтому лучше ограничиться таким частным случаем, который не требует сложного технического аппарата и позволяет показать все существенные особенности метода С. Ю. Маслова. В качестве такого частного случая возьмем класс предваренных формул, имеющих скулемовский префикс. Точ- ное определение этого класса приведено в ниже, в разд. "Дерево поиска доказатель- ства" данного приложения. В этом же разделе будут определены деревья поиска доказательств, которые будем рассматривать как промежуточный результат поиска доказательств снизу вверх. Окончательным результатом можно считать замкнутые
956 Часть VII. Приложения деревья. Метод С. Ю. Маслова является "обратным" к этому варианту поиска дока- зательств снизу вверх. Дерево поиска доказательства Пусть F — замкнутая формула вида ...^3x^2 ...XzjVjv? ...ym ’ %к s -^1 j Хп, У]> где AUi, ...» гь X], ..., хп, у], ..., ут) — дизъюнкции атомарных формул и их отрица- ний (1 < / < 5). Формулы этого вида называются предваренными формулами, имеющи- ми скулемовский префикс. В § 42 книги А. Черча [92] показано, что к таким форму- лам сводится общая задача поиска вывода в исчислении предикатов. Зафиксируем бесконечный список П переменных а2, а2, которые не входят в формулу F и попарно различны. Будем рассматривать растущие деревья D/, которые начинаются в корне. Корню Ио будет приписан пустой список формул. Каждому узлу Идерева D/, который отличен от корня Ид, будет приписана формула: •••» •••> (П4.1) где 1 < / < 5, Q, с2> сп — переменные из списка П, dx, d2i ..., dnl — попарно раз- личные переменные из списка П, которые также отличны от переменных а2, ..., ак, с2, сп. Каждый узел или является листом, или из него выходит ровно 5 ребер. Посредством Г( И) будем обозначать список формул, приписанных узлу И и узлам дерева Щ, через которые проходит путь из корня Ио в К Зафиксиру- ем бесконечный список Q всех /и-членных наборов переменных из списка П (члены наборов могут повторяться). В дальнейшем переменные а\, а2, ..., ак будем рассмат- ривать как константы. Переменные, отличные от а\, а2, ..., ак, будем называть основ- ными. Определение П4.2 Список Г формул вида (П4.1) будем называть тавтологичным, если можно ука- зать атомарную формулу £, которая входит в одну из формул списка Г в качестве члена дизъюнкции, и отрицание £ также входит в одну из формул списка Г в ка- честве члена дизъюнкции. 6 Определение П4.3 Будем говорить, что набор и2, ип подходит для продолжения списка Г фор- мул вида (П4.1), если нельзя указать натуральное число Z (1 < / < 5) и такие пере- менные t2, ..., tm, что формула •••> fyh Ч» би) содержится в списке Г. О
Приложение 4. В. П. Оревков. Обратный метод поиска вывода 957 Определение П4.4 Дерево Dy? будем называть Q-деревом поиска доказательства формулы F вида (П4.1), если для любого узла И выполняются следующие условия: □ если список Г(И) тавтологичен, то /является листом дерева D/, □ если для списка Г(Р) существует подходящий для продолжения набор из О, то из Vвыходит 3 ребер в узлы И], •••, J's, для всех z (1 < / < 3) узлу ^припи- сана формула ..., а& и\, ..., d}, d}^y где мь, ..., ип ~ первый по порядку набор из Q, который подходит для продолжения списка Г(Г); пере- менные d], d^ dm попарно различны, отличны от переменных а^, ai, ..., а& щ, , ип и не входят в формулы списка Г(Р). О Определение П4.5 О-дерево поиска доказательства формулы /’будем называть замкнутым, если оно конечно и для каждого листа Vсписок Г( Г) тавтологичен. О Очевидно, что О — дерево поиска доказательства формулы F однозначно определя- ется формулой F, бесконечным списком переменных П и бесконечным списком всех m-членных наборов О. Теорема П4.1 Для того чтобы формула /была выводима в исчислении предикатов, необходимо и достаточно построить замкнутое О-дерево поиска доказательства формулы F. О Доказательство Достаточность. Пусть D/ — замкнутое О-дерево поиска доказательства формулы F. Для любого узла И дерева D/ в секвенциальном исчислении генценовского типа (например, в исчислении G4 из книги С. К. Клини [90]) легко доказать секвенцию Fq, Г(Р), где посредством /0 обозначена формула ., a^, X],, xn, J/],..., Отсюда получаем, что в исчислении предикатов выводима формула F$ и, следо- вательно, формула F Необходимость. Допустим, что П-дерево поиска доказательства формулы F не является замкну- тым. Тогда в этом дереве существует ветвь, по которой стандартными методами можно построить опровергающую интерпретацию для формулы F. О
958 Часть VII. Приложения Допустимые списки формул и F-наборы Пусть Г — список формул вида (П4.1). Опишем условия, необходимые для того, чтобы существовал такой узел V Q-дерева Df, что Г совпадает с Г(Г)- Допустим, что переменные х и у содержатся в одной из формул списка Г. Будем писать х < у, если можно указать такую формулу Д(др л*, //р ..., </р ..., из списка Г, что х совпадает с одной из переменных др ..., un, а у — с одной из переменных г/р г/ж. Определение П4.6 Список Г формул вида (П4.1) будем называть допустимым, если выполняются следующие условия: □ для любой формулы Dj(a\, ak, //р ип, г/р ..., d,„) из списка Г переменные яр ак, d\, d/tl попарно различны; П каковы бы ни были формулы 1>/(лр ...» «4, zzp ...> ufl, d\, dni) и Dr(ab ак, vp v/t, q, cw) из списка Г, если переменная d( (1 < /< т) совпадает с переменной ср (1 <р < т), то / = р; □ нельзя указать такую цепочку переменных Хр х2, х/ (/>1), входящих в формулы из списка Г, что X] < х^< ... <х/< хр Допустимый список Г формул вида (П4.1) будем называть F-набором, если выпол- няются следующие условия: □ формулы в списке Г не повторяются; П каковы бы ни были формулы О[(а\ч ak, z/p ..., ин, d\, d}n) и Р/лр ..., ak, vp v„, q, ..., cj из списка Г, если наборы db dtn и ch ..., ст имеют общую переменную, то формула £>/(лр ..., ак, z/p ..., u/h r/p ..., dftl) сов- падает с формулой Df(au ak, vb ..., vn, ch ..., cm). 6 Пример П4.1 В этом и в дальнейших примерах в качестве исходной формулы F будем рассматри- вать формулу V^XpX2V^ & /?(х|, X] ) V v —17?(х|, Х|)) & Лр,л-|р -,R(xhz))& (П4.2) & (-.A(.r2,y)v R(z, у)) где R — двухместный предикат и к = 1. Список формул (т?(«з, а3) V , Й2)), т?(й3, <z4) V /?(«!,rz4 )Х (7?(й2 , «2) V —,/г(йг4, а4)) не является допустимым, так как z?2 < Д3 < < а2- Список формул (7?(д3,) V , СИ)), (,«3) v /?(д(, а3)) допустим, но является F-набором.
Приложение 4. В. П. Оревков. Обратный метод поиска вывода 959 Легко доказать следующее утверждение. Лемма П4.1 Каков бы ни был узел И Q-дерева D/л список Г (Г) является /’-набором. О Эта лемма описывает структуру списков формул, приписанных узлам произвольного Q-дерева поиска доказательства формулы F, при просмотре этого дерева снизу вверх. Прежде чем перейти к описанию структуры таких списков формул при просмотре замкнутого Q-дерева сверху вниз, введем несколько вспомогательных процедур. Вспомогательные процедуры Пусть oq, «2» > а/ и Рь Рг> Р/“ переменные, которые могут совпадать между собой и с переменными из списка П. Рассмотрим систему равенств «1 =₽1 .«2=32 (П4.3) « > f Пусть ..., ир — список без повторений всех переменных, отличных от перемен- ных «2, > ak и входящих в равенства системы (П4.3). Определение П4.7 Систему (П4.3) будем называть системой уравнений в переменных с неизвестными И], ..., ир. Решением системы уравнений (П4.3) будем называть всякий набор пере- менных (уь ..., ур) такой, что в результате одновременной замены переменных ..., Up на их значения в решении (g/j, ..., ур) левые и правые части каждого равен- ства системы совпадут. Будем говорить, что решение од поглощает решение 02, если 02 можно получить из oj путем замены некоторых основных переменных на другие переменные. Решение системы уравнений (П4.3) будем называть универ- сальным, если оно поглощает все решения этой системы. О Пример П4.2 Допустим, что к ~ 4. Рассмотрим следующую систему уравнений в переменных с не- известными х, у, г, и: у X Z = U V Решениями этой системы являются наборы переменных (а4, а4, z, Z) и (а4, а4, ah at). Первое решение будет универсальным, а второе нет. О
960 Часть VII. Приложения Частным случаем теоремы А. Робинсона об унификации является следующее утвер- ждение. Лемма П4.2 Если система уравнений в переменных имеет какое-нибудь решение, то она име- ет и универсальное решение. О Процедура отождествления переменных Пусть Г — список формул вида (П4.1); 5— система уравнений в переменных с основными переменными ..., ир в качестве неизвестных; о — универсальное решение этой системы. Процедура отождествления переменных в списке Г согласно системе S состоит в замене во всех формулах списка Г переменных zq, ..., ир на их значения в решении о. О Процедура отождествления формул Пусть Г — список формул вида (П4.1). Допустим, что в список Г входят форму- лы Dj(ab ..., ak, и{, ..., ип, dh ..., dfn) и Dj(a{, ..., ak, vb ..., v,„ q, ..., cm). Процедура отождествления этих формул в списке Г состоит в применении к списку Г про- цедуры отождествления переменных согласно системе уравнений в переменных U\ ип п т Определяемая процедура заканчивается успешно, если эта система имеет ре- шение. О Процедура преобразования списков формул в F-наборы Пусть Г список формул вида (П4.1). Процедура преобразования списка Г в F-набор состоит в последовательном выполнении следующих действий: 1. Проверить, является ли список допустимым. Если является, перейти к сле- дующему пункту. В противном случае определяемая процедура заканчивается без результата. 2. Сократить все повторения формул в списке. 3. Проверить, не является ли список F-набором. Если является, обрабатываемый список будет результатом работы определяемой процедуры. В противном случае перейти к следующему пункту. 4. Найти в списке такие формулы aki мь ..., ип, d\, ..., dm) и Dj(ab ..., ak, vb ..., vrt, q, ..., с/п), что наборы dA, ..., dm и q, ..., cm имеют об- щую переменную. Затем применить процедуру отождествления этих формул.
Приложение 4. В. П. Оревков. Обратный метод поиска вывода 961 Если она заканчивается успешно, то перейти к следующему пункту. В про- тивном случае определяемая процедура заканчивается без результата. 5. Проверить, есть ли в полученном на предыдущем шаге списке повторения формул. Если есть, то перейти к пункту 1. В противном случае определяемая процедура заканчивается без результата. О Процедура склейки формул в F-наборах Пусть Г — F- на бор. Допустим, что в список Г входят формулы А и В. Процедура склейки формул А и В в списке Г состоит в последовательном выполнении сле- дующих действий: 1. Применить процедуру отождествления формул А и В. Если она заканчивается успешно, то перейти к следующему пункту. В противном случае определяемая процедура заканчивается без результата. 2. Применить к полученному на предыдущем шаге списку формул процедуру преобразования списков формул в /’-наборы. О Процедура построения замкнутого F-набора Пусть ..., a^ ..., d\, ..., d^) и D^a^, a^ vj, ..., C], ..., cw) фор- мулы вида (П4.1), где все переменные a^, ..., a^, U\, ..., un, d\, dttl, V|, ..., vni q, ..., cni — попарно различны. Обозначим первую формулу посредством А, а вто- рую — посредством В. Предположим, что в А входит в качестве члена дизъюнк- ции атомарная формула P(Zb ..., ^), а в В — отрицание формулы Уу), где Р — 5-местный предикат. Процедура построения замкнутого F-набора по парам формул А, ..., ts) и В, —»7XV], ..., vj состоит в последовательном выполнении следующих действий: 1. Применить к списку А, В процедуру отождествления переменных согласно системе уравнений в переменных 2. В случае успешного завершения процедуры отождествления, применить про- цедуру преобразования полученного списка формул в /’-набор. Определение П4.8 /’-набор будем называть замкнутым, если он может быть получен в результате применения процедуры построения замкнутого /-набора к подходящим парам формул А, Р(Ц, ..., ts) и В, -пЛи, vj. О
962 Часть VII. Приложения Пример П4.3 Формула (П4.2) из примера П4.1 имеет только один (с точностью до переименова- ния основных переменных) одночленный замкнутый набор: (-.Л(а!,а2)у /?(«!,а2 Д (П4.4) Формула (см. П4.2) имеет только три (с точностью до переименования основных переменных) двухчленных замкнутых набора: (Л(°3 > °3 ) v -^(a2 > a2 ))> (Л(а4, °4 ) v ^R(a3 > а3 )); (П4.5) (Л(а3,аз)^ а2 ))> (_,^(°3> а3) v Л(а4>°зГ ->Л(оз>д1)); (П4.6) A(o2,oi)v-1/f(a1,a1))1(-,/{(fl2,o2)v R(a3,a2)v ^R(a2,ai)\ (П4.7) О Процедура применения правила Б к F-наборам Рассмотрим 5 Л-наборов Г] , Dj (й| 5 • • • , G£ > ^1 > • • • , ^7? у э • • • , d) Eg !• ^8 (С1 ’ • • • ’ ак » > ••• j сл > m ) (П4.8) где Гь Гб — списки формул вида (П4.1). Если какие-нибудь два из этих набо- ров содержат общую основную переменную, переименуем ее на новую перемен- ную. Таким способом добьемся, чтобы Л-наборы (П4.8) не содержали общих ос- новных переменных. Применим к списку Гн П процедуру отождествления переменных согласно системе уравнений в переменных ... — сп dl=d2='" = df’ (П4.9) а затем к полученному списку — процедуру преобразования списков формул в Л-наборы. Построенный таким образом Л-набор Z является результатом приме- нения правила Б к F-наборам (П4.8), если значения переменных dl, d' d^ I в универсальном решении а системы (П4.9) удовлетворяют следующим условиям: □ попарно различны; не входят формулы из £; П отличаются от значении в решении а переменных 8 Л’ 5 п п > * • •» О
Приложение 4. В. П. Оревков. Обратный метод поиска вывода 963 Пример П4.4 Применим к F-наборам (П4.5), (П4.6) и (П4.4) правило Б. Сначала, переименовав общие основные переменные, получим F-наборы: (/?(«3,«3)v -1Л(а2,д2)ЦЯ(д4,а4)у -nR(a3, д3)); (^(«6 > Об) v -п Я(а5, а5)), (-п R(a6, а6) v Я(д7, а6) v -,R(a6, щ)); (-1Я(д1,д8)у Я(дьа8)). Система (П4.9) в рассматриваемом случае имеет вид: Г«з = "6 [а4 = а3‘ Отождествление переменных согласно этой системе даст список: (Я(д3,а3)у -1Л(д2,д2)Ця(д3,д3)у -,Л(л5,а5)). Применив к этому списку процедуру преобразования в F-наборы, получим результат применения правила Б: (Я(д3, д3) vЯ(д2, д2)). (П4.10) ................................ о’ Исчисление благоприятных F-наборов Чтобы описать структуру F-наборов, приписанных узлам замкнутых Q-деревьев, по- строим исчисление. Это исчисление благоприятных F-наборов задается приведен- ными ниже правилами А, Б, склейки и перестановки. Правило А Замкнутые F-наборы являются благоприятными F-наборами. О Правило Б Если 5 F-наборов (П4.8) являются благоприятными и процедура применения правила Б к ним заканчивается успешно, то ее результат также является благо- приятным F-набором. 6 Правило склейки Результат применения к благоприятному F-набору процедуры склейки формул также является благоприятным F-набором. О
964 Часть VII. Приложения Правило перестановки Перестановка формул в благоприятном F-наборе также является благоприятным F- набором. Ь’ Пример П4.5 Благоприятными являются F-наборы (П4.4)—(П4.7) и (П4.10). Применив к F-набо- рам (П4.10), (П4.7) и (П4.4) правило Б, получим благоприятный F-набор: (-пЛ(д],Л1)у ->/?(#],£])) (П4.11) Чтобы обосновать благоприятность пустого /’-набора □, следует применить пра- вило Б к F-наборам (П4.10), (П4.11) и (П4.4). О Определение П4.9 Будем говорить, что F-набор Г поглощает F-набор Z, если существует такая под- становка о, что Z содержит все члены списка стГ. 6 Структуру F-наборов, приписанных узлам замкнутых ^-деревьев, описывает сле- дующая лемма. Лемма П4.3 Каков бы ни был узел V замкнутого ^-дерева, существует благоприятный F-набор Z, поглощающий F-набор Г(И). О Теперь осталось доказать основное свойство исчисления благоприятных наборов. Теорема П4.2 Для того чтобы формула /была доказуема в исчислении предикатов, необходимо и достаточно вывести пустой F-набор О в исчислении благоприятных наборов. О Доказательство Достаточность. Допустим, что пустой F-набор выводим в исчислении благоприятных наборов и D — его вывод в этом исчислении. Просматривая вывод D снизу вверх, легко построить замкнутое ^-дерево поиска доказательства формулы F Отсюда по тео- реме П4.1 получаем, что формула F выводима в исчислении предикатов. Необходимость. Допустим, что формула F выводима в исчислении предикатов. Тогда по теореме П4.1 построим О-де ре во Т поиска доказательства формулы F Пусть Ко — корень
Приложение 4. В. П. Оревков. Обратный метод поиска вывода 965 дерева Т. По лемме П4.3 построим благоприятный /’-набор поглощающий Лнабор Г(ко). Так как Л-набор Г(Ио) пуст, должен быть пустым и Л-набор Z. О Покажем, как обратный метод можно использовать для нахождения разрешающих алгорифмов для подклассов формул исчисления предикатов. Обозначим посредством @з класс предваренных формул, которые имеют скулемов- ский префикс и у которых 6 < 2. Пусть формула F принадлежит классу р2. Легко показать, что в этом случае благоприятные Л-наборы содержат не более двух фор- мул. Следовательно, число различных благоприятных Г-наборе в будет конечно. Та- ким образом, мы получили разрешающий алгорифм для класса р2.
ПРИЛОЖЕНИЕ 5 Описание компакт-диска Папка Описание Главы Apps Каталог содержит несколько примеров приложений, а также ссылки на Web-описания других коммерческих приложений, написанных на Visual Prolog Install Основной каталог диска, содержащий файл SETUP.EXE, 9 который необходимо запустить для установки Windows- версии Visual Prolog Linux Каталог содержит файлы, необходимые для инсталляции 9 Linux-версии Visual Prolog Run Каталог содержит инсталлированную версию Visual Prolog 8, 9, для запуска VDE с CD-диска; файл запуска RUN\BIN\WIN\32 12, 14, \vip.exe. VDE будет видеть все необходимые включаемые 18, 20, файлы и библиотеки 24—27 Sco Каталог содержит файлы, необходимые для инсталляции SCO UNIX-версии Visual Prolog Sharew Каталог содержит несколько полезных свободно распро- страняемых программ lntro.htm Стартовый файл HTML системы описания Visual Prolog вер- сии 5.2, поставляемой надиске Readme.txt Краткие инструкции по использованию CD-диска ReadmeRu.txt Описание содержимого компакт-диска на русском языке
Список литературы 1. Агафонов В. Н., Борщев В. Б., Воронков А. А. Логическое программирование в широком смысле (обзор). — В кн.: Логическое программирование. Сб. ста- тей. / Пер. с англ, и фр. под ред. В. Н. Агафонова. — М.: Мир, 1988. — С. 298—366. 2. Адаменко А. Н. Использование логического вывода в задаче построения синтак- сического анализатора. — В кн.: Методы математической логики в проблемах искусственного интеллекта. Тез. докладов и сообщений на Всесоюзной конф. — Вильнюс, 1983. — С. 70—71. 3. Адаменко А. Н. Разработка формальной теории функциональных сетей. // Тео- рия и практика автоматизированных систем аналитических преобразований. — Вильнюс, 1984. — С. 8—17. 4. Адаменко А. Н., Медынцев А. Л. Автоматизация анализа функциональных сетей на основе языка исчисления предикатов и системы логического вывода Про- лог-СМ. — Л.: Изв. Ленингр. электротехн. ин-та, № 338, 1985. — С. 61—65. 5. Адаменко А. Н. От логики к программированию и программирование на основе логики. // Компьютерные этюды Петербурга, № 2, 1994. — С. 53—59. 6. Амамия М., Танака Ю. Архитектура ЭВМ и искусственный интеллект. — М.: Мир, 1993. 7. Бехтерева Н. П. Магия мозга и лабиринты жизни. — СПб.: Нотабене, 1999. 8. Биркгофф Г. Математика и психология. — М.: Сов. радио, 1977. 9. Бобровский С.// PC Week/RE, № 32, 2001. — С. 32—35 (http://www.computer- museum.ru/frgnhist/aireview.htm). 10. Борщев В. Б. Пролог — основные идеи и конструкции. — В кн.: Прикладная информатика. Сб. статей под ред. В. М. Савинкова. — М.: Финансы и статисти- ка, 1986. - С. 49-76. 11. Братко И. Программирование на языке Пролог для искусственного интеллек- та. — М.: Мир, 1990. 12. Бриль В. Я. Кинетическая теория гравитации и основы единой теории мате- рии. — СПб.: Наука, 1995. 13. Бутовский А. Я. Язык для серьезных программистов. // Компьютерные этюды Петербурга, № 1, 1994. — С. 14—19.
968 Список литературы 14. Васюков В. Л. Автоматическое доказательство теорем. — В сб. ст.: Логика и компьютер. — Логические языки, содержательные рассуждения и методы поиска доказательств. — М.: Наука, 1995. — С. 24—62. 15. Воронков А. А., Дегтярев А. Н. Автоматическое доказательство теорем. // Ки- бернетика, № 3, 1986, № 4, 1987. 16. Виноград Т. Программа, понимающая естественный язык. — М.: Мир, 1976. 17. Гаврилова Т. А., Хорошевский В. Ф. Базы знаний интеллектуальных систем. — СПб.: Питер, 2000. 18. Губинский А. И. Надежность и качество функционирования эрготехнических систем. — Л.: Наука, 1982. 19. Девятков В. В. Системы искусственного интеллекта. — М.: МГТУ, 2001. 20. Дедков А. В. Логическое программирование. // Логическое программирова- ние. — М.: Знание, 1988. — С. 48. // Новое в жизни, науке, технике. Сер. Вы- числительная техника и её применение, № 9. — С. 3—24. 21. Джексон П. Введение в экспертные системы. — М.: Вильямс, 2001. 22. Ефимов Е. И. Решатели интеллектуальных задач.— М.: Наука, 1982. 23. Ин Ц., Соломон Д. Использование Турбо-Пролога. — М.: Мир, 1993. 24. Интеллектуальное программирование. Турбо Пролог и Рефал-5 на персональ- ных компьютерах. / И. О. Бабаев, М. А. Герасимов, Н. К. Косовский, И. П. Со- ловьев. — СПб.: Издательство С.-Петербургского университета, 1992. 25. Каймин В. А., Григорьев С. Г. Пролог в школьной информатике. // Информа- тика и образование, № 4, 1990. 26. Катречко С. Л. Обратный метод С. Ю. Маслова. — В сб. ст.: Логика и компью- тер, вып. 2. — Логические языки, содержательные рассуждения и методы поиска доказательств. — М.: Наука, 1995. — С. 62—75. 27. Клоксин У., Меллиш К. Программирование на языке Пролог. — М.: Мир, 1987. 28. Кларк К., Маккейб Ф. Введение в логическое программирование на микро- Прологе. — М.: Радио и связь, 1987. 29. Клыков Ю. И. Ситуационное управление большими системами. — М.: Энергия, 1974. 30. Ковальски Р. Логика в решении проблем. / Пер. с англ. / Гл. ред. физ.-мат. лит., Пробл. икусств. Интеллекта. — М.: Наука, 1990. 31. Колмогоров А. Н. Автоматы и жизнь. — В кн.: Кибернетика ожидаемая и ки- бернетика неожиданная. — М.: Наука, 1968. 32. Кольмероэ А., Кануи А., Ван Канегем. / Пролог — теоретические основы и со- временное развитие. — В кн.: Логическое программирование. Сб. статей. / Пер. с англ, и фр. под ред. В. Н. Агафонова. — М.: Мир, 1988. — С. 27—133. 33. Кузнецов А. П., Адельсон-Вельский Г. М. Дискретная математика для инжене- ра. — М.: Энергоиздат, 1988. 34. Лавров С. С. Программирование. Математические основы, средства, теория. — СПб.: БХВ-Петербург, 2001.
Список литературы 969 35. Лаптев Б. Л. Лобачевского геометрия. — В кн.: Математическая энциклопе- дия. - М.: СЭ, Т. 2, 1982. - С. 398-402. 36. Логический подход к искусственному интеллекту: от классической логики к ло- гическому программированию. / Пер. с франц. А. Тейз, П. Грибомон, Ж. Луи и др. — М.: Мир, 1990. 37. Логический подход к искусственному интеллекту: от классической логики к ло- гике баз данных. / Пер. с франц. П. Грибомон, А. Тейз, Г. Юлен и др. — М.: Мир, 1998. 38. Логическое программирование. / Пер. с англ, и фр. — М.: Мир, 1988. 39. Лорьер Ж.-Л. Системы искусственного интеллекта. — М.: Мир, 1991. 40. Малпас Дж. Реляционный язык Пролог и его применение. — М.: Наука, 1990. 41. Мальцев А. И. Алгебраические системы. — М.; Наука, 1970. 42. Марселлус Д. Программирование экспертных систем на Турбо Прологе. — М.: Финансы и статистика, 1994. 43. Маслов С. Ю. Обратный метод установления выводимости в классическом ис- числении предикатов. — ДАН СССР, Т. 159, № 1, 1964. — С. 17—20. 44. Маслов С. Ю. Обратный метод установления выводимости для логических ис- числений. — Труды Мат. института АН СССР, Т. 98, 1968. — С. 26—87. 45. Маслов С. Ю. Связь между тактиками обратного метода и метода резолюций. — Зап. науч, семинаров. - ЛОМИ АН СССР, Т. 16, 1969. - С. 137-146. 46. Маслов С. Ю. О поиске вывода в исчислениях общего типа. — Зап. науч, семи- наров. - ЛОМИ АН СССР, Т. 32, 1972. - С. 59-65. 47. Маслов С. Ю. Мутационные исчисления. — Зап. науч, семинаров. — ЛОМИ АН СССР, Т. 49, 1975. - С. 7-30. 48. Маслов С. Ю. Теория поиска вывода и вопросы психологии творчества. — В сб. ст.: Семиотика и информатика, Т. 13, 1979. — С. 17—46. 49. Маслов С. Ю., Данцин Е. Я. Метод расщеплений и другие системы. — В кн.: Методы математической логики в проблемах искусственного интеллекта. Тез. докладов и сообщений на Всесоюзной конф. — Вильнюс, 1980. 50. Маслов С. Ю., Минц Г. Е. Теория поиска вывода и обратный метод. — В кн.: Ч. Чень, Р. Ли. Математическая логика и машинное доказательство теорем. — М.: Наука, 1983. - С. 291-314. 51. Маслов С. Ю. Теория дедуктивных систем и ее применения. — М.: Радио и связь, 1986. 52. Машинный алгоритм установления выводимости на основе обратного метода. / Г. В. Давыдов, С. Ю. Маслов, Г. Е. Минц, В. П. Оревков, А. О. Слисенко. — Зап. науч, семинаров. - ЛОМИ АН СССР, Т. 16, 1969. - С. 8-19. 53. Медынцев А. Л., Симуни М. Л., Лаврова Е. С. / Реализация языка Пролог на СМ ЭВМ. — Ленингр. электротехн. ин-т. Деп. В ВИНИТИ 26.04.84, № 2679- 84. - Л., 1984. 54. Мендельсон М. Введение в математическую логику. / Пер. с англ. — 2-е изд. — М.: Наука, 1976.
970 Список литературы 55. Методы математической логики в проблемах искусственного интеллекта. Всесоюзной конф. Тез. докладов и сообщений. — Вильнюс, 1983. 56. Минский М., Пейперт С. Персептроны. — М.: Мир, 1971. 57. Набебин А. А. Логика и Пролог в дискретной математике. — М.: Изд. МЭИ, 1996. 58. Нильсон Н. Искусственный интеллект. Методы поиска решений. — М.: Мир, 1973. 59. Новиков П. С. Аксиоматический метод. — В кн.: Математическая энциклопе- дия, Т. 1. - М.: СЭ. - С. 110-114. 60. Новиков Ф. А. Дискретная математика для программистов. — СПб.: Питер, 2002. 61. Оревков В. П. Алгоритм Британского музея может быть эффективнее метода ре- золюций. — В кн.: Ч. Чень, Р. Ли. Математическая логика и машинное доказа- тельство теорем. — М.: Наука, 1983. — С. 314—332. 62. Паркер Б. Мечта Эйнштейна. В поисках единой теории строения Вселенной. — М.: Наука, 1991. 63. Петров А. 3. Гравитация. — В кн.: Математическая энциклопедия. — М.: СЭ, Т. 1, 1977. - С. 1078-1079. 64. Петров Ю. П. Лекции по истории прикладной математики. — СПб.: Изд. СПбГУ, 2001. 65. Пойа Д. Математическое открытие. — М.: Наука, 1976. 66. Поспелов Г. С., Ириков В. А. Программно-целевое планирование и управле- ние. — М.: Сов. радио, 1976. 67. Поспелов Д. А. Принципы ситуационного управления. — Изв. АН СССР. // Техническая кибернетика, № 2, 1971. — С. 10—17. 68. Поспелов Д. А. Логико-лингвистические модели в системах управления. — М.: Энергоиздат, 1981. 69. Построение экспертных систем. — Под ред. Ф. Хейес-Рота, Д. Уотермана, Д. Лената. — М.: Мир, 1987. 70. Попович П. Р., Губинский А. И., Колесников Г. М. / Эргономическое обеспе- чение деятельности космонавтов. — М.: Машиностроение, 1985. 71. Программные средства интеллектуальных систем. / А. Е. Городецкий, В. В. Ду- баренко, И. Л. Тарасова, А. В. Шереверов. — СПб.: СПбГТУ, 2000. 72. Пушкин В. Н. Эвристика — наука о творческом мышлении. — М.: Политиздат, 1967. 73. Робинсон Дж. Машинно-ориентированная логика, основанная на принципе ре- золюции. — Киберн. сб., Нов. сер. № 7. — М.: Мир, 1970. — С. 194—218. 74. Робинсон Дж. Логическое программирование — прошлое, настоящее и буду- щее. — В кн.: Логическое программирование. Сб. статей / Пер. с англ, и фр. под ред. В. Н. Агафонова — М.: Мир, 1988. — С. 8—26. 75. Розенблатт Ф. Принципы нейродинамики. — М.: Мир, 1965.
Список литера туры 971 76. Стерлинг Л., Шапиро Э. Искусство программирования на языке Пролог. — М.: Мир, 1990. 77. Стройк Д. Я. Краткий очерк истории математики. — М.: Мир, 1964. 78. Фути К. К вычислительным системам пятого поколения. В кн.: Язык Пролог в пятом поколении ЭВМ. — М.: Мир, 1988. — С. 7—16. 79. Хоггер К. Введение в логическое программирование. / Пер. с англ. — М.: Мир, 1988. 80. Частиков А. П. Архитекторы компьютерного мира. — СПб.: БХВ-Петербург, 2002. 81. Чень Ч., Ли Р. Математическая логика и автоматическое доказательство тео- рем. / Пер. с англ, под ред. С. Ю. Маслова. — М.: Наука, 1983. 82. Шанин Н. А. и др. Алгорифм машинного поиска естественного логического вы- вода в исчислении высказываний. — М.; Л.: Наука, 1965. 83. Шэндл Д. Нейронные сети. На пути к широкому внедрению. // Зарубежная ра- диоэлектроника, № 15, 1993. — С. 23—30. 84. Язык Пролог в пятом поколении ЭВМ. Сб. статей / Пер. с англ. — М.: Мир, 1988. 85. Gubinsky A. I., Adamenko A. N. Functional-semantics nets — the formalism for defining, designing and estimating the quality of functioning of man-machine systems. // Man-Machine Systems Analysis, Design and Evaluation. Preprints of the IFAC/IFIP/IEA/IFORS Conference. Oulu, Finland, V.2, 1988. - P. 393-398. 86. Nilsson U., Maluszynski J. Logic programming and Prolog. — John Wiley & Sons Ltd., (second edition), 1995. 87. Pereira F., Warren D. Definite clause grammars for analysis — a survey of the formal- ism and a comparison with augmented transition networks // Artificial Intelligence. № 13, 1980?- P. 231-278. 88. Robinson G. A machine-oriented logic based on the resolution principle. — J. ACM, V.12, № 1, 1965. - P. 23-41. 89. Russel S., Norvig P. Artificial Intelligence. A Modern Approach. — Prentice Hall, 1994. 90. Клини С. К. Математическая логика. / Пер. с англ. — М.: Мир, 1973. 91. Математическая теория логического вывода: Сб. переводов. — М.: Наука, Гл. редакция физико-математической лит., 1967. 92. Черч А. Введение в математическую логику. / Пер. с англ. — М.: Иностранная литература, 1960. 93. Degtyarev A., Voronkov A. The Inverse Method. — Handbook of Automated Reason- ing, Elsevier Science Publishers В. V., 2001. — P. 179—272. 94. Lifschitz V. What is the inverse method? // Journal of Automated Reasoning, vol. 5(1), 1989. - P. 1-23.