Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

1.3.3 Overview: Structure and visibility

What will seem to many to be one of the most dramatic changes in Ada 2012 concerns functions. In previous versions of Ada, functions could only have parameters of mode in. Ada 2012 permits functions to have parameters of all modes.
There are various purposes of functions. The purest is simply as a means of looking at some state. Examples are the function Is_Empty applying to an object of type Stack. It doesn't change the state of the stack but just reports on some aspect of it. Other pure functions are mathematical ones such as Sqrt. For a given parameter, Sqrt always returns the same value. These functions never have any side effects. At the opposite extreme we could have a function that has no restrictions at all; any mode of parameters permitted, any side effects permitted, just like a general procedure in fact but also with the ability to return some result that can be immediately used in an expression.
An early version of Ada had such features, there were pure functions on the one hand and so-called value-returning procedures on the other. However, there was a desire for simplification and so we ended up with Ada 83 functions.
In a sense this was the worst of all possible worlds. A function can perform any side effects at all, provided they are not made visible to the user by appearing as parameters of mode in out! As a consequence, various tricks have been resorted to such as using access types (either directly or indirectly). A good example is the function Random in the Numerics annex. It has a parameter Generator of mode in but this does in fact get updated indirectly whenever Random is called. So parameters can change even if they are of mode in. Moreover, the situation has encouraged programmers to use access parameters unnecessarily with increased runtime cost and mental obscurity.
Ada 2012 has bitten the bullet and now allows parameters of functions to be of any mode. But note that operators are still restricted to only in parameters for obvious reasons.
However, there are risks with functions with side effects whether they are visible or not. This is because Ada does not specify the order in which parameters are evaluated nor the order in which parts of an expression are evaluated. So if we write
X := Random(G) + Random(G);
we have no idea which call of Random occurs first – not that it matters in this case. Allowing parameters of all modes provides further opportunities for programmers to inadvertently introduce order dependence into their programs.
So, in order to mitigate the problems of order dependence, Ada 2012 has a number of rules to catch the more obvious cases. These rules are all static and are mostly about aliasing. For example, it is illegal to pass the same actual parameter to two formal in out parameters – the rules apply to both functions and procedures. Consider
procedure Do_It(Double, Triple: in out Integer) is
   Double := Double * 2;
   Triple := Triple * 3;
end Do_It;
Now if we write
Var: Integer := 2;
Do_It(Var, Var);    -- illegal in Ada 2012
then Var might become 4 or 6 in Ada 2005 according to the order in which the parameters are copied back.
These rules also apply to any context in which the order is not specified and which involves function calls with out or in out parameters. Thus an aggregate such as
(Var, F(Var))
where F has an in out parameter is illegal since the order of evaluation of the expressions in an aggregate is undefined and so the value of the first component of the aggregate will depend upon whether it is evaluated before or after F is called.
Full details of the rules need not concern the normal programmer – the compiler will tell you off!
Another change concerning parameters is that it is possible in Ada 2012 to explicitly state that a parameter is to be aliased. Thus we can write
procedure P(X: aliased in out T; ...);
An aliased parameter is always passed by reference and the accessibility rules are modified accordingly. This facility is used in a revision to the containers which avoids the need for expensive and unnecessary copying of complete elements when they are updated. The details will be given in Sections 4.2 and 6.3.
A major advance in Ada 2005 was the introduction of limited with clauses giving more flexibility to incomplete types. However, experience has revealed a few minor shortcomings.
One problem is that an incomplete type in Ada 2005 cannot be completed by a private type. This prevents the following mutually recursive structure of two types having each other as an access discriminant
type T1;
type T2 (X: access T1) is private;
type T1 (X: access T2) is private;
  -- OK in Ada 2012
The rules in Ada 2012 are changed so that an incomplete type can be completed by any type, including a private type (but not another incomplete type obviously).
Another change concerns the use of incomplete types as parameters. Generally, we do not know whether a parameter of a private type is passed by copy or by reference. The one exception is that if it is tagged then we know it will be passed by reference. As a consequence there is a rule in Ada 2005 that an incomplete type cannot be used as a parameter unless it is tagged incomplete. This has forced the unnecessary use of access parameters.
In Ada 2012, this problem is remedied by permitting incomplete types to be used as parameters (and as function results) provided that they are fully defined at the point of call and where the body is declared.
A final change to incomplete types is that a new category of formal generic parameter is added that allows a generic unit to be instantiated with an incomplete type. Thus rather than having to write a signature package as
   type Element is private;
   type Set is private;
   with function Empty return Set;
   with function Unit(E: Element) return Set;
   with function Union(S, T: Set) return Set;
package Set_Signature is end;
which must be instantiated with complete types, we can now write
   type Element;
   type Set;
   with function Empty return Set;
package Set_Signature is end;
where the formal parameters Element and Set are categorized as incomplete. Instantiation can now be performed using any type, including incomplete or private types as actual parameters. This permits the cascading of generic packages which was elusive in Ada 2005 as will be illustrated in Section 4.3. Note that we can also write type Set is tagged; which requires the actual parameter to be tagged but still permits it to be incomplete.
There is a change regarding discriminants. In Ada 2005, a discriminant can only have a default value if the type is not tagged. Remember that giving a default value makes a type mutable. But not permitting a default value has proved to be an irritating restriction in the case of limited tagged types. Being limited they cannot be changed anyway and so a default value is not a problem and is permitted in Ada 2012. This feature is used in the declaration of the protected types for synchronized queues as explained in Section 1.3.6.
Another small but useful improvement is in the area of use clauses. In Ada 83, use clauses only apply to packages and everything in the package specification is made visible. Programming guidelines often prohibit use clauses on the grounds that programs are hard to understand since the origin of entities is obscured. This was a nuisance with operators because it prevented the use of infixed notation and forced the writing of things such as
P."+"(X, Y)
Accordingly, Ada 95 introduced the use type clause which just makes the operators for a specific type in a package directly visible. Thus we write 
use type P.T;
However, although this makes the primitive operators of T visible it does not make everything relating to T visible. Thus it does not make enumeration literals visible or other primitive operations of the type such as subprograms. This is a big nuisance.
To overcome this, Ada 2012 introduces a further variation on the use type clause. If we write
use all type P.T;
then all primitive operations of T are made visible (and not just primitive operators) and this includes enumeration literals in the case of an enumeration type and class wide operations of tagged types.
Finally, there are a couple of small changes to extended return statements which are really corrections to amend oversights in Ada 2005.
The first is that a return object can be declared as constant. For example
function F(...) return LT is
   return Result: constant LT := ... do
   end return;
end F;
We allow everything else to be declared as constant so we should here as well especially if LT is a limited type. This was really an oversight in the syntax.
The other change concerns class wide types. If the returned type is class wide then the object declared in the extended return statement need not be the same in Ada 2012 provided it can be converted to the class wide type.
function F(...) return T'Class is
   return X: TT do
   end return;
end F;
is legal in Ada 2012 provided that TT is descended from T and thus covered by T'Class. In Ada 2005 it is required that the result type be identical to the return type and this is a nuisance with a class wide type because it then has to be initialized with something and so on. Note the analogy with constraints. The return type might be unconstrained such as String whereas the result (sub)type can be constrained such as String(1 .. 5).

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


and   Ada-Europe: