Язык Ада строго и однозначно определен в стандарте языка ISO и не допускает каких‐либо расширений и дополнений со стороны разработчиков компиляторов. Но прогресс не стоит на месте, и, вместе с развитием аппаратной базы, компьютерной науки и практики, язык должен развиваться. Регулярно, раз в десятилетие, происходит пересмотр стандарта языка.
Предыдущий пересмотр состоялся в 1995г и внес в язык Ада такие возможности, как объектно‐ориентированное программирование путем расширений типов, иерархические библиотеки и защищенные объекты. Целью нового пересмотра стандарта является утверждение языка Ада в таких качествах, как надежность, производительность, гибкость, переносимость, взаимодействие с другими языками и поддержка программирования задач реального времени.
В этом обзоре мы рассмотрим подробнее предлагаемые нововведения.
К расширениям в области ООП относятся: интерфейсы, новая нотация вызовов операций, вложенные расширения типов, родовой конструктор.
Во время разработки стандарта Ада 95 было много споров о необходимости введения возможности множественного наследования, но из‐за чрезмерной сложности реализации решено было оставить лишь простое наследование. С тех пор удалось найти удачный компромисс — механизм интерфейсов. При его использовании разрешается иметь множественное наследование для спецификаций, но единичное для реализации. Интерфейс предоставляет только определения операций, без их реализации. Такие определения выполнены либо как абстрактные, либо как пустые (null) подпрограммы. Тип может реализовывать несколько интерфейсов, но наследовать реализацию может только от одного родительского типа. Таким образом достигается удобство множественного наследования при минимальной трудности реализации.
type Widget is tagged record Object : Gtk.Widget.Gtk_Widget; end record; procedure Draw (Object : Widget) is abstract; type Observer is interface; procedure Notify (Object : Observer; Cause : access Widget'Class) is null; type Container is interface; procedure Add (Object : in out Container; Item : access Widget'Class) is abstract; type Window is new Widget and Observer and Container with private; procedure Draw (Object : Window); procedure Add (Object : in out Window; Item : access Widget'Class); procedure Notify (Object : Window; Cause : access Widget'Class);
Мощь аппарата множественного наследования проявляется в динамическом связывании и полиморфизме при использовании надклассовых типов над интерфейсами. Также в новом стандарте расширены операции тестирования на принадлежность к классу для проверки, реализует ли данный объект нужный интерфейс или нет.
Кроме обычных (процедурных) интерфейсов вводятся также синхронные. Синхронные интерфейсы могут быть реализованы только задачными и защищенными типами.
Введен новый тип описаний процедур — пустые (null). Они эквивалентны процедурам с телом из одной пустой инструкции. Кроме уже упоминавшегося применения при спецификации интерфейсов, пустые процедуры также могут быть использованы как параметры по умолчанию в настраиваемых модулях. Такие параметры могут быть опущены при конкретизации модуля.
generic with procedure Start_Document is null; with procedure End_Document is null; with procedure Start_Element (Name : String; Attr : Attributes) is null; with procedure End_Element (Name : String) is null; package SAX_Parser is ... end SAX_Parser; package My_Parser is new SAX_Parser (End_Element => Process_End_Element);
Теперь в языке поддерживается новая нотация вызова операций, часто используемая в других ООП языках. Вызов может быть записан в форме ОБЪЕКТ.ОПЕРАЦИЯ (Аргументы). Для использования этой нотации в описании примитивной операции тэгового типа управляющий аргумент должен стоять первым.
Использование новой нотации может улучшить читаемость программ. При использовании старой нотации часто приходилось явно указывать пакет, в котором описана операция или использовать спецификатор use. Это приводило в первом случае к громоздкой записи вызова операций, во втором — к чрезмерному использованию спецификаторов видимости. Зачастую определить пакет, где определена данная операция, бывает трудно, т.к. наследуемые операции могут определяться неявно, а надклассовые операции часто описаны вместе с базовым типом, а не с производным.
По правилам языка Ада 95 расширение типа должно находиться на том же лексическом уровне, что и описание родительского типа. В новом стандарте это ограничение ослаблено, и введены необходимые проверки, чтобы нельзя было вернуть объект нового типа (или указатель на него) из подпрограммы, где этот тип описан.
Новая настраиваемая функция Ada.Tags.Generic_Dispatching_Constructor позволяет создавать тип из заданной иерархии тэговых типов по заданному тэгу типа. Это полезно при чтении данных из внешних источников, потоков, XML файлов и т.д. Конкретизация данной функции исполняет роль, аналогичную методам «factory» в других ООП языках.
Ссылочные типы в Ада 95 обладают отличной надежностью. В то же время их использование накладывает некоторые ограничения и иногда требует многих преобразований типов. Нововведения в стандарте позволяют использовать ссылочные типы более гибко.
Ссылочные типы делятся на именованные и анонимные. В языке Ада 95 существует заметная асимметричность между этими типами. Например, для анонимных типов нельзя задать пустое значение, или ограничить указываемые объекты константами. В новом стандарте ссылочные типы, как именованные, так и анонимные, стали более выразительными. Любой ссылочный тип можно ограничить так, что он не будет иметь пустых значений. Можно ограничить анонимные типы ссылками на константы.
procedure Run_1 (Ptr : access Object); procedure Run_2 (Ptr : access constant Object); procedure Run_3 (Ptr : access all Object); procedure Run_4 (Ptr : not null access Object); procedure Run_5 (Ptr : not null access constant Object); procedure Run_6 (Ptr : not null access all Object);
Введены анонимные ссылки на подпрограммы. Использование анонимных ссылок на подпрограммы решает проблему с ограничением уровня вложенности. Например, при использовании именованных ссылочных типов нельзя присвоить ссылку на процедуру, находящуюся на более вложенном уровне, чем сам ссылочный тип. Вместо этого можно использовать анонимный тип. Этот прием широко используется в новой библиотеке контейнеров.
Анонимные типы теперь могут использоваться не только в параметрах и дискриминантах, но и в компонентах сложных типов, как результат, возвращаемый функцией, и в переименованиях. Широкое использование анонимных типов позволяет избежать явного преобразования типов, сохраняя при этом контроль над областью видимости. Отпадает необходимость в неполных описаниях типов при построении ссылок объекта на самого себя.
type List_Item is record Next : access all List_Item; Content : Value; end record;
В то время, как Ада 95 дает возможность строить иерархии пакетов, позволяя каждому модулю описывать отдельную абстракцию, все же остается одна проблема, ограничивающая подобную декомпозицию. Мы должны описывать циклически определенные типы в одном пакете. В новом стандарте языка Ада найдено решение, позволяющее избежать этого. Новый спецификатор видимости «limited with» позволяет внутри модуля иметь доступ к ограниченному виду другого модуля, не создавая синтаксической зависимости между модулями. В этот вид входят только неполные описания (incomplete) типов и дочерние пакеты. Таким образом, несколько пакетов могут иметь ссылки на типы друг друга, формирующие циклическую структуру, если хотя бы один пакет имеет спецификатор «limitied with».
limited with Users; package Cars is type Car is record Owner : access all Users.User; end record; ... end Cars; with Cars; package Users is type User is record Car : access all Cars.Car; end record; ... end Users;
Благодаря новой форме спецификатора видимости «private with» появилась возможность использовать определения из приватных пакетов в приватной части обычных пакетов. В Аде 95 приватные пакеты могли использоваться только в телах и спецификациях других приватных пакетов, что зачастую вызывало некоторые неудобства.
private package My_OS.Implementation is type File_Node is private; ... end Cars; private with My_OS.Implementation; package My_OS.File_Operations is type File is private; procedure Open (Object : out File); private type File is access Implementation.File_Node; end My_OS.File_Operations;
Приватный тип не может участвовать в конкретизации настраиваемых модулей до тех пор, пока не будет предоставлено полное описание типа. Новая форма конкретизации помогает преодолеть это ограничение. Выглядит это следующим образом. В видимой части пакета в конкретизации участвует приватный тип и добавляется фраза with private. В приватной части пакета, после полного описания типа, конкретизация повторяется.
with Lists; package Nodes is type Node is private; package Node_Lists is new Lists (Node) with private; private type Node is record ...; end record; package Node_Lists is new Lists (Node); end Nodes;
Основное назначение лимитированных типов — обеспечить невозможность копирования объектов данного типа. В то же время в языке Ада 95 лимитированные типы обладают рядом ограничений, напрямую не связанных с невозможностью копирования. В новом стандарте эти ограничения ослаблены, теперь для инициализации объектов лимитированных типов можно использовать агрегаты и вызовы функций.
protected type Lock is entry Allocate; entry Free; end Lock; type Locked_Value is limited record Locker : Lock; Value : Integer := 0; end record; Value : Locked_Value := (Lock => <>, Value => 1.0);
С введением агрегатов для лимитированных типов был расширен их синтаксис. Теперь в агрегатах для компонент можно указывать инициализацию по умолчанию. Для компонент задачного и защищенного типов это единственно возможный вариант.
Для конструирования объектов, возвращаемых функцией, расширен синтаксис инструкции return. Расширенная форма представляет собой составную инструкцию, которая явно определяет возвращаемый объект и выполняет произвольные действия для его инициализации.
function Create_Value return Locked_Value is begin return New_Object : Locked_Value do New_Object.Value := Next_Value; New_Object.Lock.Allocate; end return; end Create_Value; Value : Locked_Value := Create_Value;
В новом стандарте была значительно расширена и доработана стандартная библиотека языка. Перечислим введенные изменения.
Векторы и матрицы. Введены настраиваемые пакеты для работы с векторами и матрицами. Пакеты поддерживают арифметические операции, транспонирование и обращение матриц, нахождение детерминанта, решение системы линейных уравнений. Все это является хорошо проработанным материалом, стандартизованным ранее в отдельном ISO-13813. Теперь это стало доступно, как часть языка Ада.
Пакет Ada.Directories вводит единообразный механизм работы с файлами и каталогами, независимый от целевой операционной системы.
Пакет Ada.Environment_Variables предоставляет доступ к переменным окружения операционной системы.
В пакетах Ada.Strings.Unbounded, Ada.Strings.Bounded введены варианты функций Index и Slice с типами Unbounded_String и Bounded_String, для сокращения количества преобразований в тип String. Добавлен пакет Ada.Text_IO.Unbounded_IO и функция Get_Line в пакет Ada.Text_IO.
Расширен диапазон символьных типов. Введены новые типы Wide_Wide_Character и Wide_Wide_String, позволяющие хранить 32‐битные Unicode. В лексике языка также произошли изменения. Теперь правила формирования идентификаторов взяты из стандарта Unicode. В итоге, мы можем свободно писать идентификаторы на любом языке. В пакет Ada.Numerics введена константа с именем «греческая пи».
π : constant := Pi;
Библиотека контейнеров Ada.Containers содержит настраиваемые типы векторов, связных списков, хешей и множеств. Она включает алгоритмы итераций, поиска и сортировки.
Расширена работа с датой/временем, введены временные зоны, форматирование и арифметические операции.
Появилась возможность явного контроля перегрузки операций. При описании примитивной операции можно указать фразу «overloading» и компилятор проверит, что операция действительно перегружает унаследованную операцию родительского типа. При указании «not overloading» перегрузка не произойдет даже, если родительский тип имел соответствующую операцию. Эта возможность особенно важна для настраиваемых модулей, когда заранее не известен родительский тип и набор его примитивных операций.
type Child is new Parent with private; overloading procedure Run (Item : Child); generic type Parent is private; package Extend_Parent type Child is new Parent with private; not overloading procedure New_Action (Item : Child); private ... end Extended_Parent;
Стандартизованы директивы компилятору Assert, Assertion_Policy, Unsuppress, No_Return.
Большое количество нововведения касаются поддержки программирования систем реального времени. Вкратце перечислим основные.
Десятилетняя работа по улучшению языка вылилась в новый стандарт, позволяющий более гибко, легко и выразительно пользоваться возможностями языка Ада, сохраняя, и даже увеличивая при этом надежность и эффективность получаемых программ. Стандартизация новой версии языка выходит на финишную прямую. Черновик стандарта доступен для общественности на сайте www.adaic.com. Многие из новых возможностей уже доступны пользователям компилятора GNAT (Profession и GAP редакций). Очевидно, в скором будущем стандарт вступит в силу, появятся полные его реализации, и таким образом, завершится очередной цикл в развитии замечательного языка Ада.