Rationale for Ada 2012
4.5 Use clauses
Ada 2012 introduces a further form of use clause.
In order to understand the benefit it is perhaps worth just recalling
the background to this topic.
The original use clause
in Ada 83 made everything in a package directly visible. Consider the
following package
package P is
I, J, K: Integer;
type Colour is (Red, Orange, Yellow, Green, Blue, ... );
function Mix(This, That: Colour) return Colour;
type Complex is
record
Rl, Im: Float;
end record;
function "+"(Left, Right: Complex) return Complex;
...
end P;
Now suppose we have
a package Q which manipulates entities declared
in P. We need a with clause for P,
thus
with P;
package Q is
...
With just a with clause
for P we have to refer to entities in P
using the prefix P. So we get statements and
declarations in Q such as
P.I := P.J + P.K;
Mucky: P.Colour := P.Mix(P.Red, P.Green);
W: P.Complex := (1.0, 2.0);
Z: P.Complex := (4.0, 5.0);
D: P.Complex := P."+"(W, Z);
This is generally considered
tedious especially if the package name is not P
but A_Very_Long_Name. However, adding a package
use clause to Q thus
with P; use P;
package Q...
enables the P
prefix to be omitted and in particular allows infix notation for operators
so we can now simply write
D: Complex := W + Z;
But as is well known, the universal use of such use
clauses introduces ambiguity (if the same identifier is in two different
packages and we have a use clause for both), obscurity (you can't find
the wretched declaration of Red) and possibly
a maintenance headache (another package is added which duplicates some
identifiers). So there is a school of thought that use clauses are bad
for you.
However, although the
prefix denoting the package is generally beneficial it is a pain to be
forced to always use the prefix notation for operators. So in Ada 95,
the use type clause was added enabling us to write
with P; use type P.Complex;
package Q is ...
This has the effect
that only the primitive operators of the type Complex
are directly visible. So we can now write
D: P.Complex := W + Z;
Note that the type name Complex
is not itself directly visible so we still have to write P.Complex
in the declaration of D.
However, some users still grumbled. Why should only
those primitive operations that happen to be denoted by operators be
visible? Why indeed? Why cannot Mucky be declared
similarly without using the prefix P for Mix,
Red and Green?
It might be worth briefly
recalling exactly which operations of a type
T
are primitive operations of
T. They are basically
predefined operations such as "="
and "+",
subprograms declared in the same package as T
and which operate on T,
enumeration literals of T,
for a derived type, inherited or overridden subprograms.
The irritation is solved in Ada 2012 by the
use
all type clause which makes all primitive operations visible. (Note
another use for the reserved word
all.)
So we can write
with P; use all type P.Colour;
package Q is
...
and now within Q
we can write
Mucky: P.Colour := Mix(Red, Green);
Thus the enumeration literals such as Red
are made directly visible as well as obvious primitive subprograms such
as Mix.
Another impact concerns tagged types and in particular
operations on class wide types.
Remember that subprograms with a parameter (or result)
of type T'Class are not primitive operations
unless they also have a parameter (or result of type T)
as well.
Actually it is usually very convenient that operations
on a class wide type are not primitive operations because it means that
they are not inherited and so cannot be overridden. Thus we are assured
that they do apply to all types of the class.
So, suppose we have
package P is
type T is tagged private;
procedure Op1(X: in out T);
procedure Op2(Y: in T; Z: out T);
function Fop(W: T) return Integer;
procedure List(TC: in T'Class);
private
...
end P;
Then although List is
not a primitive operation of T it will certainly
look to many users that it belongs to T in
some broad sense. Accordingly, writing use all type P.T;
makes not only the primitive operations such as Op1,
Op2 and Fop, visible
but it also makes List visible as well.
Note that this is the
same as the rule regarding the prefixed form of subprogram calls which
can also be used for both primitive operations and class wide operations.
Thus given an object A of type T,
as well as statements A.Op1; and A.Op2(B);
and a function call A.Fop we can equally write
A.List; -- prefixed call of class wide procedure
Moreover, suppose we
declare a type NT in a package NP
thus
package NP is
type NT is new T with ...
...
end NP;
If we have an object
AN of type NT then
not only can we use prefixed calls for inherited and overridden operations
but we can also use prefixed calls for class wide operations in ancestor
packages such as P. So we can write
AN.List; -- prefixed call of List in ancestor package
Similarly, writing use all type NP.NT;
on Q makes the inherited (or overridden) operations
Op1, Op2 and Fop
visible and also makes the class wide operation List
declared in P visible. We do not also have
to write use all type P.T; on Q
as well.
We conclude by remarking that the maintenance problem
of name clashes really only applies to use package clauses. In the case
of use type and use all type clauses, the entities made visible are overloadable
and a clash only occurs if two have the same profile which is very unlikely
and almost inevitably indicates a bug.
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: