Rationale for Ada 2012
3.6 Membership tests
Membership tests in Ada 83 to Ada 2005 are somewhat
restrictive. They take two forms
to test whether a value is in a given range, or
to test whether a value is in a given subtype.
Examples of these are
if M in June .. August then
if I in Index then
However, the restrictions
are annoying. If we want to test whether it is safe to eat an oyster
(there has to be an R in the month) then we would really like to write
if M in
Jan .. April | Sep .. Dec then -- illegal in Ada 2005
whereas we are forced
to write something like
if M in Jan .. April or M in Sep .. Dec then
which means repeating
M and then perhaps worrying about whether
to use or or or else. Or in this case we could do the test
the other way
if M not in May .. Aug then
What we would really like to do is use the vertical
bar as in case statements and aggregates to select a combination of ranges,
subtypes, and values.
Ada 2012 is much more
flexible in this area. To see the differences it is probably easiest
to look at the old and new syntax. The relevant old syntax for Ada 2005
is
relation ::=
simple_expression [relational_operator simple_expression]
| simple_expression [not] in range
| simple_expression [not] in subtype_mark
where the last two
productions define membership tests. The syntax regarding choices in
aggregates and case statements in Ada 2005 is
discrete_choice_list ::= discrete_choice { | discrete_choice}
discrete_choice ::= expression | discrete_range | others
discrete_range ::= discrete_subtype_indication | range
The syntax in Ada 2012
is rather different and changes relation to
use new productions for membership_choice_list
and membership_choice (this enables the vertical
bar to be used in membership tests). And then a membership_choice
in turn uses choice_expression and choice_relation
as follows
relation ::=
simple_expression [relational_operator simple_expression]
| simple_expression [not] in membership_choice_list}
membership_choice_list ::=
membership_choice { | membership_choice}
membership_choice ::=
choice_expression | range | subtype_mark
choice_expression ::=
choice_relation {and choice_relation}
| choice_relation {or choice_relation}
| choice_relation {xor choice_relation}
| choice_relation {and then choice_relation}
| choice_relation {or else choice_relation}
choice_relation ::=
simple_expression [relational_operator simple_expression]}
The difference between
a choice_relation and a relation
is that the choice_relation does not include
membership tests. Moreover, discrete_choice
is changed to
discrete_choice ::= choice_expression
| discrete_subtype_indication | range
| others
the difference being that a discrete_choice
now uses a choice_expression rather than an
expression as one of its possibilities.
The overall effect of the changes is to permit the
vertical bar in membership tests without getting too confused by nesting
membership tests.
Here are some examples
that are now permitted in Ada 2012 but were not permitted in Ada 2005
if N in 6 | 28 | 496 then
-- N is small and perfect!
if M in Spring | June | October .. December then
-- combination of subtype, single value and range
if X in 0.5 .. Z | 2.0*Z .. 10.0 then
-- not discrete or static
if Obj in Triangle | Circle then
-- with tagged types
if Letter in 'A' | 'E' | 'I' | 'O' | 'U' then
-- characters
Membership tests are permitted for any type and values
do not have to be static. There is no change here but it should be remembered
that existing uses of the vertical bar in case statements and aggregates
do require the type to be discrete and the values to be static.
Another important point about membership tests is
that the membership choices are evaluated in order and as soon as one
is found to be true (or false if not is present) then the relation
as a whole is determined and the other membership choices are not evaluated.
This is therefore the same as using short circuit forms such as or
else and so gives another example of expressions which are statically
unevaluated.
There is one very minor
incompatibility. In Ada 2005 we can write
X: Boolean := ...
case X is
when Y in 1 .. 10 => F(10);
when others => F(5);
end case;
This is rather peculiar. The discrete choice Y
in 1 .. 10 must be static. Suppose
Y is 5, so that
Y in 1 .. 10
is True; then if X
is True, we call F(10)
whereas if X is False
we call F(5). And vice versa for values of
Y not in the range 1 to 10.
This is syntactically
illegal in Ada 2012 because a discrete choice can no longer be an expression
and so be a membership test. This was imposed because otherwise we might
have been tempted to write
X: Boolean := ...
case X is
when Y in 1 .. 10 | 20 => F(10);
when others => F(5);
end case;
and this is syntactically ambiguous because it might
be parsed as (Y in 1
.. 10) | 20 rather than as if we were allowed to write Y
in (1 .. 10) | 20. Although it would
be rejected anyway because of the type mismatch.
A nastier example might
make this clearer. Consider
case X is
when Y in False | True => Do_This;
when others => Do_That;
end case;
Now suppose that Y itself
is of type Boolean. Is it (Y in False)
| True rather than Y in (False
| True)? If Y happens to have the value
True then the first interpretation gives False
| True so whatever the value of X we
always Do_This but in the second interpretation
we get just True so if X
happens to be False we Do_That.
So it really is seriously ambiguous without any type mismatch in sight
and has to be forbidden.
However, this is clearly very unlikely to be a problem.
Case statements over Boolean types are pretty rare anyway.
There is one other change to membership tests which
concerns access types and so will be considered again in Section
6.4
when we discussion access types and storage pools. The change is that
membership tests can be used to check accessibility.
It is often the case
that one uses a membership test before a conversion to ensure that the
conversion will succeed. This avoids raising an exception which then
has to be handled. Thus we might have
subtype Score is Integer range 1 .. 60;
Total: Integer;
S: Score;
... -- compute Total somehow
if Total in Score then
S := Score(Total); -- reliable conversion
... -- now use S knowing that it is OK
else
... -- Total was excessive
end if;
If we are indexing some arrays whose range is Score
then it is an advantage to use S as an index
since we know it will work and no checks are needed.
However, in Ada 2005,
we cannot use a membership test to check accessibility. But Ada 2012
permits this and we can write
type Ptr is access all T;
procedure P(X: access T) is
Local: Ptr;
begin
if X in Ptr then
Local := Ptr(X); -- reliable conversion
... -- now use Ptr knowing that it is OK
else
... -- would have failed accessibility check
end if;
end P;
We could also do the
check in a precondition thus
procedure P(X: access T)
with Pre => X in Ptr;
Here we have a precondition where the expression
is simply a membership test X in Ptr.
Of course this does not avoid the exception because it will raise Assertion_Error
if the accessibility is wrong.
Finally, note that two changes have been made in
the syntax for relation since ISO standardization. One concerns the addition
of a new form of expression, the raise expression; the other concerns
an ambiguity discovered in membership tests. These changes are described
in Section
9.5 of the Epilogue.
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: