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