Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

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 [notin range
  | simple_expression [notin 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 [notin 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.

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