Rationale Update for Ada 2012

John Barnes
Contents   Index   References   Previous   Next 

Chapter 3: Expressions

The introduction of contracts triggered the need for more flexible forms of expressions in Ada 2012. These are conditional expressions (if and case), quantified expressions, and expression functions. In addition membership tests were made much more flexible.
The following Ada issues cover this area:
Raise expressions
Ambiguity in syntax for membership expression removed
Resolving the selected_expression of a case_expression
Conformance of quantified expressions
Raise expression with failing string function
Box expressions in array aggregates
A qualified expression makes a predicate check
Expression functions that are completions in package specifications
Add raise expression to Introduction
Expression functions and null procedures can be declared in a protected_body
Eliminate ambiguities in raise expression and derived type syntax
Missing rules for expression functions
Definition of quantified expressions
These changes can be grouped as follows.
The most important change in this update is perhaps the introduction of raise expressions (22, 62, 141, 152); it is convenient to discuss a change to the syntax for membership test at the same time (39).
A number of changes relate to expression functions (103, 147, 157). There are also some clarifications regarding quantified expressions (50, 158).
There are miscellaneous changes to qualified expressions (100), case expressions (40), and array aggregates (84).

The introduction of raise expressions by AI-22 was deemed important enough to be mentioned in the Introduction to the revised RM. It was discussed in some detail in the Postscript chapter of the Ada 2012 Rationale which was written after the Ada 2012 standard was published. However, the discussion there needs updating since the syntax rules have been modified as a consequence of ambiguities mentioned in AI-39 and AI-152. So here is a more integrated description.
The raise expression, is added by analogy with if statements and the raise statement. Thus as well as
if X < Y then
   Z := +1;
elsif X > Y then
   Z := –1;
   raise Error;
end if;
we can also write
Z := (if X<Y then 1 elsif X>Y then –1 else raise Error);
The syntax for raise expression is now as follows
raise_expression ::=
       raise exception_name [with string_simple_expression]
Note that unlike in a raise statement, the string expression has to be a simple_expression rather than an expression in order to avoid ambiguities involving logical operations.
A raise expression is a new form of relation (as will be seen in the syntax in a moment) and has the same precedence and so will need to be in parentheses in some contexts. But as illustrated above it does not need parentheses when used in a conditional expression which itself will have parentheses.
Raise expressions will be found useful with pre- and postconditions. Thus if we have
procedure Push(S: in out Stack; X: in Item)
      Pre => not Is_Full(S);
and the precondition is false then Assertion_Error is raised. But we can now alternatively write
procedure Push(S: in out Stack; X: in Item)
      Pre => not Is_Full(S) or else raise Stack_Error;
and of course we can also add a message thus
      Pre => not Is_Full(S) or else
 Stack_Error with "wretched stack is full";
Another issue concerns what happens if the string expression in a raise expression (or indeed in a raise statement) itself raises an exception; it could be a function call which returns the string. The answer is that the one caused by the string expression is propagated instead of the one given in the raise expression or statement (AI-62).
On a closely related topic the syntax for membership tests has been found to cause ambiguities (AI-39).
in B and C
could be interpreted as either of the following 
(A in B) and C       -- or
in (B and C)
This is cured by changing the syntax for relation to
relation ::=
    simple_expression [relational_operator simple_expression]
tested_simple_expression [notin membership_choice_list
  | raise_expression
and changing membership choice to use simple_expression as well
membership_choice ::=
    choice_simple_expression | range | subtype_mark
Thus a membership_choice no longer uses a choice_expression. However, the form choice_expression is still used in discrete_choice.
When first written, AI-22 showed the syntax for a raise expression using string_expression just as in raise statement. However, this caused ambiguities as mentioned earlier so it was changed to string_simple_expression by AI-152. Curiously enough it was also necessary to change the syntax of digits constraint and delta constraint to use simple expression as well. The AI has the following bizarre example
Atomic: String := "Gotcha";
type Fun is new My_Decimal_Type digits
   raise TBD_Error with Atomic;
This could be parsed as either
type Fun is new My_Decimal_Type digits
   (raise TBD_Error with Atomic);
type Fun is new My_Decimal_Type digits
   (raise TBD_Error) with Atomic;
So we now have
digits_constraint ::=
   digits static_simple_expression [range_constraint]
delta_constraint ::=
   delta static_simple_expression [range_constraint]
It seems cruel to have to change delta constraint which one might have thought was peacefully buried in Annex J for obsolescent features.
These potential ambiguities are unlikely to impact the normal user. If the compiler complains then the judicious insertion of some parentheses will undoubtedly cure the problem.
Expression functions were added in Ada 2012. Remember that an expression function takes the form
function F ( ... ) return T is
   (expression of subtype T);
A good example was given earlier thus
   function Non_Zero(X: T) return Boolean is
      (X /= 0);
Remember that such functions can act as a complete function or as a completion of a traditional function specification.
A number of points were overlooked in the definition of Ada 2012. One has already been mentioned namely that function expressions acting as completions cannot have pre- and postconditions (see AI-105 in Chapter 2).
Another point is that expression functions and indeed null procedures can be used in the body of a protected type as a completion of a protected subprogram (AI-147). This requires a modification to the syntax which becomes
protected_operation_item ::=
  | subprogram_body
  | null_procedure_declaration
  | expression_function_declaration
  | entry_body
  | aspect_clause
An interesting situation arises if the result expression of an expression function can be written as an aggregate as for example
function Conjugate (C: Complex) return Complex is
   ((C.Rl, –C.Im));         -- double parens
Remember that the conjugate of a complex number C has the same real part but the imaginary part changes sign so that the conjugate point in the Argand plane is the reflection of C in the real axis.
The original rules say that the expression of an expression function is simply an expression in parentheses. However, this is ugly if the expression already has parentheses as occurs with an aggregate as above. Now Ada dislikes double parentheses so we have rules for if expressions that they have to be in parentheses unless the context already supplies parentheses as in the case of a subprogram call with a single parameter. Consequently, AI-157 concludes that the second lot of parentheses are unnecessary so we can just write
function Conjugate (C: Complex) return Complex is
   (C.Rl, –C.Im);           -- single parens
As a result the syntax is revised to
expression_function_declaration ::=
    [overriding _indicator]
        [aspect_specification] ;
  | [overriding _indicator]
        [aspect_specification] ;
The above example shows a record aggregate. The same applies to array aggregates in parentheses. But of course if the result is given as a string then the parentheses are necessary. So we can have either of
function Piggy return String is ('P', 'I', 'G');
function Piggy return String is ("PIG");
There are also changes to the freezing rules which will probably leave the reader cold. These are in AI-157 and AI-103. A simple one is that expression functions acting as completions only freeze the expression and nothing else and null procedures never freeze anything.
There are a couple of minor points regarding quantified expressions.
AI-158 clarifies the result of a quantified expression where the array concerned has zero elements. In the case of the existential qualifier for some, the result is False whereas in the case of the universal qualifier for all, the result is True. So consider
B := (for all K in A'Range => A(K) = 0);
which assigns true to B if every element of A is zero. If A doesn't have any elements then the result is still true. Similarly
B := (for some K in A'Range => A(K) = 0);
assigns true to B provided at least one element of A is zero. If A doesn't have any elements then the answer is false. This all seems pretty obvious but the wording was deemed to require clarification for those not having a Fields Medal in mathematics.
Another quirk is discussed by AI-50 which is concerned with conformance. Remember that the parameters in the specification and body of a subprogram have to conform. The introduction of quantified expressions means that such an expression could occur as the default value in a subprogram specification; thus using the example above we might have
procedure P(B : Boolean :=
   (for all K in A'Range => A(K) = 0));
The corresponding text in the procedure body has to conform and additional rules are required to ensure this. The new thing is that quantified expressions introduce declarations such as that of K in the parameter list and we have to say that these two (technically different) declarations are the same in specification and body and specifically that the two defining identifiers are the same and are used in the same way.
A minor omission concerns qualified expressions (AI-100) Remember the difference between a qualified expression and a conversion. Qualification (which takes a quote) just states that an expression has the given (sub)type and is often used for resolving ambiguities. Conversion (which does not have a quote) actually changes the type (if necessary). Qualification also checks any relevant subtype properties. But on the addition of subtype predicates although it was added that they were checked on type conversions, it was forgotten to add that any subtype predicates should also be checked on qualification.
A very minor omission is covered by AI-40 which says that case expressions and case statements resolve in exactly the same way — that is have the same rules for type matching.
Finally, AI-84 concerns the use of the box notation <> in array aggregates. This was added in Ada 2005 and indicates that a component takes its default value which is the same as the default value for a stand-alone object.
However, Ada 2012 added the aspects Default_Value and Default_Component_Value. So we might write
type My_Integer is new Integer
   with Default_Value => 999;
type My_Array is array (Integer range <>) of My_Integer
   with Default_Component_Value => 666;
If we declare
X: My_Array(1 .. 10);
then the value of X(1) will be 666 of course using the aspect Default_Component_Value.
But if we write
X: My_Array(1..10) := (others => <>);
then very surprisingly X(1) was 999 rather than 666. The rules for <> were not updated to note that if the Default_Component_Value has been given then that applies rather than the stand-alone value. This is put right by AI-84 so that the value is now 666 in both cases.

Contents   Index   References   Previous   Next 
© 2016 John Barnes Informatics.