Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

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.

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

    ARA
  AdaCore:


    AdaCore
and   Ada-Europe:

Ada-Europe