1. Исторический экскурс
2. Основные концепции языка Ада83
3. Основные концепции языка Ада95
4. Что еще имеется в языке Ада95
Заключение или летайте Ада-самолетами
Литература

Ни для кого не секрет, что наибольшей популярностью среди программистов в нашей стране пользуются языки Си и C++. Не углубляясь в анализ их сильных и слабых сторон, заметим, что существующая безальтернативность не самым лучшим образом влияет на общее положение дел в программировании. Если инструментальный язык известен с самого начала, то размышления об альтернативе становятся просто излишними. В целом ряде компаний и организаций они не только не приветствуются, но и просто запрещаются. Вместе с тем задуматься все-таки следует. Сошлемся на пример компании, которая написала и поддерживала около 3,5 млн. строк исходного кода на смеси языков Си и Ада более чем 10 лет. Специалисты этой компании имеют основательный опыт работы с языком Си и несколько меньший - с языком Ада. Тем не менее они определили, что стоимость в расчете на исполняемую строку на языке Ада в два раза меньше, чем для языка Си. При этом требовалось, чтобы на 70% меньше внутренних исправлений и на 90% меньше ошибок попало к заказчику. О предвзятости такой внутренней оценки говорить просто не приходится. В работе [1] была дана общая характеристика языка Ада, а сейчас имеет смысл обсудить некоторые детали языка Ада95. При этом всегда следует иметь в виду, что дьявол скрывается именно в деталях.

В начале 70-х годов МО США отметило тенденцию существенного удорожания программного обеспечения вычислительных систем военного назначения. В частности, в 1973 году стоимость ПО составила 46% общих затрат министерства на вычислительную технику. Несмотря на это во многих случаях качество программ не удовлетворяло предъявляемым к ним требованиям. Анализ показал, что можно было бы получить огромную экономию средств - около 24 млрд. долл. за период 1983-1999 гг., если МО США воспользуется единым языком программирования для решения всех своих задач вместо примерно 450 языков программирования и несовместимых диалектов.

1. Исторический экскурс

В результате проведенного конкурса победителем стал язык, получивший название Ада. Первое сообщение о нем было опубликовано в 1979 году [2] и поэтому его часто называют Ада79. Претерпев некоторые изменения, в 1980 г. появился документ [3], описывающий предварительный вариант языка Ада. Затем начался процесс подготовки ANSI-стандарта [4], который завершился его принятием в 1983 году [5], а в 1987 и международного стандарта языка [6, 7]. Именно этот язык и называют "язык программирования Ада", хотя в среде специалистов на него часто ссылаются как на язык Ада83.

В 1988 году после нескольких лет использования языка Ада было принято решение пересмотреть его в свете новых веяний в программировании. Само по себе такое решение не является ни плохим, ни хорошим. Это обычная практика - каждые 5-10 лет пересматривать язык, вносить в него изменения, дополнения и т. д. В результате было принято решение о разработке языка Ада9Х (Ада 90-х годов). Октябрь 1988 г. можно считать точкой отсчета проекта Ада9Х. Проект содержал три основные фазы: определение требований к пересмотренному языку; собственно разработка языка; переход от использования языка Ада83 к использованию языка Ада9Х.

Одно из главных требований к новому языку - оставить язык Ада83 неизменным. Это и понятно: все наработанное программное обеспечение не должно подвергаться каким-либо даже малейшим изменениям. Поэтому пересмотр языка шел лишь в направлении внесения в него дополнений. Шестая версия языка и стала в 1995 году одновременно ANS-I и ISO-стандартом [8]. Язык, описанный в этом документе, называют Ада95. Заметим, что только язык, удовлетворяющий данному стандарту, имеет право называться языком Ада95. Следовательно, язык Ада95 не может иметь ни подмножеств, ни расширений. Отметим еще один момент. Каждая версия языка Ада9Х сопровождалась документом, в котором содержались обоснование принимаемых решений и их защита. Окончательный вариант опубликован в работе [9].

2. Основные концепции языка Ада83

Рассмотрим принципиальные концепции, заложенные в язык программирования Ада83 [7,10] и получившие дальнейшее развитие в Ада95.

Программа на языке Ада представляет собой один или несколько программных модулей, которые могут компилироваться как совместно, так и раздельно. Программные модули бывают четырех видов: подпрограммы; пакеты; задачи; настраиваемые модули. Каждый модуль обычно состоит из двух частей: спецификации, содержащей логические понятия, используемые в данной программе, и тела, определяющего выполняемые программой действия. Такое разбиение модуля на спецификацию и тело, а также возможность раздельной компиляции позволяют разрабатывать, кодировать и тестировать любую программу как набор достаточно независимых компонентов. Эти особенности очень полезны при разработке больших программных систем и библиотек программ.

Основным средством для описания алгоритмов является подпрограмма, которая может иметь параметры. В языке различают два вида подпрограмм: процедуры и функции. Процедура - это логический аналог некоторой именованной последовательности действий. Функция - логический аналог математической функции - используется для вычисления какого-либо значения.

Пакет - это основное средство для определения набора логически связанных понятий. В простейшем случае в пакете специфицируются описания типов и общих объектов. В более общем случае в нем могут специфицироваться группы взаимосвязанных понятий, включающих подпрограммы. Некоторые части пакета могут быть "скрыты" от пользователя, что дает возможность доступа только к предоставляемым пакетом ресурсам.

Задача (или задачный модуль) - это средство для определения последовательности действий, которые могут выполняться параллельно. Задачи могут реализовываться на многомашинной или многопроцессорной вычислительной конфигурации, на мультикомпьютерах или транспьютерах либо на единственном процессоре посредством чередующегося выполнения. Синхронизация достигается путем обращения ко входам, которые подобно подпрограммам могут иметь параметры, с помощью которых осуществляется передача данных между задачами.

Настраиваемые модули - это средство для параметризации подпрограмм или пакетов. В ряде случаев возникает необходимость обрабатывать объекты, которые отличаются друг от друга числом данных, типами или какими-либо другими количественными или качественными характеристиками. Если все эти изменяемые характеристики вынести из подпрограммы или пакета, то получится некоторая заготовка (или шаблон), которую можно настроить на конкретное выполнение. Непосредственно выполнить настраиваемый модуль нельзя. Но из него можно получить экземпляр исходного настраиваемого модуля (подпрограмму или пакет), который уже пригоден для выполнения. Таким образом, в язык программирования Ада включено очень мощное средство для построения многоцелевых модулей.

2.1. Типы и подтипы

Тип в языке Ада трактуется традиционно: это множество значений плюс множество операций, которые можно выполнить над этими значениями. Сущность, ассоциированная с типом, называется объектом. По сути дела, тип - это шаблон, с помощью которого по некоторым правилам получаются однородные объекты. Таким образом, тип представляет собой инвариантное свойство, позволяющее определить, принадлежит ли данный объект некоторому классу. Более того, появляется возможность определить, "одинаковы" ли два объекта. Они считаются "одинаковыми", если имеют один и тот же тип.

Язык Ада строго типизирован:

1. каждый объект в языке принадлежит точно одному типу;

2. тип есть синтаксическое свойство - тип выражения можно определить из текста программы;

3. преобразование типа происходит путем преобразования значения одного типа в другой, и никакое неявное преобразование невозможно.

Схема иерархии типов языка Ада приведена на рис. 1. Отметим, что помимо предопределенных целых и плавающих типов программист может создать в своей программе любое число своих собственных целых, плавающих и фиксированных типов.

Picture 1

Рисунок 1.
Иерархия типов языка Ада.

Любой тип характеризуется множеством значений и множеством операций, которые разрешается выполнять над этими значениями. В языке программирования Ада множество значений объекта некоторого типа может зависеть от условия, которое называется ограничением. Подтип - это тип вместе с ограничением. Множество операций, определенных над некоторым типом, определено также над любым его подтипом. Но на множество значений подтипа накладывается ограничение: переменной данного подтипа можно присвоить значение только этого подтипа.

2.2. Раздельная компиляция и программная библиотека

В языке программирования Ада спецификации и тела программных модулей могут компилироваться раздельно. При этом обеспечивается тот же уровень проверки, как и в случае использования только одного программного модуля. Раздельная компиляция позволяет по-разному подходить к проектированию программных систем: монолитно и иерархически. В первом случае одна-единственная процедура содержит в себе все необходимое, и она является самодостаточной. Во втором разработчик имеет дело в основном со спецификациями, откладывая вопросы собственно программирования модулей на самый последний момент. Программный проект в этом случае мало зависит от реализации отдельных модулей. Анализ показывает, что это приводит к увеличению относительных трудозатрат на этапе проектирования (до 65% общих трудозатрат) и снижению трудозатрат на этапах реализации (до 27% общих трудозатрат), тестирования и отладки (до 8% общих трудозатрат).

Правила языка требуют, чтобы компилятор единообразно обрабатывал программу, состоящую из одного или нескольких компилируемых модулей. Из этого следует, что компилятор или его окружение должны поддерживать некоторый библиотечный файл, содержащий информацию о компилируемых модулях программной библиотеки, который используется для проверок и обновляется после каждой успешной компиляции. Если при компиляции модуля обнаруживается ошибка, то акт компиляции отклоняется, и никакого влияния на программную библиотеку он не оказывает. Особо отметим влияние программной библиотеки на надежность программного комплекса. В случае переработки программного модуля и его перекомпиляции все связанные с ним программные модули становятся неактуальными. Их также необходимо перекомпилировать. Поэтому никогда не возникает ситуация, когда оказываются смешанными модули различных версий.

2.3. Абстрактные типы данных

Для реализации абстрактных типов данных в языке Ада обычно используются пакеты, которые, по существу, задают механизм скрытия данных, препятствующее созданию зависимых от представления программ и случайному и/или предумышленному нарушению целостности объекта абстрактного типа данных.

При этом защита данных может быть "мягкой" и "жесткой". Например, внешние подпрограммы могут получить имя файла как результат вызова процедуры OPEN. Затем его можно использовать при обращениях к процедурам READ и WRITE. Следовательно, вне пакета имя файла, полученное после вызова процедуры OPEN, выполняет функцию пароля. Его внутренние свойства неизвестны, и другие операции, кроме описанных в видимой части пакета, над этим именем выполняться не могут. Подобные пакеты служат двум целям: они препятствуют использованию внутренней структуры типа вне пакета; с их помощью осуществляется "скрытие данных", над которыми определены только заданные в спецификации пакета операции.

2.4. Настраиваемые модули

Подпрограммы и пакеты в языке Ада могут быть настраиваемыми - они являются шаблонами, по которым создаются обыкновенные подпрограммы и пакеты. Например, возьмем процедуру сортировки, где заданы три формальных параметра настройки: тип компонентов, тип индекса и тип массива, причем последний зависит от двух предыдущих. В результате получается гибкая схема, которую можно легко настроить на любое применение. Процедура, в которой используется настраиваемый модуль сортировки, может иметь следующий вид:

with SORT;
procedure MAIN is
type REAL is digits 5;
type REAL_VECTOR is array 
                (NATURAL range < >) of REAL;
SORT_VECTOR:REAL_VECTOR 
                (1..100);
procedure SORT_REAL is new SORT 
                (COMPONENT_TYPE => REAL,
                        INDEX_TYPE => NATURAL,
                        SORT_ARRAY => REAL_VECTOR);
begin-MAIN
        -Ввод компонентов массива
        ...
        SORT_REAL (SORT_VECTOR);
        -Массив отсортирован
        -Вывод компонентов массива
        ...
end MAIN;
Если задать другую конкретизацию настройки, например
procedure SORT_INTEGER is new 
                SORT(COMPONENT_TYPE => INTEGER,
                INDEX_TYPE => NATURAL,
                SORT_ARRAY => INTEGER_VECTOR);

то получим другой экземпляр процедуры сортировки, предназначенный для сортировки целых.

2.5. Атрибуты

В языке программирования Ада имеется специальный класс предопределенных операций, с помощью которых программист может определять и использовать разнообразные характеристики типов и объектов. Они называются атрибутами. В языке Ада есть более 50 атрибутов. Их основное преимущество заключается в повышении степени переносимости программ. Если в программе использованы явные константы, то при модификации необходимо заменить эти явные константы на другие. Использование же атрибутов позволяет создавать гибкие и универсальные подпрограммы, не зависящие от характеристик конкретных обрабатываемых объектов.

2.6. Параллелизм

При создании программ, имеющих в своем составе параллельные процессы, программист должен обладать возможностями описания концептуально параллельных процессов. В языке программирования Ада конструкция, используемая для задания последовательности действий, которая может выполняться параллельно с другими последовательностями действий, называется задачей.

Выполнение программы, не содержащей задач, - это последовательное выполнение ее операторов одним логическим процессором. Параллельное выполнение задач - каждая задача выполняется отдельным логическим процессом независимо, за исключением точек синхронизации. Основным средством синхронизации являются вызовы входов и операторы принятия. Описание входа напоминает описание подпрограммы. Действия, которые выполняются в случае вызова входа, задаются соответствующим оператором принятия.

Механизм синхронизации задач в языке Ада является средством высокого уровня: в рамках одного примитива совмещены два действия - синхронизация и обмен. Низкоуровневые средства синхронизации параллельных процессов, такие как сигналы и семафоры, тривиально просто моделируются посредством механизма рандеву. Многочисленные примеры взаимодействия задач приведены в [7, 10].

2.7. Исключения

Трудно представить систему реального времени, которая бы в случае возникновения непредвиденных ситуаций аварийно завершала свою работу. Для обеспечения нормальной работоспособности подобной системы необходимы действия по ее восстановлению в случае отказа. Такое восстановление заменяет ту часть действий системы, которая не может быть выполнена нормально. Это, естественно, приводит к программной избыточности, но повышает надежность всей системы.

В языке программирования Ада ошибочные ситуации, возникающие в процессе выполнения программы, называются исключениями. При их возникновении Ада-система вырабатывает сигнал о наличии исключения, приостанавливает нормальное выполнение программы для обработки соответствующей ошибочной ситуации.

Обработку исключений проиллюстрируем на примере программы генерации псевдослучайных чисел с использованием линейного конгруэнтного метода. Очередное псевдослучайное число всегда лежит в диапазоне от 0 до 65 535, однако во время умножения могут получаться числа, выходящие за этот диапазон. В этих случаях возбуждается исключение NUMERIC_ ERROR и управление передается обработчику. В нем осуществляются переход на арифметику с большим диапазоном и последующее преобразование результата к типу INTEGER_16. Таким образом, если переполнение не происходит, то очередное псевдослучайное число генерируется в теле главной программы, а если оно все-таки произойдет, то в обработчике.

2.8. Спецификаторы представления

Язык программирования Ада можно рассматривать и как язык системного программирования, который содержит средства для манипулирования объектами на двух уровнях: абстрактном, где важны только логические свойства, и физическом, который имеет дело с аппаратной частью и способами представления.

Возможности, которыми обладает язык Ада для связи с аппаратурой, называются спецификаторами представления: спецификаторы представления типа и спецификаторы адреса. С их помощью программист может точно определять соответствие между типом данных и используемыми машинными средствами, специфицируя именно то представление, которое необходимо использовать, например размер выделяемой памяти, внутреннее представление перечислимого типа или формат типа записи, а также связывать объекты программы с определенными адресами памяти, размещать модули, начиная с заданного адреса и т. д.

Язык программирования Ада позволяет программисту обращаться к зависящим от реализации константам, описанным в предопределенном пакете SYSTEM (пакете системы программирования), который содержит данные об имени операционной системы, количество битов в кванте памяти и число доступных квантов памяти, наибольшее и наименьшее доступное целое число, а также другие характеристики системы.

3. Основные концепции языка Ада95

В данном разделе описываются наиболее важные дополнения, внесенные в язык Ада95 [8,9].

3.1. Программирование посредством расширений

Основная идея программирования посредством расширений [11,12] проста и понятна - дать программисту возможность определять новые типы, которые усовершенствуют существующие родительские типы путем наследования, добавления или модификации существующих компонентов и операций над ними. Основная цель такого подхода заключается в повторном использовании уже существующего программного обеспечения без перекомпиляции и повторного тестирования.

Расширение типа в языке Ада95 базируется на концепции производных типов языка Ада83, где позволяется наследовать операции от предков и добавлять новые операции. Добавлять же новые компоненты к типу не разрешается. В целом такой механизм представляется статическим. В языке Ада95 разрешается добавлять новые компоненты. Рассмотрим простой пример. Все геометрические фигуры имеют X- и Y-координаты. Поэтому можно определить корневой элемент иерархии:

type OBJECT is tagged
        record
                X_COORD: FLOAT;
                Y_COORD: FLOAT;
        end record;

Другие геометрические объекты могут базироваться на этом типе. Можно, например, определить тип CIRCLE (ОКРУЖНОСТЬ):

type OBJECT is tagged
        record
                X_COORD: FLOAT;
                Y_COORD: FLOAT;
        end record;

Тип CIRCLE теперь имеет три компонента: X_COORD, Y_COORD и RADIUS. Он наследует две координаты от типа OBJECT, а компонент RADIUS добавлен явно.

Иногда бывает удобно определить новый производный тип, не добавляя новых компонентов. Например:

type POINT is new OBJECT with 
                null record;

Фактически теперь один и тот же тип имеет два разных имени. Тем не менее поскольку речь идет о меченой записи (tagged record) обязательно должна быть указана последовательность зарезервированных слов - нулевых записей. Подобное указание позволяет отличать меченые записи от обычных традиционных. Попутно заметим, что в языке Ада95 появились шесть новых зарезервированных слов.

Предположим, что определена функция для вычисления расстояния между двумя точками:

function Distance(O_1, O_2: in 
                OBJECT) return FLOAT is
begin
        return SQRT((O_1.X_COORD - 
                O_2.X_COORD) ** 2 + 
                O_1.Y_COORD - O_2.Y_COORD) ** 2);
end DISTANCE;

Тип CIRCLE непосредственно наследует эту функцию. Несколько по-другому будут обстоять дела в случае вычисления площадей. Начинать нужно с определения функции:

function AREA(O: in OBJECT) 
                return FLOAT is
begin
        return 0.0;
end AREA;

которая возвращает нулевое значение для точки. Эта функция также наследуется типом CIRCLE, но вряд ли ее можно признать подходящей для данного случая. Поэтому необходимо определить другую функцию AREA с другим аргументом:

function AREA(O: in CIRCLE) 
                return FLOAT is
begin
        return PI*C.RADIUS**2;
end AREA;

которая будет перекрывать унаследованную операцию. При обращении

A := AREA(O);

будет вычисляться "площадь" точки, а при обращении

A := AREA(C);

будет вычисляться площадь круга.

Появляется также возможность конвертировать значения типа CIRCLE в значения типа OBJECT, и наоборот. Для преобразования значения типа CIRCLE в значения типа OBJECT достаточно записать:

O: OBJECT := (1.0, 2.0);
C: CIRCLE := (0.0, 0.0, 10.1);
...
O := OBJECT(C);

Третий компонент объекта C типа CIRCLE просто игнорируется. Для преобразования в другом направлении необходимо использовать агрегат

C := (O with 15.6);

Значение объекта O уже задано. Поэтому необходимо дополнительно задать только значение радиуса.

Рассмотрим более сложный пример. Предположим, что в программе требуется манипулировать геометрическими фигурами. При программировании на языке Ада83 использование записей с вариантами становится неизбежным (рис. 2).

package GEOMETRY is
        type SHAPES is (RECTANGLE, 
                TRIANGLE, CIRCLE);
        type GEOMETRIC_FIGURE 
                (THE_FIGURE) is
                record
                        X_COORD: FLOAT;
                        Y_COORD: FLOAT;
                        case THE_FIGURE is
                                when RECTANGLE =>
                                        WIDTH : FLOAT;
                                        HEIGHT: FLOAT;
                                when TRIANGLE =>
                                        SIDE1: FLOAT;
                                        SIDE2: FLOAT;
                                        ANGLE: FLOAT;
                                when CIRCLE =>
                                        RADIUS: FLOAT;
                        end case;
                end record;
function AREA(GEOM_FIG: in 
                GEOMETRIC_FIGURE) return FLOAT;
...
end GEOMETRY;

package body GEOMETRY is
        function AREA(GEOM_FIG: in 
                GEOMETRIC_FIGURE) return FLOAT is
        begin -AREA
                case GEOM_FIG.THE_FIGURE is
                        when RECTANGLE =>
                                return WIDTH * HEIGHT;
                        when TRIANGLE =>
                                return 0.5 * SIDE1 *
                                        SIDE2 * SIN(ANGLE);
                        when CIRCLE =>
                                return PI * SQR(RADIUS);
                end case;
        end AREA;
...
end GEOMETRY;

Рисунок 2.
Манипулирование геометрическими фигурами на языке Ада83.

Критический анализ этой программы показывает, что при ее модификации возникают очень серьезные проблемы. Если, например, необходимо добавить какую-либо новую геометрическую фигуру, то придется внести текстуальные изменения не только в определение типа SHAPES, но и в определение типа GEOMETRIC_FIGURE и в ряд других мест программы. Следовательно, спецификация пакета и его тело требуют тщательной переработки, перекомпиляции, повторной отладки и тестирования. Использование меченых записей языка Ада95 позволяет избежать всех этих неприятностей (рис. 3).

package NEW_GEOMETRY is
        type SHAPES is (RECTANGLE, 
                TRIANGLE, CIRCLE);
        type OBJECT is tagged
                record
                        X_COORD: FLOAT;
                        Y_COORD: FLOAT;
                end record;
        type RECTANGLE is new OBJECT with
                record
                        WIDTH : FLOAT;
                        HEIGHT: FLOAT;
                end record;
        function AREA(REC: in RECTANGLE)
                return FLOAT;
        type TRIANGLE is new OBJECT with
                record
                        SIDE1: FLOAT;
                        SIDE2: FLOAT;
                        ANGLE: FLOAT;
                end record;
        function AREA(TRI: in TRIANGLE) 
                return FLOAT;
        type CIRCLE is new OBJECT with
                record
                        RADIUS: FLOAT;
                end record;
        function AREA(CIR: in CIRCLE) 
                return FLOAT;
        ...
end NEW_GEOMETRY;

package body NEW_GEOMETRY is
        function AREA(REC: in RECTANGLE)
                return FLOAT is
        begin -AREA
                return WIDTH * HEIGHT;
        end AREA;

        function AREA(TRI: in TRIANGLE) 
                return FLOAT is
        begin -AREA
                return 0.5 * SIDE1 * SIDE2 * 
                        SIN(ANGLE);
        end AREA;

        function AREA(CIR: in CIRCLE) 
                return FLOAT is
        begin -AREA
                return PI * SQR(RADIUS);
        end AREA;
...
end NEW_GEOMETRY;

Рисунок 3.
Манипулирование геометрическими фигурами на языке Ада95.

Если теперь необходимо добавить новую геометрическую фигуру, которая наследует свойства треугольника, то делается это тривиально просто:

with NEW_GEOMETRY;
package NEW_NEW_GEOMETRY is
        type NEW_FIGURE is new TRIANGLE with
                record
                        -определение новых
                        -компонентов
                end record;
        function AREA(NEW_FIG: in 
                NEW_FIGURE) return FLOAT;
end NEW_NEW_GEOMETRY;
package body NEW_NEW_GEOMETRY is
        function AREA(NEW_FIG: in NEW_FIGURE)
                return FLOAT is
        begin -AREA
                return ...;
        end AREA;
end NEW_NEW_GEOMETRY;

Нетрудно видеть, что добавление новой геометрической фигуры происходит совершенно безболезненно. Перекомпиляция уже созданных программных модулей не требуется. Но еще более важно отсутствие повторного тестирования уже существующего программного обеспечения. Таким образом, язык Ада95 действительно предоставляет все возможности для программирования посредством расширений.

3.2. Классы

Рассмотренные средства предоставляют программистам возможность определять новые типы данных посредством расширения уже существующих типов. В приведенном примере были определены несколько видов геометрических фигур как индивидуальных, но связанных типов. Что еще необходимо, так это возможность манипулировать любым видом геометрических фигур и соответствующим образом обрабатывать их. Это можно сделать, введя понятие класса.

Для каждого меченого типа T существует ассоциированный тип T'CLASS - объединение всех типов в дереве наследования типов с корнем в T. Значения типа T'CLASS - это значения T и всех производных типов. Более того, значение любого производного от типа T неявно преобразуется к типу T'CLASS. Дерево типов для геометрических фигур показано на рис. 4. Значение любого типа неявно преобразуется к OBJECT'CLASS.

Picture 4

Рисунок 4.
Дерево геометрических объектов.

В языке Ада95 разрешено определять ссылочные типы, указывающие на классы. Ссылка, естественно, будет в различные моменты времени указывать на различные значения класса. Поэтому использование ссылочных типов является ключевым при обработке классов. Более того, параметры подпрограмм также могут быть классами, например:

procedure PROCESS_AREA(O: in out 
        OBJECT'CLASS) is
        ...
begin
        ...
        A:= AREA(O); -диспетчеризация в соответствии -со значением тега
        ...
end PROCESS_AREA;

В данном случае неизвестно, какая функция AREA будет вызвана во время выполнения. Выбор соответствующей подпрограммы в процессе выполнения называется диспетчеризацией.

3.3. Абстрактные типы и абстрактные подпрограммы

Цель введения абстрактных типов - заложить самый общий фундамент, на котором можно строить необходимые пользователю типы. Как правило, абстрактный тип используется в качестве родительского типа. Абстрактные подпрограммы - это своего рода заготовки для последующего определения операций над абстрактными типами. Абстрактные подпрограммы не имеют тел. Для них определяется только интерфейс. При наследовании свойств абстрактного типа абстрактные подпрограммы заменяются фактическими, которые, естественно, уже имеют тела. Диспетчеризация вызовов используется для определения конкретной подпрограммы, которая будет исполняться в данный момент времени.

Смысл введения абстрактных типов и абстрактных подпрограмм заключается в том, что проектируемая программная система описывается как совокупность абстрактных приложений, и в любой момент, подставив вместо абстрактных сущностей конкретные, программист получает готовую конкретную систему. Исходная абстрактная программная система оказывается легко настраиваемой на любые конкретные применения.

На рис. 5 приведена абстрактная спецификация множеств. Абстрактные функции определяют набор операций над множествами. У них нет тел и их нельзя вызвать непосредственно. Тем не менее их свойства можно наследовать.

package ABSTRACT_SETS is
        type SET is abstract tagged private;
        function EMPTY return SET is abstract;
                -пустое множество
        function UNIT(ELEMENT: 
                SET_ELEMTNT) return SET is 
                abstract;
                -конструктор множества
        function UNION(LEFT,RIGHT: SET) 
                return SET is abstract;
                -объединение двух множеств
        ...
        private
                type SET is abstract tagged null record;
end ABSTRACT_SETS;

Рисунок 5.
Абстрактная спецификация множеств.

Так, например, можно построить битовые множества (рис. 6), где все абстрактные сущности заменены конкретными типами и подпрограммами. В результате получается конкретный исполняемый вариант пакета.

with ABSTRACT_SETS;
package BIT_VECTOR_SET is
        type BIT_SET is new 
                ABSTRACT_SETS.SET with private;
        function EMPTY return BIT_SET;
        function UNIT(ELEMENT: 
                SET_ELEMTNT) return BIT_SET;
        function UNION(LEFT,RIGHT: 
                BIT_SET) return BIT_SET;
        ...
        private
                BIT_SET_SIZE: constant :=64;
                type BIT_VECTOR is
                        array(SET_ELEMENT range
                                0..BIT_SET_SIZE - 1) of 
                                BOOLEAN;
                type BIT_SET is new ABSTRACT_SETS.SET with
                        record
                                DATA: BIT_VECTOR;
                        end record;
end BIT_VECTOR_SET;

Рисунок 6.
Спецификация битовых множеств.

Отметим, что при использовании языка Ада83 можно получить практически тот же эффект - все базовые типы данных (определенные в языке Ада95 как абстрактные) можно сосредоточить в одном модуле, а в остальных модулях рассматривать только внешний эффект этих типов через их простое именование. И в том и в другом случае информационные объекты программной системы создаются по принципу инкапсуляции. Но использование явной языковой конструкции оказывается предпочтительным при модификации и сопровождении системы.

3.4. Динамический отбор

В языке Ада95 ссылочные типы могут указывать не только на типы или подтипы, но и на подпрограммы. Значение ссылочного объекта в этом случае создается с помощью атрибута ACCESS. Можно, например, написать:

type MATH_FUNCTION is access 
        function(F: FLOAT) return FLOAT;
M               : MATH_FUNCTION;
VALUE, ARG: FLOAT;

Ссылочный объект M может теперь указывать на математические функции, такие как SIN, LOG, SQRT и т. д. Если необходимо вычислить значение COS(ARG), то следует написать:

M := COS'ACCESS;

Вызов самой функции осуществляется неявно:

VALUE := M(ARG);

или без сокращений как

VALUE := M.all(ARG);

Рассмотренный механизм помимо действительно динамического отбора выполняемой подпрограммы снимает ограничение языка Ада83 на передачу подпрограмм в качестве параметров других подпрограмм.

Другим расширением ссылочного типа в языке Ада95 является так называемый обобщенный ссылочный тип. Теперь можно написать:

type INT_PTR is access all INTEGER;

в результате чего появляется возможность присвоить ссылку на любой объект типа INTEGER объекту ссылочного типа INT_PTR. Но при этом объект типа INTEGER должен быть определен как aliased (иначе названный тип INTEGER). Итак, если написано:

IP: INT_PTR;
I : aliased INTEGER;
...
IP := I'ACCESS;

то можно изменять значения объекта I посредством манипуляции ссылочным объектом IP.

3.5. Объектно-ориентированное программирование

Мы не собираемся детально обсуждать детали объектно-ориентированной парадигмы и способы ее воплощения в различных языках программирования. Дадим лишь ссылку на статью [13]. Наша цель - обсуждение объектно-ориентированного программирования на языке Ада95.

Язык Ада83 традиционно ассоциируется с объектно-ориентированным программированием. Действительно, в нем присутствуют все ингредиенты, присущие ОО языкам. Вместе с тем вопросы наследования и полиморфизма поддерживаются в нем в объеме, недостаточном с точки зрения современных воззрений. Осознание сложившегося положения привело к тому, что в язык Ада95 введены понятия программирования посредством расширений, классов, абстрактных типов и подпрограмм, динамического отбора и обобщенного ссылочного типа. В нынешнем виде он устроит даже гурманов "объектно-ориентированной кухни".

В качестве примера объектно-ориентированного программирования на языке Ада95 приведем спецификацию пакета, определяющего абстрактную очередь (рис. 7).

package QUEUES is
        type QUEUE is limited private;
        type QUEUE_ELEMENT is abstract 
                tagged private;
        type ELEMENT_PTR is access all 
                QUEUE_ELEMENT'CLASS;
        function IS_EMPTY(Q: QUEUE) 
                return BOOLEAN;
        procedure ADD_TO_QUEUE(Q: access 
                QUEUE; E: in ELEMENT_PTR);
        function REMOVE_FROM_QUEUE(Q: 
                access QUEUE) return ELEMENT_PTR;
private
        type QUEUE_ELEMENT is tagged
                record
                        NEXT: ELEMENT_PTR:= NULL;
                end record;
        type QUEUE is limited
                record
                        HEAD: ELEMENT_PTR:= NULL;
                        TAIL: ELEMENT_PTR:= NULL;
                end record;
end QUEUES;

Рисунок 7.
Абстрактная очередь.

С помощью этого пакета можно создавать не только гомогенные, но и гетерогенные очереди, поскольку тип ELEMENT_PTR определен как обобщенный ссылочный тип на класс. Если необходимо создать очередь из упоминавшихся ранее геометрических фигур, то сделать это можно, например, так:

type FIGURE_PTR is access all 
                OBJECT'CLASS;
type FIGURE_ELEMENT is new 
                QUEUE_ELEMENT with
        record
                THE_PTR: FIGURE_PTR;
        end record;
...
type QUEUE_PTR is access all QUEUE;
THE_QUEUE: QUEUE_PTR:= new QUEUE;
...
NEW_FIG: FIGURE_PTR:= new 
        CIRCLE'(0.0, 0.0, 1.0);
NEW_ELEMENT: ELEMENT_PTR:=
        new FIGURE_ELEMENT'(QUEUE_ELEMENT 
                with NEW_FIG);
ADD_TO_QUEUE(THE_QUEUE, NEW_ELEMENT);

Заметим, что приведенный способ построения гетерогенной очереди далеко не единственный. Использование языка Ада95 позволяет перевести программирование в несколько иную плоскость: разработку абстракций и механизмов манипулирования ими для конкретных предметных областей с последующей их конкретизацией для заданного применения.

3.6. Наследование

Производные типы языка Ада83 представляют собой простейший механизм наследования: наследуются структура, операции и значения родительского типа. Такое наследование позволяет определять дополнительные операции, но не расширять структуру.

Механизм меченых записей в языке Ада95 дает возможность осуществлять так называемое единичное наследование. Более интересным представляется анализ множественного и смешанного наследований. Множественное позволяет наследовать свойства более чем одного родительского класса. Например, ограниченный стек может быть спроектирован как наследник двух классов: стек и массив. Для решения этой задачи достаточно средств языка Ада83: композиции типов вместо наследования - можно реализовать один тип в терминах другого и скрыть эту реализацию как личный тип. Ничто, впрочем, не мешает определить абстрактный тип STACK и объявить ограниченный стек как его наследника. Подобный метод уже обсуждался на примере абстрактных множеств. Какой из двух предложенных методов лучше - дело вкуса. Для каждого из них найдется как апологетика, так и критика.

Другой вид множественного наследования называют смешанным. В этом случае один из родительских классов не имеет сущностей своего собственного типа и существует только для обеспечения набора свойств, наследуемых от него. Как правило, такого рода абстракция создается специально для последующего комбинирования с другими классами. В языке Ада95 смешанное наследование реализуется путем использования меченых типов (единичное наследование) и настраиваемых модулей. Настраиваемый шаблон определяет смешение, а тип фактического параметра настройки - родителя. Таким образом, можно написать:

generic
        type S is abstract tagged private;
package P is
        type T is abstract new S with private;
                -операции над типом T
private
        type T is abstract new S with
                record
                        -новые компоненты
                end record;
end P;

Конкретизацию настройки пакета P теперь можно использовать для добавления новых операций над типом T для любых существующих меченых типов. Результирующий тип будет соответствовать классу типа, передаваемому как фактический параметр. Такой подход позволяет определять каскад типов, добавляя произвольное число свойств к первоначальному типу. В конце концов определяется тип, который является конкретным, а не абстрактным.

3.7. Иерархические библиотеки

Может возникнуть ситуация, когда необходимо написать два логически различных пакета, которые используют одни и те же личные типы, и при этом необходимо обеспечить целостность данных при их совместном использовании. Это невозможно сделать в языке Ада83. Действительно, если тип определяется не как личный, то оба пакета могут использовать его совершенно непредсказуемо, и все пакеты-клиенты также имеют доступ к этому типу. Как результат абстракция разрушается. С другой стороны, если необходимо поддерживать абстракцию, то можно слить два пакета в один монолит, что, естественно, плохо с точки зрения методологии программирования. В языке Ада95 подобная проблема решается с помощью иерархических библиотек.

Рассмотрим пакет для манипулирования комплексными числами:

package COMPLEX_NUMBERS is
        type COMPLEX is private;
        function "+" (LEFT, RIGHT: 
                COMPLEX) return COMPLEX;
        function REAL_PART (X: COMPLTX) 
                return FLOAT;
        function IMAGE_PART (X: COMPLTX) 
                return FLOAT;
private
        ...
end COMPLEX_NUMBERS;

Данный пакет позволяет манипулировать декартовыми комплексными числами. Их конкретное представление скрыто от пользователя в личной части пакета. Если же возникает необходимость манипулировать комплексными числами в полярных координатах, то в языке Ада95 это можно сделать, определив пакет-наследник:

package COMPLEX_NUMBERS.POLAR is
        function POLAR_TO_COMPLEX (R, 
                ANGLE: FLOAT) return COMPLEX;
        function RADIUS (R: COMPLTX) return FLOAT;
        function ANGLE (A: COMPLTX) return FLOAT;
end COMPLEX_NUMBERS.POLAR;

Внутри тела этого пакета разрешен доступ к объекту личного типа COMPLEX. Теперь любой пакет-клиент может использовать операции не только над декартовыми комплексными числами, но и операции над комплексными числами в полярных координатах. Необходимо лишь указать спецификатор использования:

with COMPLEX_NUMBERS.POLAR;
package CLIENT is
        ...
end CLIENT;

Каждый пакет может иметь несколько наследников. Так, в частности, пакет для манипулирования комплексными числами можно реализовать в виде трех пакетов: родительский пакет содержит личный тип и четыре арифметических операции, а два пакета-наследника позволяют по-разному взглянуть на комплексные числа.

4. Что еще имеется в языке Ада95

По сравнению с документом [5], описывающим стандарт языка Ада83, документ [8] по стандарту языка Ада95 вырос вдвое. Дать полный и исчерпывающий анализ поэтому не представляется возможным. Для полноты изложения приведем только список тех новых возможностей, которые не были описаны в данной статье:

  • технические усовершенствования, касающиеся типов, операторов, подпрограмм, пакетов и правил видимости;
  • защищенные типы;
  • дополнительные предопределенные средства окружения;
  • интерфейсы с языками КОБОЛ, ФОРТРАН и Си;
  • системное программирование;
  • системы реального времени;
  • распределенные системы;
  • информационные системы;
  • числа и вычисления;
  • надежность и безопасность.
  • Заметим, что не все предложения проекта Ада9Х, вошли в язык Ада95. Уже в ближайшее время следует ожидать начала работы над проектом Ада0Х, окончательная редакция которого ожидается в 2005 году.

    Заключение или летайте Ада-самолетами

    20 марта 1997 года Президент Российской Федерации Б. Н. Ельцин отправился в Хельсинки на встречу с Президентом США Б. Клинтоном на новом президентском самолете ИЛ-96-300. Об этом факте наслышаны многие. Но лишь очень немногие знают, что вся система управления этим самолетом написана на языке Ада. Вся система управления новейшим широкофюзеляжным самолетом Боинг-777 также написана на языке Ада. Уместно в связи с этим процитировать Г. Буча [13]: "Многие из систем управления воздушным движением следующего поколения разрабатываются на языке Ада; как человек, которому приходится часто летать, я чувствую себя очень комфортно, зная это!"


    Литература

    [1] О. Перминов, К. Перминов. Ада - язык разработки больших программных комплексов реального времени. Открытые системы, # 6, 1996.

    [2] Preliminary Ada Reference Manual, Sigplan Notices, vol. 14, # 6, 1979.

    [3] Reference Manual for the Ada Programming Language, United States Department of Defence, 1980.

    [4] Reference Manual for the Ada Programming Language, United States Department of Defence, 1982.

    [5] Ada programming language. American National Standards Institute, Inc. ANSI/MIL-STD-1815A-1983.

    [6] International Standard programming languages-Ada. ISO 8652:1987.

    [7] Джехани Н. Язык Ада: пер. с англ./Под ред. А.А. Красилова и О.Н. Перминова. - М.: Мир, 1988.

    [8] Ada95 Reference Manual. The Language. The Standard Libraries. International Standard ANSI/ISO/IEC-8652: 1995, January 1995.

    [9] Ada95 Rationale. The Language. The Standard Libraries. January 1995.

    [10] Перминов О.Н. Программирование на языке Паскаль. - М.: Радио и связь, 1988.

    [11] N.Wirth. Type extension. ACM Transactions on Programming Languages and Systems, vol. 10, # 2, 1988.

    [12] Н. Вирт. Долой "жирные" программы. Открытые системы, # 6, 1996.

    [13] В. Аджиев. Объектная ориентация:философия и футурология. Открытые системы, # 6, 1996.