Rationale for Ada 2005: Introduction

RUSTOP
BACKNEXT

ENG

3.3 Structure, visibility, and limited types

@ Structure is vital for controlling visibility and thus abstraction. There were huge changes in Ada 95. The whole of the hierarchical child unit mechanism was introduced with both public and private children. It was hoped that this would provide sufficient flexibility for the future.

@ But one problem has remained. Suppose we have two types where each wishes to refer to the other. Both need to come first! Basically we solve the difficulty by using incomplete types. We might have a drawing package concerning points and lines in a symmetric way. Each line contains a list or array of the points on it and similarly each point contains a list or array of the lines through it. We can imagine that they are both derived from some root type containing printing information such as color. In Ada 95 we might write

  1        type Object is abstract tagged
  2                record
  3                        Its_Color : Color;
  4                        ...
  5                end record;
  6
  7        type Point;
  8        type Line;
  9        type Acc_Point is access all Point;
 10        type Acc_Line is access all Line;
 11        subtype Index is Integer range 0 .. Max;
 12        type Acc_Line_Array is array (1 .. Max) of Acc_Line;
 13        type Acc_Point_Array is array (1 .. Max) of Acc_Point;
 14
 15        type Point is new Object with
 16                record
 17                        No_Of_Lines : Index;
 18                        LL          : Acc_Line_Array;
 19                        ...
 20                end record;
 21
 22        type Line is new Object with
 23                record
 24                        No_Of_Points : Index;
 25                        PP           : Acc_Point_Array;
 26                        ...
 27                end record;

@ This is very crude since it assumes a maximum number Max of points on a line and vice versa and declares the arrays accordingly. The reader can flesh it out more flexibly. Well this is all very well but if the individual types get elaborate and each has a series of operations, we might want to declare them in distinct packages (perhaps child packages of that containing the root type). In Ada 95 we cannot do this because both the incomplete declaration and its completion have to be in the same package.

@ The net outcome is that we end up with giant cumbersome packages.

@ What we need therefore is some way of logically enabling the incomplete view and the completion to be in different packages. The elderly might remember that in the 1980 version of Ada the situation was even worse - the completion had to be in the same list of declarations as the incomplete declaration. Ada 83 relaxed this (the so-called Taft Amendment) and permits the private part and body to be treated as one list - the same rule applies in Ada 95. We now go one step further.

@ Ada 2005 solves the problem by introducing a variation on the with clause - the limited with clause. The idea is that a library package (and subprogram) can refer to another library package that has not yet been declared and can refer to the types in that package but only as if they were incomplete types. Thus we might have a root package Geometry containing the declarations of Object, Max, Index, and so on and then

  1        limited with Geometry.Lines;
  2
  3        package Geometry.Points is
  4                type Acc_Line_Array is array (1 .. Max) of access Lines.Line;
  5                type Point is new Object with
  6                        record
  7                                No_Of_Lines : Index;
  8                                LL          : Acc_Line_Array;
  9                                ...
 10                        end record;
 11                ...
 12        end Geometry.Points;

@ The package Geometry.Lines is declared in a similar way. Note especially that we are using the anonymous access type facility discussed in Section 3.2 and so we do not even have to declare named access types such as Acc_Line in order to declare Acc_Line_Array.

@ By writing limited with Geometry.Lines; we get access to all the types visible in the specification of Geometry.Lines but as if they were declared as incomplete. In other words we get an incomplete view of the types. We can then do all the things we can normally do with incomplete types such as use them to declare access types. (Of course the implementation checks later that Geometry.Lines does actually have a type Line.)

@ Not only is the absence of the need for a named type Acc_Line a handy shorthand, it also prevents the proliferation of named access types. If we did want to use a named type Acc_Line in both packages then we would have to declare a distinct type in each package. This is because from the point of view of the package Points, the Acc_Line in Lines would only be an incomplete type (remember each package only has a limited view of the other) and thus would be essentially unusable. The net result would be many named access types and wretched type conversions all over the place.

@ There are also some related changes to the notation for incomplete types. We can now write

  1        type T is tagged;

@ and we are then guaranteed that the full declaration will reveal T to be a tagged type. The advantage is that we also know that, being tagged, objects of the type T will be passed by reference. Consequently we can use the type T for parameters before seeing its full declaration. In the example of points and lines above, since Line is visibly tagged in the package Geometry.Lines we will thus get an incomplete tagged view of Lines.

@ The introduction of tagged incomplete types clarifies the ability to write

  1        type T_Ptr is access all T'Class;

@ This was allowed in Ada 95 even though we had not declared T as tagged at this point. Of course it implied that T would be tagged. In Ada 2005 this is frowned upon since we should now declare that T is tagged incomplete if we wish to declare a class wide access type. For compatibility the old feature has been retained but banished to Annex J for obsolescent features.

@ Further examples of the use of limited with clauses will be given in a later paper.

@ Another enhancement in this area is the introduction of private with clauses which overcome a problem with private child packages.

@ Private child packages were introduced to enable the details of the implementation of part of a system to be decomposed and yet not be visible to the external world. However, it is often convenient to have public packages that use these details but do not make them visible to the user. In Ada 95 a parent or sibling body can have a with clause for a private child. But the specifications cannot. These rules are designed to ensure that information does not leak out via the visible part of a specification. But there is no logical reason why the private part of a package should not have access to a private child. Ada 2005 overcomes this by introducing private with clauses. We can write

  1        private package App.Secret_Details is
  2                type Inner is ...
  3                ... -- various operations on Inner etc
  4        end App.Secret_Details;
  5
  6        private with App.Secret_Details;
  7
  8        package App.User_View is
  9                type Outer is private;
 10                ... -- various operations on Outer visible to the user
 11                -- type Inner is not visible here
 12        private
 13                -- type Inner is visible here
 14                type Outer is
 15                        record
 16                                X : Secret_Details.Inner;
 17                                ...
 18                        end record;
 19                ...
 20        end App.User_View;

@ thus the private part of the public child has access to the type Inner but it is still hidden from the external user.

@ Note that the public child and private child might have mutually declared types as well in which case they might also wish to use the limited with facility. In this case the public child would have a limited private with clause for the private child written thus

  1        limited private with App.Secret_Details;
  2        package App.User_View is ...

@ In the case of a parent package, its specification cannot have a with clause for a child - logically the specification cannot know about the child because the parent must be declared (that is put into the program library) first. Similarly a parent cannot have a private with clause for a private child. But it can have a limited with clause for any child (thereby breaking the circularity) and in particular it can have a limited private with clause for a private child. So we might also have

  1        limited private with App.Secret_Details;
  2        package App is ...

@ The final topic in this section is limited types. The reader will recall that the general idea of a limited type is to restrict the operations that the user can perform on a type to just those provided by the developer of the type and in particular to prevent the user from doing assignment and thus making copies of an object of the type.

@ However, limited types have never quite come up to expectation both in Ada 83 and Ada 95. Ada 95 brought significant improvements by disentangling the concept of a limited type from a private type but problems have remained.

@ The key problem is that Ada 95 does not allow the initialization of limited types because of the view that initialization requires assignment and thus copying. A consequence is that we cannot declare constants of a limited type either. Ada 2005 overcomes this problem by allowing initialization by aggregates.

@ As a simple example, consider

  1        type T is limited
  2                record
  3                        A : Integer;
  4                        B : Boolean;
  5                        C : Float;
  6                end record;

@ in which the type as a whole is limited but the components are not. If we declare an object of type T in Ada 95 then we have to initialize the components (by assigning to them) individually thus

  1        X : T;
  2        begin
  3                X.A := 10; X.B := True; X.C := 45.7;

@ Not only is this annoying but it is prone to errors as well. If we add a further component D to the record type T then we might forget to initialize it. One of the advantages of aggregates is that we have to supply all the components (allowing automatic so-called full coverage analysis, a key benefit of Ada).

@ Ada 2005 allows the initialization with aggregates thus

  1        X : T := (A => 10, B => True, C => 45.7);

@ Technically, Ada 2005 just recognizes properly that initialization is not assignment. Thus we should think of the individual components as being initialized individually in situ - an actual aggregated value is not created and then assigned. (Much the same happens when initializing controlled types with an aggregate.)

@ Sometimes a limited type has components where an initial value cannot be given. This happens with task and protected types. For example

  1        protected type Semaphore is ... ;
  2
  3        type PT is
  4                record
  5                        Guard    : Semaphore;
  6                        Count    : Integer;
  7                        Finished : Boolean := False;
  8                end record;

@ Remember that a protected type is inherently limited. This means that the type PT is limited because a type with a limited component is itself limited. It is good practice to explicitly put limited on the type PT in such cases but it has been omitted here for illustration. Now we cannot give an explicit initial value for a Semaphore but we would still like to use an aggregate to get the coverage check. In such cases we can use the box symbol <> to mean use the default value for the type (if any). So we can write

  1        X : PT := (Guard => <>, Count => 0, Finished => <>);

@ Note that the ability to use <> in an aggregate for a default value is not restricted to the initialization of limited types. It is a new feature applicable to aggregates in general. But, in order to avoid confusion, it is only permitted with named notation.

@ Limited aggregates are also allowed in other similar contexts where copying is not involved including as actual parameters of mode in.

@ There are also problems with returning results of a limited type from a function. This is overcome in Ada 2005 by the introduction of an extended form of return statement. This will be described in detail in a later paper.

Rationale for Ada 2005: Introduction

ENGRUSTOP
BACKNEXT

3.3 Структура, видимость, и ограниченные типы

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

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

  1        type Object is abstract tagged
  2                record
  3                        Its_Color : Color;
  4                        ...
  5                end record;
  6
  7        type Point;
  8        type Line;
  9        type Acc_Point is access all Point;
 10        type Acc_Line is access all Line;
 11        subtype Index is Integer range 0 .. Max;
 12        type Acc_Line_Array is array (1 .. Max) of Acc_Line;
 13        type Acc_Point_Array is array (1 .. Max) of Acc_Point;
 14
 15        type Point is new Object with
 16                record
 17                        No_Of_Lines : Index;
 18                        LL          : Acc_Line_Array;
 19                        ...
 20                end record;
 21
 22        type Line is new Object with
 23                record
 24                        No_Of_Points : Index;
 25                        PP           : Acc_Point_Array;
 26                        ...
 27                end record;

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

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

@ Мы нуждаемся в том чтобы неполное представление и его завершение могли быть в различных пакетах. Ветераны могли бы впомнить, что в версии Ады 1980 года ситуация была еще хуже - завершение должно было быть в том же самом списке объявлений где и неполное объявление. Ада 83 ослабила это (так называемая Поправка Taft) и разрешила приватной части и телу пакета быть обработанными как один список - то же самое правило применяется и в Аде 95. Теперь делаем следующий шаг вперёд.

@ Ада 2005 решает проблему введением конструкции limited with ... Идея состоит в том, что библиотечный пакет (или подпрограмма) может обратиться к другому библиотечному пакету который еще не был объявлен и может обратиться к типам в том пакете, но только так как будто они были неполными типами. Таким образом, у нас мог бы быть корневой пакет Geometry, содержащий объявления Object, Max, Index и т.д. В этом случае:

  1        limited with Geometry.Lines;
  2
  3        package Geometry.Points is
  4                type Acc_Line_Array is array (1 .. Max) of access Lines.Line;
  5                type Point is new Object with
  6                        record
  7                                No_Of_Lines : Index;
  8                                LL          : Acc_Line_Array;
  9                                ...
 10                        end record;
 11                ...
 12        end Geometry.Points;

@ Пакет Geometry.Lines объявлен аналогичным образом. Обратите внимание, что мы используем анонимное средство ссылочного типа, обсуждаемое в Разделе 3.2 и, таким образом, мы не должны даже объявить именованные ссылочные типы, такие как Acc_Line, чтобы объявить Acc_Line_Array.

@ Написав limited with Geometry.Lines мы получаем доступ ко всем типам, видимым в спецификации Geometry.Lines как будто они были объявлены как неполные. Другими словами, мы получаем неполное представление типов. Мы можем тогда сделать любые вещи, которые мы можем обычно делать с неполными типами, например использовать их чтобы объявить ссылочные типы. (Конечно при последующей реализации будет проведена проверка Geometry.Lines есть ли действительно в нём тип Line).

@ Не только отсутствие необходимости в именованом типе Acc_Line удобная вещь, но также это предотвращает разрастание количества именованных ссылочных типов. Если бы мы действительно хотели использовать именованный тип Acc_Line в обоих пакетах, тогда мы должны были бы объявить по собственному типу в каждом пакете. Так как с точки зрения пакета Points, Acc_Line в Lines только был бы неполным типом (помните, что у каждого пакета есть только ограниченное представление другого), и таким образом, это было бы чрезвычайно неудобно. Следствием была бы масса именованных ссылочных типов и их преобразований повсеместно.

@ Есть также немного соответсвующих изменений для неполных теговых типов. Теперь мы можем написать:

  1        type T is tagged;

@ что гарантирует нам, что при полном объявлении тип T окажется теговым. Преимущество здесь состоит в том, что мы заранее знаем, что тэговые объекты типа T будут передаваться по ссылке. Следовательно, мы можем использовать тип T для параметров прежде, чем увидим его полное объявление. В примере точек и линий выше, так как Line явно отмечена как tagged в пакете Geometry.Lines то мы, таким образом, получим неполное теговое представление Lines.

@ Введение неполных теговых типов позволяет нам написать:

  1        type T_Ptr is access all T'Class;

@ Это возможно было и в Аде 95 даже в том случае если мы не объявили T как tagged в этом месте. Конечно это подразумевало, что T будет tagged. В Аде 2005 это не приветствуется, так как мы должны теперь объявить, что T tagged неполный, если мы желаем объявить class wide access type. Для совместимости старая возможность была сохранена, но вынесена в Приложение J как анахронизм.

@ Дальнейшие примеры использования конструкции limited with ... будут даны в более поздней публикации.

@ Другое расширение в этой области - введение конструкции private with ..., которая преодолевает проблему с приватными дочерними пакетами.

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

  1        private package App.Secret_Details is
  2                type Inner is ...
  3                ... -- various operations on Inner etc
  4        end App.Secret_Details;
  5
  6        private with App.Secret_Details;
  7
  8        package App.User_View is
  9                type Outer is private;
 10                ... -- various operations on Outer visible to the user
 11                -- type Inner is not visible here
 12        private
 13                -- type Inner is visible here
 14                type Outer is
 15                        record
 16                                X : Secret_Details.Inner;
 17                                ...
 18                        end record;
 19                ...
 20        end App.User_View;

@ таким образом, у частной части публичного потомка есть доступ к типу Inner, но это все еще скрыто от внешнего пользователя.

@ Заметим, что публичный и приватный потомки могут иметь взаимно объявленные типы и в этом случае они должны использовать выражение limited with. В этом случае у публичного потомка было бы limited private with выражение для частного потомка написанное таким образом:

  1        limited private with App.Secret_Details;
  2        package App.User_View is ...

@ В случае родительского пакета его спецификация не может иметь with выражение для потомка, фактически, спецификация не может знать о потомке, потому что родитель объявляется (который помещен в библиотеку программы) раньше. Так же у родителя не может быть private with выражения для частного потомка. Но он может иметь limited with выражение для любого потомка (таким образом ломается зацикливание), и в особенности у этого может быть limited private with выражение для частного потомка. Таким образом, мы могли бы также иметь:

  1        limited private with App.Secret_Details;
  2        package App is ...

@ Последняя тема в этом разделе - ограниченные (limited) типы. Читатель напомнит, что общее представление об ограниченном типе должно ограничить операции, которые пользователь может выполнить с типом и в особенности препятствовать тому, чтобы пользователь делал присваивания копии типа объекта.

@ Однако, ограниченные типы никогда не соответсвовали ожиданиям ни в Аде 83 ни в Аде 95. Ада 95 после существенных усовершенствований, развязала понятия ограниченного типа и частного типа, но проблемы остались.

@ Ключевая проблема состоит в том, что Ада 95 не позволяет инициализацию ограниченных типов из-за представления что инициализация требует присваивания и, таким образом, копирования. Вследствие чего, мы не можем объявить константы ограниченного типа также. Ада 2005 преодолевает эту проблему, позволяя инициализацию агрегатами как в следующем примере:

  1        type T is limited
  2                record
  3                        A : Integer;
  4                        B : Boolean;
  5                        C : Float;
  6                end record;

@ в котором тип в целом является ограниченым, но его компоненты нет. Если мы объявляем объект типа T на Аде 95 тогда, мы должны инициализировать компоненты (назначая их) индивидуально таким образом:

  1        X : T;
  2        begin
  3                X.A := 10; X.B := True; X.C := 45.7;

@ Мало того, что такое назначение выглядит коряво, оно к тому же, склонно к ошибкам. Если мы добавляем новый компонент D к типу записи T тогда, мы могли бы забыть инициализировать его. Одно из преимуществ агрегатов состоит в том, что мы должны поставлять все компоненты (позволяющий использовать автоматический контроль при компиляции, используя ключевое приемущество Ады).

@ Ада 2005 позволяет инициализацию с агрегатами таким образом:

  1        X : T := (A => 10, B => True, C => 45.7);

@ Технически, Ада 2005 только должным образом распознает, что инициализация это не присваивание. Таким образом, мы должны думать об индивидуальных компонентах, как инициализируемых индивидуально на месте - фактическое объединенное значение не создаётся при таком назначении. (Аналогично получается, когда производится инициализация управляемого (controlled) типа с агрегатом).

@ Иногда у ограниченного типа есть компоненты, которым начальное значение не может быть дано. Это случается в случае с задачного или защищённого типа. Например:

  1        protected type Semaphore is ... ;
  2                type PT is
  3                        record
  4                                Guard    : Semaphore;
  5                                Count    : Integer;
  6                                Finished : Boolean := False;
  7                        end record;

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

  1        X : PT := (Guard => <>, Count => 0, Finished => <>);

@ Заметим, что способность использовать <> в агрегате для значения по умолчанию не ограничена инициализацией ограниченных типов. Это - новая особенность, применимая к агрегатам вообще. Но, чтобы избежать беспорядка, это разрешено только с именованными компонентами агрегата.

@ Ограниченные агрегаты также разрешены в других подобных контекстах, где копирование не вовлечено, включая фактические параметры режима in.

@ Есть также проблемы с возвращением результатов ограниченного типа из функции. Это преодолено в Аде 2005 введением расширенной формы оператора return. Оно будет подробно описано в более поздней публикации.

ENG RUS

TOP BACK NEXT

2010-10-24 00:26:52

. .