Rationale for Ada 2012
4.2 Subprogram parameters
The main topic here is the fact that functions (but
not operators) in Ada 2012 can have parameters of any mode.
This is a topic left over from Ada 2005. The epilogue
to the Rationale for Ada 2005
[15] discusses
a number of topics that were abandoned and in the case of function modes
says:
"Clearly, Ada functions are indeed curious.
But strangely this AI (that is
AI95-323)
was abandoned quite early in the revision process on the grounds that
it was 'too late'. (Perhaps too late in this context meant 25 years too
late.)" It was not possible to agree on a way forward and so effort
was devoted to other topics.
But mists clear with time. The big concern was that
allowing parameters of all modes might open the door to dangerous programming
practices but a solution to that was found in the introduction of stricter
rules preventing many order dependences.
It is instructive to quickly go through the various
historical documents.
A probably little known document is one written in
1976 by David Fisher of the Institute for Defense Analyses
[16]
which provided the foundation for the requirements for the development
of a new language. It doesn't seem to distinguish between procedures
and functions; it does mention the need for parameters which are constant
and those which effectively rename a variable. Moreover, it does say
(item C1 on page 81):
Side effects which are dependent on the evaluation
order among the arguments of an expression will be evaluated left-to-right.
This does not actually require left-to-right evaluation but the behaviour
must be as if it were. I have always thought it tragic that this was
not observed.
This document was followed by a series known as Strawman,
Woodenman, Tinman, Ironman
[17] and
finishing with Steelman
[12].
The requirement on left-to-right evaluation remained
in Tinman and was even stronger in Ironman but was somewhat weakened
in Steelman to allow instrumentation and ends with a warning about being
erroneous.
Further requirements are introduced in Ironman which
requires both functions and procedures as we know them. Moreover, Ironman
has a requirement about assignment to variables non-local to a function;
they must be encapsulated in a region that has no calls on the function;
this same requirement notes that it implies that functions can only have
input parameters. This requirement does not seem to have carried forward
to Steelman.
However, Ironman also introduces a requirement on
restrictions to prevent aliasing. One is that the same actual parameter
of a procedure cannot correspond to more than one input-output parameter.
This requirement does survive into Steelman. But, it only seems to apply
to procedures and not to functions and Steelman appears not to have noticed
that the implied requirement that functions can only have input parameters
has vanished.
It interesting to then see what was proposed in the
sequence of languages leading to Ada 83, namely, Preliminary Green
[18],
Green
[19], Preliminary Ada
[20],
and Ada
[21]. Note that Preliminary
Green was based on Ironman whereas Green was based on Steelman.
In Preliminary Green
we find procedures and functions. Procedures can have parameters of three
modes,
in,
out and
access (don't get excited,
access
meant
in out). Functions can only have parameters of mode
in.
Moreover,
side effects to variables accessible at the function
call are not allowed. In particular, variables that are global to the
function body may not be updated in the function body. The rationale
for Preliminary Green makes it quite clear that functions can have no
side effects whatsoever.
In Green we find the
three modes
in,
out, and
in out.
But
the big difference is that as well as procedures and functions as in
preliminary Green, there are now value returning procedures such as
procedure Random return Real range -1.0 .. 1.0;
The intent is that
functions are still free of all side effects whereas value returning
procedures have more flexibility. However, value returning procedures
can only have parameters of mode in and
assignments to global variables are permitted
within value returning procedures. Calls of such procedures are only
valid at points of the program where the corresponding variables are
not within the scope of their declaration. The order of evaluation of
these calls is strictly that given in the text of the program. Calls
to value returning procedures are only allowed in assignment statements,
initializations and procedure calls.
The rationale for Green
notes that if you want to instrument a function then use a pragma. It
also notes that functions
with arbitrary side effects would undermine the
advantage of the functional approach to software. In addition it would
complicate the semantics of all language structures where expressions
involving such calls may occur. Hence this form of function is not provided.
And now we come to Ada herself. There are manuals
dated July 1979 (preliminary Ada), July 1980 (draft mil-std), July 1982
(proposed ANSI standard), and January 1983 (the ANSI standard usually
known as Ada 83).
In Preliminary Ada, we have procedures, functions
and value returning procedures exactly as in Green. Indeed, it seems
that the only difference between Green and Preliminary Ada is that the
name Green has been converted to Ada.
But the 1980 Ada manual omits value returning procedures
and any mention of any restrictions on what you can do in a function.
And by 1982 we find that we are warned that parameters can be evaluated
in any order and so on.
The Rationale for Ada 83
[6]
didn't finally emerge until 1986 and discusses briefly the reason for
the change which is basically that benevolent side effects are important.
It concludes by quoting from a paper regarding Algol 60
[22]
The plain fact of the matter is (1) that side-effects
are sometimes necessary, and (2) programmers who are irresponsible enough
to introduce side-effects unnecessarily will soon lose the confidence
of their colleagues and rightly so.
However, an interesting
remark in the Rationale for Ada 83 in the light of the change in Ada
2012 is
The only limitation imposed in Ada on functions
is that the mode of all parameters must be in: it would not be
logical to allow in out and out parameters for functions
in a language that excludes nested assignments within an expression.
Hmm. That doesn't really seem to follow. Allowing
assignments in expressions as in C is obnoxious and one of the sources
of errors in C programs. It is not so much that permitting side-effects
in expressions via functions is unwise but more that treating the result
of an assignment as a value nested within an expression is confusing.
Such nested constructions are naturally still excluded from Ada 2012
and so it is very unlikely that the change will be regretted.
Now we must turn to
the question of order dependences. Primarily, to enable optimization,
Ada does not define the order of evaluation of a number of constructions.
These include
the parameters in a subprogram or entry call,
the operands of a binary operator,
the destination and value in an assignment,
the components in an aggregate,
the index expressions in a multidimensional name,
the expressions in a range,
the barriers in a protected object,
the guards in a select statement,
the elaboration of library units.
The expressions involved in the above constructions
can include function calls. Indeed, as
AI-144
states "Arguably, Ada has selected the worst possible solution to
evaluation order dependences (by not specifying an order of evaluation),
it does not detect them in any way, and then says that if you depend
upon one (even if by accident), your code will fail at some point in
the future when your compiler changes. Something should be done about
this."
It is far too late to do anything about specifying
the order of evaluation so the approach taken is to prevent as much aliasing
as possible since aliasing is an important cause of order of evaluation
problems. Ada 2012 introduces rules for determining when two names are
"known to denote the same object".
Thus they denote the
same object if
both names statically denote the same stand-alone
object or parameter; or
both names are selected components, their prefixes
are known to denote the same object, and their selector names denote
the same component.
and so on with similar
rules for dereferences, indexed components and slices. There is also
a rule about renaming so that if we have
C: Character renames S(5);
then C and S(5)
are known to denote the same object. The index naturally has to be static.
A further step is to define when two names "are
known to refer to the same object". This covers some cases of overlapping.
Thus given a record
R
of type
T with a component
C,
we say that
R and
R.C
are known to refer to the same object. Similarly with an array
A
we say that
A and
A(K)
are known to refer to the same object (
K does
not need to be static in this example).
Given these definitions we can now state the two
basic restrictions.
The first concerns
parameters of elementary types:
For each name N that
is passed as a parameter of mode in out or out to a call
of a subprogram S, there is no other name
among the other parameters of mode in out or out to that
call of S that is known to denote the same
object.
Roughly speaking this comes down to saying two or
more parameters of mode out or in out of an elementary
type cannot denote the same object. This applies to both functions and
procedures.
This excludes the example
given in the Introduction which was
procedure Do_It(Double, Triple: in out Integer) is
begin
Double := Double * 2;
Triple := Triple * 3;
end Do_It;
with
Var: Integer := 2;
...
Do_It(Var, Var); -- illegal in Ada 2012
The key problem is that parameters of elementary
types are always passed by copy and the order in which the parameters
are copied back is not specified. Thus Var
might end up with either the value of Double
or the value of Triple.
The other restriction
concerns constructions which have several constituents that can be evaluated
in any order and can contain function calls. Basically it says:
If a name N is passed
as a parameter with mode out or in out to a function call
that occurs in one of the constituents, then no other constituent can
involve a name that is known to refer to the same object.
Constructions cover many situations such as aggregates,
assignments, ranges and so on as listed earlier.
This rule excludes
the other example in the Introduction, namely, the aggregate
(Var, F(Var)) -- illegal in Ada 2012
where F has an in out
parameter.
The rule also excludes
the assignment
Var := F(Var); -- illegal
if the parameter of F
has mode in out. Remember that the destination of an assignment
can be evaluated before or after the expression. So if Var
were an array element such as A(I) then the
behaviour could vary according to the order. To encourage good practice,
it is also forbidden even when Var is a stand-alone
object.
Similarly, the procedure
call
Proc(Var, F(Var)); -- illegal
is illegal if the parameter
of F has mode in out. Examples of overlapping
are also forbidden such as
ProcA(A, F(A(K))); -- illegal
ProcR(R, F(R.C)); -- illegal
assuming still that F
has an in out parameter and that ProcA
and ProcR have appropriate profiles because,
as explained above, A and A(K)
are known to refer to the same object as are R
and R.C.
On the other hand
Proc(A(J), F(A(K))); -- OK
is permitted provided that J
and K are different objects because this is
only a problem if J and K
happen to have the same value.
For more details the reader is referred to the AI.
The intent is to detect situations that are clearly troublesome. Other
situations that might be troublesome (such as if J
and K happen to have the same value) are allowed,
since to prevent them would make many programs illegal that are not actually
dubious. This would cause incompatibilities and upset many users whose
programs are perfectly correct.
The other change in
Ada 2012 concerning parameters is that they may be explicitly marked
aliased thus
procedure P(X: aliased in out T; ... );
As a consequence within P
we can write X'Access. Recall that tagged
types were always considered implicitly aliased anyway and always passed
by reference. If the type T is a by-copy type
such as Integer, then adding aliased
causes it to be passed by reference. (So by-copy types are not always
passed by copy!)
The possibility of
permitting explicitly aliased function results such as
function F( ... )return aliased T; --illegal Ada 2012
was considered but this led to difficulties and so
was not pursued.
The syntax for parameter
specification is modified thus
parameter_specification ::=
defining_identifier_list: [aliased] mode [null exclusion] subtype_mark [:= default_expression]
| defining_identifier_list: access_definition [:= default_expression]
showing that aliased comes first as it does
in all contexts where it is permitted.
The rules for mode conformance are modified as expected.
Two profiles are only mode conformant if both or neither are explicitly
marked as aliased. Although adding aliased for a tagged type parameter
makes little difference since tagged types are implicitly aliased, if
this is done for a subprogram declaration then it must be done for the
corresponding body as well.
There are (of course) rules regarding accessibility;
these are much as expected although a special case arises in function
return statements. These allow a function to safely return an access
to a part of an explicitly aliased parameter and be assured that the
result will not outlive the parameter. As usual, if the foolish programmer
does something silly, the compiler will draw attention to the error.
Explicitly aliased parameters are used in the functions
Reference (and Constant_Reference) declared in the various container
packages. See Section
6.3 on Iteration and
Section
8.3 on Iterating and updating containers.
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: