Rationale for Ada 2005: Object oriented model

RUSTOP
BACKNEXT

ENG

5. Nested type extension

@ In Ada 95 type extension of tagged types has to be at the same level as the parent type. This can be quite a problem. In particular it means that all controlled types must be declared at library level because the root types Controlled and Limited_Controlled are declared in the library level package Ada.Finalization. The same applies to storage pools and streams because again the root types Root_Storage_Pool and Root_Stream_Type are declared in library packages.

@ This has a cumulative effect since if we write a generic unit using any of these types then that package can itself only be instantiated at library level. This enforces a very flat level of programming and hinders abstraction.

@ The problems can actually be illustrated without having to use controlled types or generics. As a simple example consider the following which is adapted from a text book [3]. It manipulates lists of colours and we assume that the type Colour is declared somewhere.

  1        package Lists is
  2                type List is limited private;
  3                type Iterator is abstract tagged null record;
  4                procedure Iterate (IC : in Iterator'Class; L : in List);
  5                procedure Action (It : in out Iterator; C : in out Colour) is abstract;
  6        private
  7                ...
  8        end;

@ The idea is that a call of Iterate calls Action (by dispatching) on each object of the list and thereby gives access to the colour of that object. The user has to declare an extension of Iterator and a specific procedure Action to do whatever is required on each object.

@ Some readers may find this sort of topic confusing. It might be easier to understand if we look at the private part and body of the package Lists which might be

  1                ...
  2        private
  3                type Cell
  4                        is record
  5                                Next : access Cell; -- anonymous type
  6                                C    : Colour;
  7                        end record;
  8                type List is access Cell;
  9        end;
 10
 11        package body Lists is
 12                procedure Iterate (IC : in Iterator'Class; L : in List) is
 13                        This : access Cell := L;
 14                begin
 15                        while This /= null loop
 16                                Action (IC, This.C); -- dispatching call
 17                                -- or IC.Action (This.C);
 18                                This := This.Next;
 19                        end loop;
 20                end Iterate;
 21        end Lists;

@ Note the use of the anonymous access types which avoid the need to have an incomplete declaration of Cell in the private part.

@ Now suppose we wish to change the colour of every green object to red. We write (in some library level package)

  1        type GTR_It is new Iterator with null record;
  2        procedure Action (It : in out GTR_It; C : in out Colour) is
  3        begin
  4                if C = Green then C := Red; end if;
  5        end Action;
  6
  7        procedure Green_To_Red (L : in List) is
  8                It: GTR_It;
  9        begin
 10                Iterate (It, L); -- or It.Iterate(L);
 11        end Green_To_Red;

@ This works but is not ideal. The type GTR_It and the procedure Action should not be declared outside the procedure Green_To_Red since they are really only part of its internal workings. But we cannot declare the type GTR_It inside the procedure in Ada 95 because that would be an extension at an inner level.

@ The extra facilities of the predefined library in Ada 2005 and especially the introduction of containers which are naturally implemented as generic units forced a reconsideration of the reasons for restricting type extension in Ada 95. The danger of nested extension of course is that values of objects could violate the accessibility rules and outlive their type declaration. It was concluded that type extension could be permitted at nested levels with the addition of just a few checks to ensure that the accessibility rules were not violated.

@ So in Ada 2005 the procedure Green_To_Red can be written as

  1        procedure Green_To_Red (L : in List) is
  2                type GTR_It is new Iterator with null record;
  3                procedure Action (It : in out GTR_It; C : in out Colour) is
  4                begin
  5                        if C = Green then C := Red; end if;
  6                end Action;
  7                It : GTR_It;
  8        begin
  9                Iterate (It, L); -- or It.Iterate(L);
 10        end Green_To_Red;

@ and all the workings are now wrapped up within the procedure as they should be.

@ Note incidentally that we can use the notation It.Iterate (L); even though the type GTR_It is not declared in a package in this case. Remember that although we cannot add new dispatching operations to a type unless it is declared in a package specification, nevertheless we can always override existing ones such as Action.

@ This example is all quite harmless and nothing can go wrong despite the fact that we have performed the extension at an inner level. This is because the value It does not outlive the execution of the procedure Action.

@ But suppose we have a class wide object Global_It as in the following

  1        with Lists; use Lists;
  2        package body P is
  3                function Dodgy return Iterator'Class is
  4                        type Bad_It is new Iterator with null record;
  5                        procedure Action (It : in out Bad_It; C : in out Colour) is
  6                        begin
  7                                ...
  8                        end Action;
  9                        It : Bad_It;
 10                begin
 11                        return It;
 12                end Dodgy;
 13                Global_It : Iterator'Class := Dodgy;
 14        begin
 15                Global_It.Action (Red_For_Danger); -- dispatches
 16        end P;

@ Now we are in deep trouble. We have returned a value of the local type Bad_It, assigned it as the initial value to Global_It and then dispatched on it to the procedure Action. But the procedure Action that will be called is the one inside Dodgy and this does not exist anymore since we have left the function Dodgy. So this must not be allowed to happen.

@ So various accessibility checks are required. There is a check on the return from a function with a class wide result that the value being returned does not have the tag of a type at a deeper level than that of the function itself. So in this example there is a check on the return from the function Dodgy; this fails and raises Program_Error so all is well.

@ There are similar checks on class wide allocators and when using T'Class'Input or T'Class'Output. Some of these can be carried out at compile time but others have to be checked at run time and they also raise Program_Error if they fail.

@ Moreover, in order to implement the checks associated with T'Class'Input and T'Class'Output two additional functions are declared in the package Ada.Tags; these are

  1        function Descendant_Tag (External : String; Ancestor : Tag) return Tag;
  2        function Is_Descendant_At_Same_Level (Descendant, Ancestor : Tag) return Boolean;

@ The use of these will be outlined in the next section.

Rationale for Ada 2005: Object oriented model

@ENGRUSTOPBACKNEXT

5. Вложенное расширение типов

@ В Аде 95 расширения тегового типа должны выполняться на том же самом уровне что и его родительский тип. Это может быть настоящей проблемой. Это означает, что все управляемые (controlled) типы должны быть объявлены на библиотечном уровне, потому что корневые типы Controlled и Limited_Controlled объявлены на библиотечном уровне пакета Ada.Finalization. То же самое относится к пулам памяти и потокам, потому что кореневые типы Root_Storage_Pool и Root_Stream_Type объявлены в библиотечных пакетах.

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

@ Эти проблемы могут быть проиллюстрированы без необходимости использования контролируемых (controlled) типов или настраиваемых средств. Рассмотрим простой пример из учебника [3]. Он управляет списками цветов, и мы предполагаем, что тип Color где-то уже объявлен:

  1        package Lists is
  2                type List is limited private;
  3                type Iterator is abstract tagged null record;
  4                procedure Iterate (IC : in Iterator'Class; L : in List);
  5                procedure Action (It : in out Iterator; C : in out Colour) is abstract;
  6        private
  7                ...
  8        end;

@ Идея здесь в том, что процедура Iterate вызывает процедуру Action для каждого объекта списка, и таким образом предоставляет доступ к цвету соответствующего объекта. Пользователь должен объявить расширение Iterator и процедуру Action для каждого объекта.

@ Некоторые читатели могут найти это весьма запутанным. Это проще понять, если посмотреть на частную секцию и тело пакета Lists:

  1                ...
  2        private
  3                type Cell
  4                        is record
  5                                Next : access Cell; -- anonymous type
  6                                C    : Colour;
  7                        end record;
  8                type List is access Cell;
  9        end;
 10
 11        package body Lists is
 12                procedure Iterate (IC : in Iterator'Class; L : in List) is
 13                        This : access Cell := L;
 14                begin
 15                        while This /= null loop
 16                                Action (IC, This.C); -- dispatching call
 17                                -- or IC.Action (This.C);
 18                                This := This.Next;
 19                        end loop;
 20                end Iterate;
 21        end Lists;

@ Отметим использование анонимных ссылочных типов, которые избавляют от необходимости иметь неполное объявление Cell в частной секции.

@ Теперь предположим, что мы желаем изменить цвет каждого зеленого объекта на красный. Мы пишем (на некотором библиотечном уровня пакета)

  1        type GTR_It is new Iterator with null record;
  2        procedure Action (It : in out GTR_It; C : in out Colour) is
  3        begin
  4                if C = Green then C := Red; end if;
  5        end Action;
  6
  7        procedure Green_To_Red (L : in List) is
  8                It: GTR_It;
  9        begin
 10                Iterate (It, L); -- or It.Iterate(L);
 11        end Green_To_Red;

@ Это работает, но не идеально. Тип GTR_It и процедура Action не должны быть объявлены вне процедуры Green_To_Red, так как они - действительно только часть ее внутренних работ. Но мы не можем объявить тип GTR_It в процедуре на Аде 95 потому что, это было бы расширением на внутреннем уровне.

@ Дополнительные средства предопределенной библиотеки Ады 2005 и особенно введение контейнеров, которые естественно реализованы как настраиваемые модули, вызвали повторное рассмотрение причин для ограничения расширения типов ada. Опасность вложенного расширения конечно состоит в том, что значения объектов могут нарушить правила доступности и пережить их описание типа. В конце-концов было решено, что расширение типа может быть разрешено на вложенных уровнях с выполнением только некоторых проверок для гарантии, что правила доступности не были нарушены.

@ Так на Аде 2005 процедура Green_To_Red может быть написана как:

  1        procedure Green_To_Red (L : in List) is
  2                type GTR_It is new Iterator with null record;
  3                procedure Action (It : in out GTR_It; C : in out Colour) is
  4                begin
  5                        if C = Green then C := Red; end if;
  6                end Action;
  7                It : GTR_It;
  8        begin
  9                Iterate (It, L); -- or It.Iterate(L);
 10        end Green_To_Red;

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

@ Кстати, обратите внимание, что мы теперь можем использовать нотацию It.Iterate (L) даже при том, что тип GTR_It не объявлен в пакете. Помните, что, хотя мы не можем добавить новые операции диспетчеризации к типу, если это не объявлено в спецификации пакета, однако мы можем всегда заменять (override) существующие, такие как Action.

@ Этот пример весьма безопасен, и ничто в нём не может пойти не так как надо несмотря на то, что мы выполнили расширение на внутреннем уровне. Это потому что значение It не переживает выполнение процедуры Action.

@ Но предположим, что у нас есть надклассовый объект Global_It:

  1        with Lists; use Lists;
  2        package body P is
  3                function Dodgy return Iterator'Class is
  4                        type Bad_It is new Iterator with null record;
  5                        procedure Action (It : in out Bad_It; C : in out Colour) is
  6                        begin
  7                                ...
  8                        end Action;
  9                        It : Bad_It;
 10                begin
 11                        return It;
 12                end Dodgy;
 13                Global_It : Iterator'Class := Dodgy;
 14        begin
 15                Global_It.Action (Red_For_Danger); -- dispatches
 16        end P;

@ Теперь мы находимся в глубокой задумчивости. Мы возвратили значение локального типа Bad_It, назначили начальное значение Global_It и затем послали её процедуре Action. Но процедура Action, которая будет вызвана, является внутренней частью Dodgy, которая не существует больше, так как мы оставили функцию Dodgy. Но этому нельзя позволить случиться.

@ Таким образом, требуются некоторые проверки доступности. Есть проверка по возвращению из функции с наклассовым результатом, что у возвращаемого значения нет тэгового типа на более глубоком уровне чем функции непосредственно. Так в этом примере есть проверка по возвращению из функции Dodgy она проводится и возбуждает исключение Program_Error, и таким образом, всё хорошо.

@ Есть подобные проверки на надклассовые программы распределения и когда используется T'Class'Input или T'Class'Output. Некоторые из них могут быть выполнены во время компиляции, но другие должны быть проверены во время выполнения, и они также возбуждат Program_Error, если они терпят неудачу.

@ Кроме того, чтобы осуществить проверки, связанные с T'Class'Input и T'Class'Output, в пакете Ada.Tags объявлены две дополнительные функции:

  1        function Descendant_Tag (External : String; Ancestor : Tag) return Tag;
  2        function Is_Descendant_At_Same_Level (Descendant, Ancestor : Tag) return Boolean;

@ Их использование будет показано в следующем разделе.

@ ENG RUS

TOP BACK NEXT

2010-10-31 15:31:38

. .