Rationale Update for Ada 2012
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;
else
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)
with
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)
with
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
raise 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).
Thus
A in B and C
could be interpreted as either of the following
(A in B) and C -- or
A in (B and C)
This is cured by changing
the syntax for relation to
relation ::=
simple_expression [relational_operator simple_expression]
| tested_simple_expression [not] in 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);
or
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_declaration
| 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]
function_specification is
(expression)
[aspect_specification] ;
| [overriding _indicator]
function_specification is
aggregate
[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.
© 2016 John Barnes Informatics.