Rationale for Ada 2005

John Barnes
Table of Contents   Index   References   Search   Previous   Next 

1.3.3 Overview: 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 
type Object is abstract tagged
   record
      Its_Color: Color;
      ...
   end record;
type Point;
type Line;
type Acc_Point is access all Point;
type Acc_Line is access all Line;
subtype Index is Integer range 0 .. Max;
type Acc_Line_Array is array (1 .. Max) of Acc_Line;
type Acc_Point_Array is array (1 .. Max) of Acc_Point;
type Point is new Object with
   record
      No_Of_Lines: Index;
      LL: Acc_Line_Array;
      ...
   end record;
type Line is new Object with
   record
      No_Of_Points: Index;
      PP: Acc_Point_Array;
      ...
   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 
limited with Geometry.Lines;
package Geometry.Points is
   type Acc_Line_Array is array (1 .. Max) of access Lines.Line;
   type Point is new Object with
      record
         No_Of_Lines: Index;
         LL: Acc_Line_Array;
         ...
      end record;
      ...
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 1.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
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 
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 chapter (see 4.2).
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 
private package App.Secret_Details is
   type Inner is ...
   ...  -- various operations on Inner etc
end App.Secret_Details;
private with App.Secret_Details;
package App.User_View is
   type Outer is private;
   ...  -- various operations on Outer visible to the user
   -- type Inner is not visible here
private
   -- type Inner is visible here
   type Outer is
      record
         X: Secret_Details.Inner;
         ...
      end record;
   ...
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 
limited private with App.Secret_Details;
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 
limited private with App.Secret_Details;
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 
type T is limited
   record
      A: Integer;
      B: Boolean;
      C: Float;
   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 
   X: T;
begin
   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
   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 
protected type Semaphore is ... ;
type PT is
   record
      Guard: Semaphore;
      Count: Integer;
      Finished: Boolean := False;
   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
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 chapter (see 4.5).

Table of Contents   Index   References   Search   Previous   Next 
© 2005, 2006 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