Rationale Update for Ada 2012

John Barnes
Contents   Index   References   Previous   Next 

Chapter 4: Structure and visibility

One of the most dramatic changes in Ada 2012 concerns subprogram parameters and is that functions can have parameters of all modes. Other areas covered here include incomplete types and discriminants.
The following Ada Issues cover this area:
65
Descendants of incomplete views
74
View conversions and out parameters passed by copy
94
An access definition should be a declarative region
95
Generic formal types and constrained partial views
97
Tag of the return object of a simple return expression
101
Incompatibility of hidden untagged record equality
109
Representation of untagged derived types
132
Freezing of renames-as-body
137
Incomplete views and access to class wide types
These changes can be grouped as follows.
A number of issues concern views. There are clarifications of incomplete views (65, 137) and omissions concerning view conversions (74) and constrained views (95).
An amusing issue concerns the definition of a declarative region (94).
Miscellaneous issues concern renaming (132), untagged record equality (101), untagged derived types (109), and the tag of return objects (97).

A curious situation is discussed by AI-65. A type T3 can be a descendant of T1 but nevertheless inherits no characteristics of T1 because of an intervening type T2. Consider
package P is
   type T1 is private;    -- partial view
   C: constant: T1;
private
   type T1 is new Integer;    -- complete view
   C: constant := 37;    -- my favourite number
end P;
with P;
package Q is
   type T2 is new P.T1;
end Q;
with Q;
package P.Child is
   type T3 is new Q.T2;
private
                                   -- what can we do with T3 here?
end P.Chlld;
In this example T3 is derived from T2 and T2 is derived from T1. The fact that T2 is derived from Integer is not visible to the declaration of T3. Nevertheless the conversion rules allow a value of type T1 to be converted to T3 in the private part of the child package. But the fact that T3 is an integer type is not visible.
We say that T3 is effectively a descendant of an incomplete view of T1. (Note "effectively"; it's not technically an incomplete view but behaves in some ways as if it were.) So we can convert C but not 73 to type T3 in the private part of C.Child.
   X: T3 := T3(P.C)    -- OK
   Y: T3 := T3(73);    -- No, T3 is not visibly numeric!
It was meant to be like this in Ada 95; Ada 2005 meddled with it and Ada 2012 made a confusing "improvement". Hopefully the clarifications made now will be the end of the story.
It is helpful to remember the distinction between a partial view and an incomplete view.
A partial view is the view given by a private type declaration in contrast to the full view given by the full declaration in the private part. As in type T1 above.
An incomplete view is the view given by an incomplete declaration such as occurs with access types. Thus
type Cell;    -- incomplete view
type Link is access Cell:

type Cell is    -- completion
   record
      Next: Link;
      ...
   end record;
The use of the concept of incomplete views was much extended in Ada 2005 by the introduction of the limited with clause. It was extended again in Ada 2012 by allowing incomplete types to be completed by types other than access types and allowing incomplete views as parameters.
There are many rules concerning access types that designate incomplete views. AI-137 clarifies that they also apply to access to class wide types.
AI-95 concerns an omission/confusion with regard to generic untagged formal types and partial views. Briefly, within a generic body we assume the worst as to whether or not a formal subtype has a constrained partial view. In particular we assume that untagged formal private and derived types do indeed have a constrained partial view.
As Ada has grown there have been further lexical amusements such as functions returning access to functions. Thus we can now have
type T is
   access function( ... ) return
      access function( ... ) return
         access function( ... ) return ...
ad infinitum. To be more specific the rules seem to prohibit
type T is
   access function(A: Integer) return
      access function(A: Float) return Boolean;
because here we have two instances of A in the declarative region for T. There is no real reason why this should not be permitted so the definition of declarative region is extended to include an access definition (AI-94).
A somewhat different topic is addressed by AI-74 and concerns parameters of mode out which have always been the source of troubles. The basic problem is that such parameters can become undefined. Consider this simple procedure to find the two roots of a quadratic equation
procedure Quadratic(A, B, C: in Real;
         Root_1, Root_2: out Real; OK: out Boolean) is
   D: constant Real := B**2 – 4.0*A*C;
begin
   if D < 0.0 or A = 0.0 then
      OK := False; return;
   end if;
   Root_1 := (–B + Sqrt(D)) / (2.0*A);
   Root_2 := (–B – Sqrt(D)) / (2.0*A);
   OK := True;
end Quadratic;
If the equation has complex roots then no values are assigned to Root_1 and Root_2 so they are likely to contain rubbish. So if we call Quadratic thus
Quadratic(AA, BB, CC, R1, R2, State);
then because of the copy in and out rules for parameters of elementary types, the variables R1 and R2 which might have had respectable values will now contain rubbish.
Of course if we had made the parameters Root_1 and Root_2 of mode in out then the original values of R1 and R2 would have been retained if no assignments were made to Root_1 and Root_2.
However, if we had been wise and used the Default_Value aspect introduced in Ada 2012 thus
type Real is new Float
   with Default_Value := 0.0;
then the behaviour is different. In this case Root_1 and Root_2 will behave essentially as if they were of mode in out and will remain unchanged. Note carefully that they will not take the default values of 0.0 and so the existing values of R1 and R2 will not be disturbed. Of course if we had declared a local variable R_Temp of type Real then it would take the initial value of 0.0.
This technique of initially copying in parameters of mode out has existed in Ada for access types since Ada 83. Remember that access types always have a default initial value of null and so this copying in behaviour is identical. Incidentally, this copying in is done "in the raw" without making any subtype checks such as range constraints; again this follows the behaviour of access types. Note also that the whole purpose of an out parameter is to give it some value without concern for the original value of the actual parameter and so gratuitously checking the original value of the actual could be irritating if it raised an exception. Another point is that the default value applies to the type and not to the subtype.
However, do remember that we cannot give a default value to the predefined types such as Float so this is a good reason for declaring our own types.
Other problems arise when an actual parameter is a view conversion and this is the real topic of AI-74. Consider the following simple example
procedure Inc(X: in out Integer) is
begin
   X := X + 1;
end Inc;
...
F: Float;
...
F := 3.14;
Inc(Integer(F));
Remember that the behaviour is that the value of F is converted to type Integer (and thus becomes 3) and this is the initial value of the parameter X which is then incremented to 4 and finally converted to 4.0 and copied back into F. This is as in Ada 83.
But problems arise if the parameter is an out parameter and not an in out parameter. Consider
procedure P(X: out My_Integer) is ...
...
Y: Long_Float := 1.0E20;
...
P(My_Integer(Y));
Now suppose we have given Default_Value for My_Integer. An important goal of Default_Value is to ensure that junk values do not arise. This is done by treating out parameters essentially as in out parameters as illustrated by Quadratic. But now we are in trouble because we are unlikely to be able to convert the giant floating value Y to the type My_Integer.
This problem is overcome by saying that if the aspect Default_Value is given for the type of the formal parameter then there must be an ancestor of both the target type and the operand type of the view conversion and the operand type itself must also have the aspect Default_Value. If the conversion meets these requirements, then it is bound to work. Otherwise, the view conversion (such as the example above) is made illegal.
AI-132 concerns expression functions and freezing again (see the brief mention of AI-103 in the previous Chapter). If we have an expression function such as
function F(...) return T is
   (expression of subtype T);
then it can occur in a renaming as body thus
function G( ... ) return T renames F;
This AI points out that this renaming freezes the expression of the expression function F.
The redefining of equality has always been a bother. Originally there were different rules for composition of tagged and untagged types. The difference was removed in Ada 2012 in order to make composition more uniform. However, a quirk in the rules meant that a hidden definition of equality for an untagged record type as in
package P is
   type PT is private;
private
   type PT is record ... end record;   -- untagged
   function "=" (L, R: PT) return Boolean;
end P;
was not permitted. This was a mistake and accordingly this restriction is removed by AI-132.
There are omissions regarding aspect specifications and derived types. One of the advantages of the introduction of aspect specifications is that they occur with the entity to which they apply. This means that the traditional linear elaboration does not always apply because the aspect might refer to things that have not yet been declared. AI-109 clarifies the situation with regard to the freezing of the representation of untagged types.
Finally, AI-97 addresses a minor error in the description of the tag of an object in a return statement. The introduction of the extended return statement where we have
return R: T do
   ...
end return;
needed clarification because T might not be identical to the return type given in the function specification (it might be a subtype; perhaps the function has an indefinite type and the return is definite, perhaps classwide and specific and so on.) So the rules were rewritten to cover the extended return. Unfortunately the rules were written in a way that was incorrect for an old-fashioned return statement. This is now put right.

Contents   Index   References   Previous   Next 
© 2016 John Barnes Informatics.