Rationale for Ada 2012
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
begin
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
generic
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
generic
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.
Thus
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).
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: