Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

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
         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);
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.

Contents   Index   References   Search   Previous   Next 
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association:


and   Ada-Europe: