Rationale for Ada 2005

John Barnes
Contents   Index   References   Search   Previous   Next 

2.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 [6]. It manipulates lists of colours and we assume that the type Colour is declared somewhere.
package Lists is
   type List is limited private;
   type Iterator is abstract tagged null record;
   procedure Iterate(IC: in Iterator'Class; L: in List);
   procedure Action(It: in out Iterator; C: in out Colour) is abstract;
private
   ...
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 
private
   type Cell is
      record
         Next: access Cell;    -- anonymous type
         C: Colour;
      end record;
   type List is access Cell;
end;
package body Lists is
   procedure Iterate(IC: in Iterator'Class; L: in List) is
      This: access Cell := L;
   begin
      while This /= null loop
         Action(IC, This.C);    -- dispatching call
                                                    -- or IC.Action(This.C);
         This := This.Next;
      end loop;
   end Iterate;
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) 
type GTR_It is new Iterator with null record;
procedure Action(It: in out GTR_It; C: in out Colour)is
begin
   if C = Green then C := Red; end if;
end Action;
procedure Green_To_Red(L: in List) is
   It: GTR_It;
begin
   Iterate(It, L);    -- or It.Iterate(L);
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 
procedure Green_To_Red(L: in List) is
   type GTR_It is new Iterator with null record;
   procedure Action(It: in out GTR_It; C: in out Colour) is
   begin
      if C = Green then C := Red; end if;
   end Action;
   It: GTR_It;
begin
   Iterate(It, L);    -- or It.Iterate(L);
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
with Lists; use Lists;
package body P is
   function Dodgy return Iterator'Class is
      type Bad_It is new Iterator with null record;
      procedure Action(It: in out GTR_It; C: in out Colour) is
      begin
         ...
      end Action;
      It: Bad_It;
   begin
      return It;
   end Dodgy;
   Global_It: Iterator'Class := Dodgy;
begin
   Global_It.Action(Red_For_Danger);    -- dispatches
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 
function Descendant_Tag(External: String; Ancestor: Tag) return Tag;
function Is_Descendant_At_Same_Level (Descendant, Ancestor: Tag) return Boolean;
The use of these will be outlined in the next section.

Contents   Index   References   Search   Previous   Next 
© 2005, 2006, 2007 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association and its member companies: ARA Members AdaCore Polyspace Technologies Praxis Critical Systems IBM Rational Sofcheck and   Ada-Europe:
Ada-Europe