Rationale for Ada 2005
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.
© 2005, 2006 John Barnes Informatics.
Sponsored in part by: