Дальнейшим развитием локальных средств разработки программ, которые объединяют набор средств для комплексного их применения на всех технологических этапах создания программ, являются интегрированные программные среды разработчиков. Основное назначение инструментария данного вида – повышение производительности труда программистов, автоматизация создания кодов программ, обеспечивающих интерфейс пользователя графического типа, разработка приложений для архитектуры клиент-сервер, запросов и отчетов.
1.2.4. Средства CASE-технологии
Средства CASE-технологии – относительно новое, сформировавшееся на рубеже 80-х годов, направление. Массовое применение затруднено из-за высокой стоимости и предъявляемыми требованиями к оборудованию рабочего места разработчика.
Средства CASE-технологий делятся на две группы:
встроенные в систему реализации, в которых все решения по проектированию и реализации привязаны к выбранной системе управления базами данных (СУБД);
независимые от системы реализации, в которых все решения по проектированию ориентированы на унификацию начальных этапов жизненного цикла, средств их документирования и обеспечивают большую гибкость в выборе средств реализации.
Основное достоинство CASE-технологии – поддержка коллективной работы над проектом за счет возможности работы в локальной сети разработчиков, экспорта/импорта любых фрагментов проекта, организационного управления проектом.
Некоторые CASE-технологии ориентированы только на системных проектировщиков и предоставляют специальные графические средства для изображения различного вида моделей:
диаграмм потоков данных (DFD – data flow diagrams) совместно со словарями данных и спецификациями процессов;
диаграмм “сущность–связь” (ERD – entity relationship diagrams), являющихся инфологической моделью предметной области;
диаграмм переходов состояний (STD – state transition diagrams), учитывающих события и реакцию на них системы обработки данных.
Диаграммы DFD устанавливают связь источников информации с потребителями, выделяют логические функции (процессы) преобразования информации, определяют группы элементов данных и их хранилища (базы данных).
Описание структуры потоков данных, определение их компонентов хранятся в актуальном состоянии в словаре данных, который выступает как база данных проекта. Каждая логическая функция может детализироваться с помощью DFD нижнего уровня согласно методам нисходящего проектирования.
Этими CASE-технологиями выполняются автоматизированное проектирование спецификаций программ (задание основных характеристик для разработки программ) и ведение словаря данных.
Другой класс CASE-технологий поддерживает только разработку программ, включая:
автоматическую генерацию кодов программ на основании их спецификаций;
проверку корректности описания моделей данных и схем потоков данных;
документирование программ согласно принятым стандартам и актуальному состоянию проекта;
тестирование и отладку программ.
Кодогенерация программ выполняется двумя способами: создание каркаса программ и создание полного продукта. Каркас программы служит для последующего ручного варианта редактирования исходных текстов, обеспечивая возможность вмешательства программиста; полный продукт не редактируется вручную.
В рамках CASE-технологий проект сопровождается целиком, а не только его программные коды. Проектные материалы, подготовленные в CASE-технологии, служат заданием программистам, а само программирование скорее сводится к кодированию – переводу на определенный язык структур данных и методов их обработки, если не предусмотрена автоматическая кодогенерация.
Большинство CASE-технологий использует также метод “прототипов” для быстрого создания программ на ранних этапах разработки. Кодогенерация программ осуществляется автоматически – до 85–90% объектных кодов и текстов на языках высокого уровня, а в качестве языков наиболее часто используются Ада, Си.
1.3. Языки и системы программирования
1.3.1. Развитие языков программирования
Поколения языков программирования
Языки программирования принято делить на пять поколений. В первое поколение входят языки, созданные в начале 50-х годов, когда только появились первые компьютеры. Это был первый язык ассемблера, созданный по принципу “одна инструкция – одна строка”.
Расцвет второго поколения языков программирования пришелся на конец 50-х – начало 60-х годов. Тогда был разработан символический ассемблер, в котором появилось понятие переменной. Он стал первым полноценным языком программирования. Благодаря его возникновению заметно возросли скорость разработки и надежность программ.
Появление третьего поколения языков программирования принято относить к 60-м годам. В это время возникли универсальные языки высокого уровня, с их помощью удается решать задачи из любых областей. Такие качества новых языков, как относительная простота, независимость от конкретного компьютера и возможность использования мощных синтаксических конструкций, позволили резко повысить производительность труда программистов. Понятная большинству пользователей структура этих языков привлекла к написанию небольших программ (как правило, инженерного или экономического характера) значительное число специалистов из некомпьютерных областей. Подавляющее большинство языков этого поколения успешно применяется и сегодня.
С начала 70-х годов по настоящее время продолжается период языков четвертого поколения. Эти языки предназначены для реализации крупных проектов, повышения их надежности и скорости создания. Они обычно ориентированы на специализированные области применения, где хороших результатов можно добиться, используя не универсальные, а проблемно-ориентированные языки, оперирующие конкретными понятиями узкой предметной области. Как правило, в эти языки встраиваются мощные операторы, позволяющие одной строкой описать такую функциональность, для реализации которой на языках младших поколений потребовались бы тысячи строк исходного кода.
Рождение языков пятого поколения произошло в середине 90-х годов. К ним относятся также системы автоматического создания прикладных программ с помощью визуальных средств разработки, без знания программирования. Главная идея, которая закладывается в эти языки, – возможность автоматического формирования результирующего текста на универсальных языках программирования (который потом требуется откомпилировать). Инструкции же вводятся в компьютер в максимально наглядном виде с помощью методов, наиболее удобных для человека, не знакомого с программированием.
Обзор языков программирования высокого уровня
Fortran (Фортран). Это первый компилируемый язык, созданный Джимом Бэкусом в 50-е годы. Программисты, разрабатывавшие программы исключительно на ассемблере, выражали серьезное сомнение в возможности появления высокопроизводительного языка высокого уровня, поэтому основным критерием при разработке компиляторов Фортрана являлась эффективность исполняемого кода. Хотя в Фортране впервые был реализован ряд важнейших понятий программирования, удобство создания программ было принесено в жертву возможности получения эффективного машинного кода. Однако для этого языка было создано огромное количество библиотек, начиная от статистических комплексов и кончая пакетами управления спутниками, поэтому Фортран продолжает активно использоваться во многих организациях. Имеется стандартная версия Фортрана HPF (High Performance Fortran) для параллельных суперкомпьютеров со множеством процессоров.
Cobol (Кобол). Это компилируемый язык для применения в экономической области и решения бизнес-задач, разработанный в начале 60-х годов. Он отличается большой “многословностью” – его операторы иногда выглядят как обычные английские фразы. В Коболе были реализованы очень мощные средства работы с большими объемами данных, хранящимися на различных внешних носителях. На этом языке создано очень много приложений, которые активно эксплуатируются и сегодня. Достаточно сказать, что наибольшую зарплату в США получают программисты на Коболе.
Algol (Алгол). Компилируемый язык, созданный в 1960 г. Он был призван заменить Фортран, но из-за более сложной структуры не получил широкого распространения. В 1968 г. была создана версия Алгол 68, по своим возможностям и сегодня опережающая многие языки программирования, однако из-за отсутствия достаточно эффективных компьютеров для нее не удалось своевременно создать хорошие компиляторы.
Pascal (Паскаль). Язык Паскаль, созданный в конце 70-х годов основоположником множества идей современного программирования Никлаусом Виртом, во многом напоминает Алгол, но в нем ужесточен ряд требований к структуре программы и имеются возможности, позволяющие успешно применять его при создании крупных проектов.
Basic (Бейсик). Для этого языка имеются и компиляторы, и интерпретаторы, а по популярности он занимает первое место в мире. Он создавался в 60-х годах в качестве учебного языка и очень прост в изучении.
С (Си). Данный язык был создан в лаборатории Bell и первоначально не рассматривался как массовый. Он планировался для замены ассемблера, чтобы иметь возможность создавать столь же эффективные и компактные программы, и в то же время не зависеть от конкретного типа процессора. Си во многом похож на Паскаль и имеет дополнительные средства для прямой работы с памятью (указатели). На этом языке в 70-е годы написано множество прикладных и системных программ и ряд известных операционных систем (Unix).
C++ (Си++). Си++ – это объектно-ориентированное расширение языка Си, созданное Бьярном Страуструпом в 1980 г. Множество новых мощных возможностей, позволивших резко повысить производительность программистов, наложилось на унаследованную от языка Си определенную низкоуровневость, в результате чего создание сложных и надежных программ потребовало от разработчиков высокого уровня профессиональной подготовки.
Java (Ява). Этот язык был создан компанией Sun в начале 90-х годов на основе Си++. Он призван упростить разработку приложений на основе Си++ путем исключения из него всех низкоуровневых возможностей. Но главная особенность этого языка – компиляция не в машинный код, а в платформно-независимый байт-код (каждая команда занимает один байт). Этот байт-код может выполняться с помощью интерпретатора – виртуальной Java-машины JVM (Java Virtual Machine), версии которой созданы сегодня для любых платформ. Благодаря наличию множества Java-машин программы на Java можно переносить не только на уровне исходных текстов, но и на уровне двоичного байт-кода, поэтому по популярности язык Ява сегодня занимает второе место в мире после Бейсика.
Особое внимание в развитии этого языка уделяется двум направлениям: поддержке всевозможных мобильных устройств и микрокомпьютеров, встраиваемых в бытовую технику (технология Jini) и созданию платформно-независимых программных модулей, способных работать на серверах в глобальных и локальных сетях с различными операционными системами (технология Java Beans). Пока основной недостаток этого языка – невысокое быстродействие, так как язык Ява интерпретируемый.
Языки программирования баз данных
Эта группа языков отличается от алгоритмических языков прежде всего решаемыми задачами. База данных – это файл (или группа файлов), представляющий собой упорядоченный набор записей, имеющих единообразную структуру и организованных по единому шаблону (как правило, в табличном виде). База данных может состоять из нескольких таблиц. Удобно хранить в базах данных различные сведения из справочников, картотек, журналов бухгалтерского учета и т. д.
При работе с базами данных чаще всего требуется выполнять следующие операции:
создание/модификация свойств/удаление таблиц в базе данных;
поиск, отбор, сортировка информации по запросам пользователей;
добавление новых записей;
модификация существующих записей;
удаление существующих записей.
Первые базы данных появились очень давно, как только появилась потребность в обработке больших массивов информации и выборки групп записей по определенным признакам. Для этого был создан структурированный язык запросов SQL (Structured Query Language). Он основан на мощной математической теории и позволяет выполнять эффективную обработку баз данных, манипулируя не отдельными записями, а группами записей.
Для управления большими базами данных и их эффективной обработки разработаны СУБД (Системы Управления Базами Данных). Практически в каждой СУБД помимо поддержки языка SQL имеется также свой уникальный язык, ориентированный на особенности этой СУБД и не переносимый на другие системы. Сегодня в мире насчитывается пять ведущих производителей СУБД: Microsoft (SQL Server), IBM (DB2), Oracle, Software AG (Adabas), Informix и Sybase. Их продукты нацелены на поддержку одновременной работы тысяч пользователей в сети, а базы данных могут храниться в распределенном виде на нескольких серверах. В Oracle имеется встроенный язык
PL/SQL, в Informix – INFORMIX 4GL, в Adabas – Natural и т. д.
С появлением персональных компьютеров были созданы так называемые настольные СУБД. Родоначальником современных языков программирования баз данных для ПК принято считать СУБД dBase II, язык которой был интерпретируемым. Затем для него были созданы компиляторы, появились СУБД FoxPro и Clipper, поддерживающие диалекты этого языка. Сегодня похожие, но несовместимые версии языков семейства dBase реализованы в продуктах Visual FoxPro фирмы Microsoft и Visual dBase фирмы Inprise.
Языки программирования для Интернета
С активным развитием глобальной сети было создано немало популярных языков программирования, адаптированных специально для Интернета. Все они отличаются характерными особенностями: языки являются интерпретируемыми, интерпретаторы для них распространяются бесплатно, а сами программы – в исходных текстах. Такие языки называют скрипт-языками.
HTML. Общеизвестный язык для оформления документов. Он очень прост и содержит элементарные команды форматирования текста, добавления рисунков, задания шрифтов и цветов, организации ссылок и таблиц. Все Web-страницы написаны на языке HTML или используют его расширения.
Perl. В 80-х годах Ларри Уолл разработал язык Perl. Он задумывался как средство эффективной обработки больших текстовых файлов, генерации текстовых отчетов и управления задачами. По мощности Perl значительно превосходит языки типа Си. В него введено много часто используемых функций работы со строками, массивами, всевозможные средства преобразования данных, управления процессами, работы с системной информацией и др.
Tcl/Tk. В конце 80-х годов Джон Аустираут придумал популярный скрипт-язык Тсl и библиотеку Tk. В Тсl он попытался воплотить видение идеального скрипт-языка. Тсl ориентирован на автоматизацию рутинных процессов и состоит из мощных команд, предназначенных для работы с абстрактными нетипизированными объектами. Он независим от типа системы и при этом позволяет создавать программы с графическим интерфейсом.
VRML. В 1994 г. был создан язык VRML для организации виртуальных трехмерных интерфейсов в Интернете. Он позволяет описывать в текстовом виде различные трехмерные сцены, освещение и тени, текстуры (покрытия объектов), создавать свои миры, путешествовать по ним, “облетать” со всех сторон, вращать в любых направлениях, масштабировать, регулировать освещенность и т. д.
Языки моделирования
При создании программ и формировании структур баз данных нередко применяются формальные способы их представления – формальные нотации, с помощью которых можно визуально представить (изобразить с помощью мыши) таблицы баз данных, поля, объекты программы и взаимосвязи между ними в системе, имеющей специализированный редактор и генератор исходных текстов программ на основе созданной модели. Такие системы называются CASE-системами. В них активно применяются нотации IDEF, а в последнее время все большую популярность завоевывает язык графического моделирования UML.
Прочие языки программирования
PL/1 (ПЛ/1). В середине 60-х годов компания IBM решила взять все лучшее из языков Фортран, Кобол и Алгол. В результате в 1964 г. на свет появился новый компилируемый язык программирования, который получил название Programming Language One. В этом языке было реализовано множество уникальных решений, полезность которых удается оценить только спустя 33 года, в эпоху крупных программных систем. По своим возможностям ПЛ/1 значительно мощнее многих других языков (Си, Паскаля). Например, в ПЛ/1 присутствует уникальная возможность указания точности вычислений – ее нет даже у Си++ и Явы. Этот язык и сегодня продолжает поддерживаться компанией IBM.
Smalltalk (Смолток). Работа над этим языком началась в 1970 г. в исследовательской лаборатории корпорации XEROX, а закончились спустя 10 лет, воплотившись в окончательном варианте интерпретатора SMALLTALK-80. Данный язык оригинален тем, что его синтаксис очень компактен и базируется исключительно на понятии объекта. В этом языке отсутствуют операторы или данные. Все, что входит в Смолток, является объектами, а сами объекты общаются друг с другом исключительно с помощью сообщений (например, появление выражения I+1 вызывает посылку объекту I сообщения “+”, т.е. “прибавить”, с параметром 1, который считается не числом-константой, а тоже объектом). Больше никаких управляющих структур, за исключением “оператора” ветвления (на самом деле функции, принадлежащей стандартному объекту), в языке нет, хотя их можно очень просто смоделировать. Сегодня версия VisualAge for Smalltalk активно развивается компанией IBM.
LISP (Лисп). Интерпретируемый язык программирования, созданный в 1960 г. Джоном Маккарти. Ориентирован на структуру данных в форме списка и позволяет организовывать эффективную обработку больших объемов текстовой информации,
Prolog (Пролог). Создан в начале 70-х годов Аланом Колмероэ. Программа на этом языке, в основу которого положена математическая модель теории исчисления предикатов, строится из последовательности фактов и правил, а затем формулируется утверждение, которое Пролог будет пытаться доказать с помощью введенных правил. Человек только описывает структуру задачи, а внутренний “мотор” Пролога сам ищет решение с помощью методов поиска и сопоставления.
Ada (Ада). Назван по имени леди Августы Ады Байрон, дочери английского поэта Байрона и его отдаленной родственницы Анабеллы Милбэнк. В 1980 г. сотни экспертов Министерства обороны США отобрали из 17 вариантов именно этот язык, разработанный небольшой группой под руководством Жана Ишбиа. Он удовлетворил на то время все требования Пентагона, а к сегодняшнему дню в его развитие вложены десятки миллиардов долларов. Структура самого языка похожа на Паскаль. В нем имеются средства строгого разграничения доступа к различным уровням спецификаций, доведена до предела мощность управляющих конструкций.
Forth (Форт). Результат попытки Чарльза Мура в 70-х годах создать язык, обладающий мощными средствами программирования, который мог бы быть эффективно реализованным на компьютерах с небольшими объемами памяти, а компилятор мог бы выдавать очень быстрый и компактный код, т.е. служил заменой ассемблеру. Однако, сложности восприятия программного текста, записанного в непривычной форме, сильно затрудняли поиск ошибок и с появлением Си язык Форт оказался забытым.
1.3.2. Современные системы программирования
Основы визуального программирования интерфейса
Сколько существует программирование, столько существуют в нем и тупики, в которые оно постоянно попадает и из которых в конце концов выходит. Один из таких тупиков, или кризисов, не так давно был связан с разработкой графического интерфейса пользователя. Программирование вручную всяких привычных пользователю окон, кнопок, меню, обработка событий мыши и клавиатуры, включение в программы изображений и звука требовало все больше и больше времени программиста. В ряде случаев весь этот сервис начинал занимать до 80–90% объема программных кодов. Причем весь этот труд нередко пропадал почти впустую, поскольку через год – другой менялся общепринятый стиль графического интерфейса и все приходилось начинать заново.
Выход из этой ситуации обозначился благодаря двум подходам. Первый – стандартизация многих функций интерфейса, благодаря чему появилась возможность использовать библиотеки, имеющиеся, например, в Windows. В итоге при смене стиля графического интерфейса (например, при переходе от Windows 3.х к Windows 95) приложения смогли автоматически приспосабливаться к новой системе без какого-либо перепрограммирования. На этом пути создались прекрасные условия для решения одной из важнейших задач совершенствования техники программирования – повторного использования кодов. Однажды разработанные формы, компоненты, функции могли быть впоследствии неоднократно использованы для решения различных задач. Каждый программист получил доступ к наработкам других программистов и к огромным библиотекам, созданным различными фирмами. Причем была обеспечена совместимость программного обеспечения, разработанного на разных алгоритмических языках.
Вторым революционным шагом, кардинально облегчившим жизнь программистов, явилось появление визуального программирования, возникшего в Visual Basic и нашедшего блестящее воплощение в Delphi и C++Builder фирмы Borland.
Визуальное программирование позволило свести проектирование пользовательского интерфейса к простым и наглядным процедурам, которые дают возможность за минуты или часы сделать то, на что ранее уходили месяцы работы. В современном виде в Delphi это выглядит так.
Вы работаете в Интегрированной Среде Разработки (ИСР или Integrated development environment – IDE) Delphi. Среда предоставляет вам формы (в приложении их может быть несколько), на которых размещаются компоненты. Обычно это оконная форма, хотя могут быть и невидимые формы. На форму с помощью мыши переносятся и размещаются пиктограммы компонентов, имеющихся в библиотеках Delphi. С помощью простых манипуляций вы можете изменять размеры и расположение этих компонентов. При этом вы все время в процессе проектирования видите результат – изображение формы и расположенных на ней компонентов. Вам не надо мучиться, многократно запуская приложение и выбирая наиболее удачные размеры окна и компонентов. Результаты проектирования вы видите, даже не компилируя программу, немедленно после выполнения какой-то операции с помощью мыши.
Но достоинства визуального программирования не сводятся к этому. Самое главное заключается в том, что во время проектирования формы и размещения на ней компонентов Delphi автоматически формирует коды программы, включая в нее соответствующие фрагменты, описывающие данный компонент. А затем в соответствующих диалоговых окнах пользователь может изменить заданные по умолчанию значения каких-то свойств этих компонентов и, при необходимости, написать обработчики каких-то событий. Фактически, проектирование сводится к размещению компонентов на форме, заданию некоторых их свойств и написанию, при необходимости, обработчиков событий.
Компоненты могут быть визуальные, видимые при работе приложения, и невизуальные, выполняющие те или иные служебные функции. Визуальные компоненты сразу видны на экране в процессе проектирования в таком же виде, в каком их увидит пользователь во время выполнения приложения. Это позволяет очень легко выбрать место их расположения и их дизайн – форму, размер, оформление, текст, цвет и т.д. Невизуальные компоненты видны на форме в процессе проектирования в виде пиктограмм, но пользователю во время выполнения они не видны, хотя и выполняют для него за кадром весьма полезную работу.
В библиотеки визуальных компонентов Delphi включено множество типов компонентов и их номенклатура очень быстро расширяется от версии к версии. Имеющегося уже сейчас вполне достаточно, чтобы построить практически любое самое замысловатое приложение, не прибегая к созданию новых компонентов. При этом даже неопытный программист, делающий свои первые шаги на этом поприще, может создавать приложения, которые выглядят совершенно профессионально.
Компоненты достаточно легко создавать самостоятельно, поэтому в мире сегодня распространяются тысячи бесплатных и платных компонентов для наиболее известных RAD-сред, из них формируются библиотеки компонентов – объектные репозитории. Компоненты выступают в роли “строительных кирпичиков”, позволяющих собирать готовое приложение с богатыми возможностями, написав всего десяток строк исходного кода, и такой компонентный подход к созданию программ считается очень перспективным, потому что без лишних усилий и на законных основаниях допускает повторное использование чужого труда.
Системы быстрой разработки приложений
Визуальное объектно-ориентированное программирование способствовало созданию технологии, получившей название быстрая разработка приложений – RAD. Эта технология характерна для нового поколения систем программирования, к которому относится и Delphi.
Первой в этом новом мире более простого и наглядного интерфейса была среда Visual Basic. Она сформировала новый стиль взаимодействия разработчика программы с компьютером, который позволяет наглядно конструировать пользовательский интерфейс с помощью мыши, а не обычным для прежних времен путем: написанием кодов, их трансляцией и выполнением программы, после чего только и можно посмотреть, как же это выглядит на экране.
Хотя Visual Basic имел широкий спрос и помог открыть мир программирования для людей, не слишком в нем искушенных, он не свободен от многих проблем. Главные из них – низкая производительность разрабатываемых приложений при их выполнении, недостаточная строгость и объектная ориентированность языка, способствующая скорее быстрой разработке поделок, а не созданию мощных эффективных приложений, а также ряд других недостатков.
Delphi – это следующий шаг в развитии среды быстрой разработки приложений. Она исправляет многие дефекты, обнаруженные в Visual Basic. Разработчики Delphi создали инструмент, который на первый взгляд похож на среду Visual Basic, хотя в действительности он заметно лучше.
Delphi базируется на языке Object Pascal. Компиляторы с языков семейства Паскаль фирмы Borland (начиная с Turbo Pascal 1.0) были одними из самых быстрых компиляторов. В настоящее время Object Pascal – это объектно-ориентированный язык с твердой опорой в виде хорошего компилятора.
Надо иметь в виду, что ориентация приложений Delphi на Object Pascal нисколько не сужает возможностей разработчика. Приложения Delphi прекрасно могут использовать разработки на других языках, включая C++ и даже ассемблер. Можно использовать библиотеки, созданные другими фирмами, в частности, Microsoft или независимыми разработчиками. Можно реализовывать свои разработки в виде самостоятельно выполняемых файлов или в виде пакетов, поддерживающих выполнение ряда приложений.
Отдельно надо сказать об одной из главных задач Delphi – разработке приложений для работы с базами данных. В этой области Delphi занимает самые передовые позиции, работая с любыми системами управления базами данных.
Основные системы программирования
Из универсальных языков программирования сегодня наиболее популярны следующие:
Бейсик (Basic) – для освоения требует начальной подготовки (общеобразовательная школа);
Паскаль (Pascal) – требует специальной подготовки (школы с углубленным изучением предмета и общетехнические ВУЗы);
Си++ (C++), Ява (Java) – требуют профессиональной подготовки (специализированные средние и высшие учебные заведения).
Для каждого из этих языков программирования сегодня имеется немало систем программирования, выпускаемых различными фирмами и ориентированных на различные модели ПК и операционные системы. Наиболее популярны следующие визуальные среды быстрого проектирования программ для Windows:
Basic: Microsoft Visual Basic;
Pascal: Borland Delphi;
C++: Borland C++Bulider;
Java: Symantec Cafe.
Для разработки серверных и распределенных приложений можно использовать систему программирования Microsoft Visual C++, продукты фирмы Inprise под маркой Borland, практически любые средства программирования на Java.
1.3.3. Архитектура программных систем
В то время как большинство автономных приложений – офисные программы, среды разработки, системы подготовки текстов и изображений – выполняются на одном компьютере, крупные информационные комплексы (например, система автоматизации предприятия) состоят из десятков и сотен отдельных программ, которые взаимодействуют друг с другом по сети, выполняясь на разных компьютерах. В таких случаях говорят, что они работают в различной программной архитектуре. Она делится на следующие группы.
Автономные приложения. Работают на одном компьютере.
Приложения в файл-серверной архитектуре. Компьютеры пользователей системы объединены в сеть, при этом на каждом из них (на клиентском месте) запущены копии одной и той же программы, которые обращаются за данными к серверу – специальному компьютеру, который хранит файлы, одновременно доступные всем пользователям (как правило, это базы данных). Сервер обладает повышенной надежностью, высоким быстродействием, большим объемом памяти, на нем установлена специальная серверная версия операционной системы.
При одновременном обращении нескольких программ к одному файлу, например, с целью его обновления, могут возникнуть проблемы, связанные с неоднозначностью определения его содержимого. Поэтому каждое изменение общедоступного файла выделяется в транзакцию – элементарную операцию по обработке данных, имеющую фиксированные начало, конец (успешное или неуспешное завершение) и ряд других характеристик.
Особенность этой архитектуры в том, что все вычисления выполняются на клиентских местах, что требует наличия на них достаточно производительных ПК (это так называемые системы с толстым клиентом – программой, которая выполняет всю обработку получаемой от сервера информации).
Приложения в клиент-серверной архитектуре. Эта архитектура похожа на предыдущую, только сервер помимо простого обеспечения одновременного доступа к данным способен еще выполнять программы (обычно выполняются СУБД – тогда сервер называется сервером баз данных), которые берут на себя определенный объем вычислений (в файл-серверной архитектуре он реализуется полностью на клиентских местах). Благодаря этому удается повысить общую надежность системы, так как сервер работает значительно более устойчиво, чем ПК, и снять лишнюю нагрузку с клиентских мест, на которых удается использовать дешевые компьютеры. Запускаемые на них приложения реально осуществляют небольшие объемы вычислений, а иногда занимаются только отображением получаемой от сервера информации, поэтому они называются тонкими клиентами.
Приложения в многозвенной архитектуре. Недостаток предыдущей архитектуры в том, что резко возрастает нагрузка на сервер, а если он выходит из строя, то работа всей системы останавливается. Поэтому в некоторых случаях в систему добавляется так называемый сервер приложений, на котором выполняется вся вычислительная работа. Другой сервер баз данных обрабатывает запросы пользователей, на третьем может быть установлена специальная программа – монитор транзакций, которая оптимизирует обработку транзакций и балансирует нагрузку на серверы. В большинстве практических случаев все серверы соединены последовательно – позвенно, и выход из строя одного звена если и не останавливает всю работу, то по крайней мере резко снижает производительность системы.
Приложения в распределенной архитектуре. Чтобы избежать недостатков рассмотренных архитектур, были придуманы специальные технологии, позволяющие создавать программу в виде набора компонентов, которые можно запускать на любых серверах, связанных в сеть (компоненты как бы распределены по сети). Основное преимущество подобного подхода в том, что при выходе из строя любого компьютера специальные программы-мониторы, которые следят за корректностью работы компонентов и позволяют им “переговариваться” между собой, сразу перезапускают временно пропавший компонент на другом компьютере. При этом общая надежность всей системы становится очень высокой, а вычислительная загрузка распределяется между серверами оптимальным образом. Доступ к возможностям любого компонента, предназначенного для общения с пользователем, осуществляется с произвольного клиентского места. При этом, так как все вычисления происходят на серверах, появляется возможность создавать сверхтонкие клиенты – программы, только отображающие получаемую из сети информацию и требующие минимальных компьютерных ресурсов. Благодаря этому доступ к компонентной системе возможен не только с ПК, но и с небольших мобильных устройств.
Частный случай компонентного подхода – доступ к серверным приложениям из броузеров через Интернет.
Сегодня наиболее популярны три компонентные технологии – СОRВА консорциума OMG, Java Beans компании Sun и СОМ+ корпорации Microsoft. Эти технологии будут определять развитие информационной индустрии в ближайшие десятилетия.
2. Особенности разработки программного обеспечения на языках функционального и логического программирования
2.1. Особенности языка функционального программирования Лисп
2.1.1. Назначение и общая характеристика языка
В программировании помимо процедурного подхода, представителями которого являются такие универсальные языки высокого уровня как Бейсик, Паскаль, Си, и логического подхода, представленного языком Пролог, существует еще одно направление – функциональное. Оно возникло в 1962 г. вместе с созданием Дж.Маккарти языка программирования Лисп (Lisp). Долгое время этот язык занимал особое место. Подавляющее большинство программ искусственного интеллекта составлено на языке Лисп. До сих пор он считается стандартным языком разработки систем искусственного интеллекта. Его популярность особенно велика в США. В нашей стране этот язык не получил широкого распространения (одна из причин – недостаток литературы о нем на русском языке), однако в настоящее время популярность этого языка быстро растет. Несмотря на то, что Лисп – один из самых старых используемых языков программирования, у него многое еще впереди.
Язык Лисп – один из первых языков обработки данных в символьной форме. Его название происходит от англ. list processing – обработка списков. В Лиспе и программа, и обрабатываемые ею данные представляются в одной и той же форме – в форме списка. Таким образом, программы могут обрабатывать и преобразовывать другие программы и даже самих себя.
Используемый в Лиспе функциональный подход к программированию основывается на той простой идее, что вся обработка информации и получение искомого результата могут быть представлены в виде вложенных и/или рекурсивных вызовов функций, выполняющих некоторые действия, так что значение одной функции используется как аргумент другой. Значение этой функции становится аргументом следующей и т.д. пока не будет получен конечный
результат – решение задачи.
Программы строятся из логически расчлененных определений функций. Определения состоят из управляющих структур, организующих вычисления, и из вложенных вызовов функций. Основными методами функционального программирования являются композиция и рекурсия. Все это представляет собой реализацию идей теории рекурсивных функций.
Имеется большое число систем программирования на Лиспе, реализованных для компьютеров различных типов. Как правило, это интерпретирующие системы, работающие в интерактивном (диалоговом) режиме. Соответствующие описания и команды вводятся с клавиатуры после приглашения “_”, затем прочитывается результат.
2.1.2. Основные элементы программы на языке Лисп. Списки
Программы на языке Лисп строятся из простейших неделимых элементов, называемых атомами. Символы и числа представляют собой атомы, из них состоят все остальные структуры.
Символ – это имя, состоящее из букв, цифр и специальных знаков, которое обозначает какой-нибудь предмет или действие из реального мира, а также число, функцию (программу) и другие объекты. Наряду с символами используются и числа (значения), которые могут быть целыми (например, 543), десятичными (например, 3.789) и в представлении с мантиссой и порядком (например, 1.0243Е-6).
Главной структурой в Лиспе является список. Списком называется упорядоченная последовательность, элементами которой являются либо атомы, либо списки (подсписки). Списки заключаются в круглые скобки, а их элементы разделяются пробелами. Например,
(а b (с d) e)
(В группе 18 студентов)
(((((первый) 2) третий) 4) 5)
Список, в котором нет ни одного элемента, называется пустым и обозначается “()” или специальным символом NIL. Список – это структура данных, представляющая некоторую иерархическую связь (дерево) с помощью строго соответствующих друг другу открывающих и закрывающих скобок.
Имеется и альтернативный способ записи списков – с использованием точечной нотации. Точка при этом отделяет начальный элемент списка – его голову – от остальной части списка – хвоста: (голова, хвост) или
(а1 а2... aN) = (а1. (а2.... (aN.Nil)...))
Здесь Nil – это предопределенная константа, означающая пустой список (и одновременно логическое значение “Ложь”).
Атомы и списки называются S-выражениями.
Списки в Лиспе – основное средство представления знаний. Например, с помощью вложенных списков может быть представлена характеристика человека:
(сотрудник
(имя Петр)
(отчество Петрович)
(фамилия Иванов)
(образование (среднее (с 1969 по 1979))
(высшее (ВГУ г.Воронеж (с 1979 по 1982)
(МГУ г.Москва (с 1982 по 1984))
(специальность (техническая кибернетика)
(программирование)
(стаж (с 1984 по 1997)
)
2.1.3. Функции
Функции в Лиспе аналогично математическим функциям ставят в соответствие элементам из одного множества – определения (аргументов) – единственный элемент из множества значений. В программах следует различать определение функций и вызов (применение) функции.
В языке Лисп принята единообразная префиксная форма записи, при которой как имя функции или действия, так и аргументы записываются внутри скобок и действие предшествует аргументам, к которым оно применяется:
(f х)
(g х у)
(сумма_квадратов 2 3)
Аналогично записываются и арифметические действия:
(+ х у) (x + y)
(* х (+ у z)) (x * (y + z))
(+ (* х х) (* у у)) ((x *x) + (y * y))
Определение функций и их вычисление в Лиспе основано на лямбда-исчислении Черча. В -исчислении Черча функция записывается в виде
(х1, х2,..., xn).fn
В Лиспе -выражение имеет вид
(LAMBDA (x1, х2,..., xn).fn)
Символ LAMBDA означает, что мы имеем дело с определением функции. Символы xi являются формальными параметрами, они образуют список, называемый лямбда-списком; fn – это тело функции, которая может иметь произвольную форму, допускаемую интерпретатором Лиспа. Телом функции может быть, например, константа или композиция из вызовов функций. Функцию, вычисляющую сумму квадратов двух чисел, можно, например, определить так:
(lambda (х у) (+ (* x х) (* у у)))
____ ______________
лямбда-список тело функции
Лямбда-выражение – это безымянная функция, которая может быть использована для связывания формальных и фактических параметров на время вычислений. Вызов такой функции происходит по форме
(лямбда-выражение а1 а2... an)
Здесь ai – формальные параметры, с которыми происходит вычисление.
Например
((lambda (х у) (+ (*х x) (* у у))) 3 4), где x и y – формальные параметры, а 3 и 4 – фактические параметры
Результат: 25.
Определить новую функцию и дать ей имя для последующих вызовов можно с помощью функции DEFUN (define function):
(DEFUN имя лямбда-список тело).
DEFUN соединяет символ с лямбда-выражением, и символ начинает представлять (именовать) определенные этим лямбда-выражением вычисления. Значением этой формы является имя новой функции:
(defun sumsquare (х у) (+ (* х х) (* у у)))
________ ____ ______________
имя лямбда-список тело
Результат: sumsquare.
Вызов (применение) этой функции:
(sumsquare 3 4)
Результат:25.
Определение функции задается списком, поэтому его можно модифицировать в ходе выполнения программы. Кроме того, некоторый символ может быть и именем функции и переменной.
В Лиспе передача параметров происходит по значению. Формальные параметры функций являются статическими и локальными, т.е. действительны только внутри той формы, в которой они определены.
Основу для построения различных функций образует набор небольшого числа примитивных встроенных функций. Базовыми функциями обработки S-выражений являются функции:
CAR, CDR, CONS, ATOM, EQ, EQL, =
и другие, смысл которых отражен в табл. 2.1.
Отметим, что в программах на Лиспе надо тщательно отличать значения от их обозначений.
В Лиспе константы обозначают самих себя. Выражения типа (* 2 2) сразу вычисляются. Чтобы избежать нежелательного вычисления выражения используется функция QUOTE или знак апострофа (‘) перед выражением:
(* 2 2): 4
‘ (* 2 2): ‘ (* 2 2) – список
Произвольный символ можно использовать как переменную, и он может обозначать произвольное выражение. При первом использовании символу должно быть присвоено или с ним связано некоторое значение с помощью функции SET, например:
(SET ‘операции ‘ (+ – * /))
Знак ‘ используется для подавления вычисления аргументов функции SET. Функция SETQ не вычисляет значения 1-го аргумента (а 2-го вычисляет).
На значение символа можно сослаться, указав его без
апострофа (‘).
Для занесения значений в ячейку памяти, связанную с символом, можно пользоваться обобщенной функцией присваивания SETF, размещающей значения в соответствующей ячейке памяти:
(SETF ячейка_памяти значение)
Переменная “ячейка_памяти” без апострофа указывает на ячейку памяти. Присвоение, выполняемое функциями SET, SETQ и SETF, является побочным эффектом этих функций, помимо того, данные функции возвращают присваиваемые значения.
2.1.4. Формы. Управляющие конструкции в Лисп-программе
Программа состоит не только из функций, но и из форм. Простейшими формами являются константы, переменные, лямбда-вызовы, вызовы функций.
Остановимся более подробно на специальных формах, предназначенных для управления обработкой программы и контекстом. У каждой формы определенный синтаксис и семантика, основанные на едином способе записи и интерпретации.
Управляющие предложения Лиспа внешне выглядят как вызовы функций – в виде скобочных выражений, первый элемент которых действует как имя управляющей структуры, а остальные элементы – как аргументы. Наиболее важные формы можно разделить на следующие группы:
работа с контекстом
– QUOTE или блокировка вычисления,
– вызов функции и лямбда-вызов,
– предложения LET и LET*;
последовательное исполнение
– предложения PROG1, PROG2 и PROGN;
разветвление исполнения
– условные предложения COND, IF, WHEN, UNLESS,
– выбирающее предложение CASE;
итерации
– циклические предложения DO, DO*, LOOP, DOTIMES, DOUNTIL;
передачи управления
– предложения PROG, GO и RETURN;
динамическое управление вычислением
– THROW, CATCH, а также BLOCK.
Эти управляющие формы (кроме QUOTE и лямбда-вызова, а также вызовов функций), в основном, используются в теле лямбда-выражений, определяющих функции.
Предложение LET используется для создания связи переменных внутри формы:
(LET ((пер1 знач1) (пер2 знач2)...) форма1 форма2...).
При вычислении этого выражения статические переменные пер1, пер2,... связываются (одновременно) с соответствующими значениями знач1, знач2,..., а затем вычисляются значения форм: форма1,
форма2,... Значение последней формы возвращается как общий результат. Форма LET* отличается от LET лишь тем, что связывание переменных и вычисление форм происходит не одновременно, а последовательно, вначале 1-е, потом 2-е и т.д.
Например:
(let* ((х 2) (у (* 3 х))); сначала присваивает х значение 2, затем
y – значение х*3=6
(list x у); строит список из аргументов х и y
Результат: (2 6).
Предложения PROG1, PROG2 и PROGN позволяют организовывать последовательные вычисления из нескольких вычисляемых форм:
(PROG1 форма1 форма2... формаn)
(PROG2 форма1 форма2... формаn)
(PROGN форма1 форма2... формаn)
Различие этих форм лишь в возвращаемых ими в качестве общего значения результатах. Форма PROG1 возвращает значение формы1, PROG2 – формы2, PROGN – последней формы n.
Например:
(progn (setq х 2) (setq у (* 3 х))); присваивается х значение 2, присваивается y значение 3*х=6, возвращается результат последней формы – 6.
Результат:6.
Предложение COND является основным средством разветвления обработки. Структура условного предложения такова:
(COND (р1 а1) (р2 а2)... (pn an))
pi – это предикаты (выражения-условия, которые могут быть либо истинными (Т), либо ложными (NIL)). Их значения вычисляются слева направо, пока не будет получено значение “истина” (Т), затем вычисляется и возвращается в качестве результата результирующее выражение ai, соответствующее 1-му истинному предикату pi. Если истинного предиката нет, то значение COND – NIL. Форма ai для соответствующего предиката может отсутствовать (тогда возвращается значение этого предиката в случае его истинности), или, наоборот, может быть задана последовательность форм для предиката pi – тогда эти формы вычисляются последовательно и возвращается значение последней.
В следующем примере с помощью предложения COND определена функция, устанавливающая тип выражения:
(defun тип (1); определяется функция с именем ТИП и формальным параметром 1
(cond ((null 1) ‘пусто) ((atom 1) ‘атом) (t ‘список))); тело функции
Примеры применения этой функции:
(тип ‘ (a b с))
Результат: СПИСОК.
(тип (atom ‘ (а т о м))); функция (atom ‘ (а т о м)) возвращает Nil
Результат: ПУСТО.
Для организации ветвления можно использовать и формулы IF, WHEN, UNLESS:
(IF условие то-форма иначе-форма)
что эквивалентно
(COND (условие то-форма) (Т иначе форма));
(WHEN условие форма1 форма2...)
что эквивалентно
(UNLESS (NOT условие) форма1 форма2...)
или
(COND (условие форма1 форма2...))
Можно применять и выбирающее предложение CASE:
(CASE ключ (список ключей1 форма11 форма12...)
(список ключей2 форма21 форма22...)
В этой форме сначала вычисляется значение ключевой формы “ключ”, затем происходит сравнение с элементами списков ключей и, если найдено значение ключевой формы, вычисляется последовательность соответствующих форм, значение последней из которых возвращается как значение всего выражения CASE.
Предложения PROG, GO и RETURN аналогичны конструкциям неструктурных языков программирования (типа FORTRAN, Бейсик); пользоваться ими не рекомендуется.
2.1.5. Рекурсия и цикл в программах на Лиспе
В “чистом” функциональном программировании организация повторяющихся вычислений должна происходить лишь с помощью условных предложений и определения рекурсивных, вызывающих самих себя, функций. Рассмотрим в качестве примера функцию, просто определяемую через рекурсию, – факториал
n!=1 * 2 * 3 *... * (n–1) * n = (n–1)! * n (0! = 1 по определению):
(defun! (n) (if (= n 0) 1 (* n (! (– n 1)))))
Имя функции – “!”, ее аргументом является переменная n. Лямбда-выражение, определяющее функцию, представляет собой условную if-форму, которая в случае n=0 возвращает 1, а в противном случае вычисляет произведение n и результата вызова этой же функции! для аргумента n–1.
Пример вызова этой функции:
(! 5)
Результат:120.
В случае повторяющихся вычислений в Лиспе могут быть использованы не только рекурсивные функции, но и известные по процедурным языкам циклы. Самым общим циклическим предложением в Лиспе является DO, имеющее следующую форму:
(DO ((пер1 знач1 шаг1) (пер2 знач2 шаг2)...)
(условие-окончания форма11 форма12...)
форма21 форма22...)
Вычисление предложения DO начинается с присваивания переменным пер1, пер2,... начальных значений знач1, знач2,... соответственно; потом вычисляется условие окончания и, если оно истинно, последовательно вычисляются формы форма1i, и значение последней возвращается как результат DO-предложения. В противном случае вычисляются формы форма2i из тела предложения DO, затем значения переменных пер1, пер2,... изменяются на величину шага шаг1, шаг2,... и все повторяется.
Для примера с помощью предложения DO определим функцию expt, вычисляющую n-ю степень числа х (n – целое положительное):
(defun expt (х n)
(do ((результат 1)); начальное значение
((= n 0) результат); условие окончания
(setq результат (* результат х))
(setq n (– n 1))))
Результат задания функции: EXPT.
Пример вызова:
(expt 2 3)
Результат: 8.
Итеративные (циклические) и рекурсивные программы теоретически одинаковы по своим вычислительным возможностям, однако свойства итеративных и рекурсивных вариантов программ могут существенно различаться. Рекурсивные программы более короткие и содержательные. Особенно полезно использовать рекурсию в тех случаях, когда решаемая задача и обрабатываемые данные по своей сути рекурсивны, например, при обработке списков, так как списки могут рекурсивно содержать подсписки, при работе с другими динамическими структурами, которые заранее не полностью известны. Рекурсивные процедуры играют важнейшую роль почти во всех программах, связанных с искусственным интеллектом.
2.1.6. Ввод-вывод данных
До сих пор рассматривался ввод и вывод данных в лисповских программах через параметры функций и свободные переменные. Для организации диалога человека с программой в Лиспе существуют специальные функции READ и PRINT.
Для вывода результатов можно использовать функцию PRINT. Это функция с одним аргументом, которая сначала вычисляет значение аргумента, а затем выводит это значение.
Например:
(PRINT (* 2 2))
Результат: 4.
Перед выводом происходит переход на новую строку.
Функция READ читает и возвращает выражение: (READ). Как только интерпретатор встречает такое предложение, вычисления приостанавливаются до тех пор, пока не будет введен какой-либо символ или целиком выражение. Аргументов у функции READ нет, ее использование построено на побочном эффекте, состоящем именно во вводе выражения. Прочитанное выражение можно сохранить для следующего использования и обработки, например, так:
(setq input (read));
прочитанное READ выражение присваивается переменной input.
Лисповские операторы ввода-вывода очень гибки, их можно использовать в качестве аргументов других функций. Для более эстетичного оформления вывода можно использовать функции PRINC, печатающую строку без окаймляющих кавычек и со специальными символами, а также TERPRI, переводящую строку.
Для форматного вывода (в соответствии с некоторым образом) существует функция FORMAT, обладающая гибкими возможностями, описанными в руководствах по языку Лисп.
Помимо стандартных устройств ввода-вывода, может осуществляться обработка файлов на магнитных носителях, загружаться из файлов определения функций и т.д.
2.1.7. Свойства символов
В Лиспе могут быть определены свойства символов. Список свойств имеет вид:
(имя_свойства1 значение1 имя_свойства2 значение2... имя свойстваN значениеN)
Присваивание нового свойства или изменение значения существующего осуществляется с помощью функции PUTPROP (или просто PUT):
(PUTPROP символ свойство значение)
Выяснить значение свойства, связанного с символом, можно с помощью функции GET:
(GET символ свойство)
С использованием этой функции можно также присваивать свойства символам:
(SETF (GET символ свойство) значение)
Свойства символов глобальны. Эта конструкция языка Лисп полезна во многих типичных случаях представления данных, в том числе семантических сетей, фреймов и объектов объектно-ориентированного программирования.
2.2. Пример программирования на языке Лисп
Рассмотрим в качестве примера программирования на языке Лисп менее элементарную классическую задачу, которая называется игры в “ханойские башни”.
Игра состоит в следующем. Используются три вертикальных стержня А, В, С и набор N дисков разного диаметра с отверстием посередине (их можно надевать на стержни). В начальном положении все диски надеты на стержень А по порядку убывания диаметров: внизу самый большой, над ним – поменьше и т.д., а наверху – самый маленький. Целью является перенос всех дисков со стержня А на стержень В по следующим правилам:
1) за один раз можно перенести только один диск;
2) больший по размеру диск нельзя положить на меньший;
3) третий стержень С можно использовать как вспомогательный.
Алгоритм решения задачи можно представить в виде трех следующих рекурсивных подзадач:
1) перенести со стержня А N–1 дисков на вспомогательный стержень С;
2) перенести нижний диск со стержня А на стержень В;
3) перенести со стержня С N–1 дисков на стержень В.
Программа состоит из трех последовательно определяемых функций “ханойские башни”, “перенос”, “выведи” и имеет вид:
(defun ханойские башни (высота)
(рrogn (перенос “а “b “с высота) “готово))
; сначала вызывается функция ПЕРЕНОС, которой передаются параметры a,b,c и значение
; высоты, затем возвращается ГОТОВО
ХАНОЙСКИЕ БАШНИ
(defun перенос (из в вспомогательный n)
; определение функции ПЕРЕНОС, где ИЗ, В, ВСПОМОГАТЕЛЬНЫЙ, n – формальные
; параметры
(cond
; выполняется при n=1
((= n 1) (выведи из в)
; вызывается рекурсивно функция ПЕРЕНОС, пока n не станет
равно 1
(t (перенос из вспомогательный в (– n 1))
(выведи из в)
(перенос вспомогательный в из (– n 1)))))
ПЕРЕНОС
(defun выведи (из в)
(format t “~S ~S~%”из в)); выводит ИЗ В
ВЫВЕДИ
Вызов функции “ханойские башни” дает такое решение:
(ханойские башни 3)
А В
А С
B C
А В
C A
C B
А В
готово
Можно убедиться, что определенная нами функция дает правильное решение для произвольного числа дисков, однако время решения задачи с увеличением числа дисков быстро возрастает.
2.3. Особенности применения языка Пролог
2.3.1. Общие сведения
Язык Пролог является представителем семейства языков логического программирования и в сравнении с традиционными языками программирования, предназначенными для записи алгоритмов, такими как Бейсик, Фортран, Паскаль, Си, обладает существенными особенностями:
программа на языке Пролог не является алгоритмом, а представляет собой запись условия задачи на языке формальной логики (т.е. это дескриптивный, описательный язык программирования);
язык Пролог предназначен не для решения вычислительных или графических задач, а для решения логических задач, для моделирования процесса логического умозаключения человека; вычисления же и графические построения выполняются в Прологе как побочный продукт логического вывода;
язык Пролог требует особого стиля мышления программиста, что затрудняет изучение его теми, кто уже привык к процедурному программированию, поэтому, так называемые, практические программисты не стремятся переходить на этот язык, что мешает росту популярности Пролога; однако во многих странах (Япония, Англия, Франция, Германия, Израиль и т.д.) расширяется практика применения Пролога в образовании как первого изучаемого языка программирования; переход к процедурным языкам типа Паскаля в этом случае трудностей не вызывает.
Все это позволяет отнести Пролог в существующем делении языков программирования на языки низкого и высокого уровней к языкам сверхвысокого уровня. В японском проекте создания в 90-х годах XX в. компьютеров 5-го поколения (обладающих искусственным интеллектом) Пролог положен в основу аппаратной организации и разработки программного обеспечения. Нынешний Пролог, безусловно, не является окончательным вариантом языка программирования ЭВМ 5-го поколения и в ближайшие годы получит существенное развитие. По-видимому, он сыграет роль Бейсика дескриптивного программирования: его значение и возможности в популяризации и распространении идей логического программирования чрезвычайно велики.
Изучению языка Пролог очень способствует предшествующее изучение математической логики, понятийной системой которой он пользуется.
Программирование на Прологе включает в себя следующие этапы:
1) объявление фактов об объектах и отношениях между ними;
2) определение правил взаимосвязи объектов и отношений между ними;
3) формулировка вопроса об объектах и отношениях между ними.
Имена – это последовательности букв и цифр, начинающиеся с буквы (строчной!). Системы программирования на Прологе для компьютеров допускают использование лишь латинских строчных и прописных букв: а.. z, A.. Z. Использование русских строчных и прописных букв: а.. я, А.. Я не допускается. При практической работе с интерпретатором рекомендуется, чтобы смысл имен оставался понятным, использовать в качестве имен запись русских слов латинскими буквами. В данном параграфе мы будем записывать все имена русскими буквами, чтобы сделать смысл программ наиболее понятным. При запуске этих программ в “англоязычных” системах программирования нужно заменять русские буквы в именах на латинские. Типы данных включают переменные, атомарные значения и структуры (рис. 2.1).
Примеры специальных атомов:
: - (обозначающая импликацию),
? (вопрос, обозначающий отрицание),
! (предикат отсечения, рассматривается далее).
Переменные обозначаются последовательностью букв и цифр, начинающейся с заглавной буквы. Особый вид переменной – анонимная переменная _, используемая в качестве аргумента предиката, когда конкретное значение переменной несущественно.
Структура – это конструкция, состоящая из имени структуры и заключенного в скобки списка ее аргументов, разделенных запятыми. Элементами структур могут быть числа, атомы, переменные, другие структуры и списки. Примеры структур: str(A,B,C), носит(юрий,пиджак).
Списки представляют собой объединение элементов произвольных видов, разделенных запятыми и заключенных в квадратные скобки. Списки отличаются от структур тем, что количество элементов может меняться при выполнении программы. Примеры списков: [1,3,5,7], [красный,желтый,зеленый].
Основная операция, выполняемая в языке Пролог, – это операция сопоставления (называемая также унификацией или согласованием). Операция сопоставления может быть успешной, а может закончиться неудачно. Определяется операция сопоставления так:
константа сопоставляется только с равной ей константой;
идентичные структуры сопоставляются друг с другом;
переменная сопоставляется с константой или с ранее связанной переменной (и становится связанной с соответствующим значением);
две свободные переменные могут сопоставляться (и связываться) друг с другом, с момента связывания они трактуются как одна переменная: если одна из них принимает какое-либо значение, то вторая немедленно принимает то же значение.
Примеры: 5 сопоставляется с 5, “имеет” сопоставляется с “имеет”, “сергей” не сопоставляется с “юрий”, “имеет(сергей,машина)” не сопоставляется с “имеет(сергей, телевизор)”, “имеет(сергей,машина)” сопоставляется с “имеет(Х,машина)”, в этом случае переменная Х получает в качестве значения атом “сергей”.
Факты – это предикаты с аргументами-константами, обозначающие отношения между объектами или свойства объектов, именованные этими константами. Факты в программе считаются всегда безусловно истинными и таким образом служат основой доказательства, происходящего при выполнении программы.
Пример 1. Факты, описывающие телефонные номера:
телефон(иванов,т561532).
телефон(петров,т642645).
телефон(Сидоров,т139833).
Это означает: телефон Иванова – 56-15-32 и т.п. Заметим, что перед цифрами номера идет буква “т”. Она делает номер телефона литерной константой, так как числа 561532, 642645, 139833 слишком велики, чтобы быть числовыми константами.
Пример 2. Факты, описывающие студентов:
нравится(сергей,рэп).
нравится(юрий,джаз).
носит(сергей,блейзер).
носит(юрий,пиджак).
Это означает: “Сергею нравится рэп”, “Юрию нравится джаз” и т.п.
Правила – это хорновские фразы с заголовком и одной или несколькими подцелями-предикатами. Правила имеют форму
<голова правила>:- <список подцелей>
где знак:- читается “если”, а список подцелей состоит из отдельных подцелей, разделенных знаком “запятая” (читаемым как “и”). Правила позволяют определить новые отношения между объектами на основе уже объявленных с помощью фактов. В качестве аргументов в предикатах правила могут использоваться не только константы, но и переменные. На переменные в правилах действуют кванторы общности, поэтому правила очень концентрированно и лаконично выражают конструкции логического вывода. Так, к фактам примера 2 можно добавить следующее утверждение:
круто