Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

9.5 Postscript

It should also be noticed that a few corrections and improvements have been made since Ada 2012 was approved as a standard. The more important of these will now be discussed.
A new form of expression, the raise expression, is added (AI12-22). This means that by analogy with
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);
A raise expression is a new form of relation so the syntax for relation (see Section 3.6 of the chapter on Expressions) is extended as follows
relation ::=
    simple_expression [relational_operator simple_expression]
  | simple_expression [notin membership_choice_list
  | raise_expression
raise_expression ::=
    raise exception_name [with string_expression]
Since a raise expression is a relation it 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 are useful in 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";
On a closely related topic the new syntax for membership tests (also see Section 3.6 of the chapter on Expressions) has been found to cause ambiguities (AI12-39).
Thus
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 yet again to
relation ::=
    simple_expression [relational_operator simple_expression]
  | simple_expression [notin membership_choice_list
  | raise_expression
and changing
membership_choice ::=
    choice_expression | range | subtype_mark
to
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.
A curious difficulty has been found in attempting to use the seemingly innocuous package Ada.Locales described in Section 7.4 of the chapter on the Predefined Library.
The types Language_Code and Country_Code were originally declared as
type Language_Code is array (1 .. 3) of Character range 'a' .. 'z';
type Country_Code is array (1 .. 2) of Character range 'A' .. 'Z';
The problem is that a value of these types is not a string and cannot easily be converted into a string because of the range constraints and so cannot be a simple parameter of a subprogram such as Put. If LC is of type Language_Code then we have to write something tedious such as
Put(LC(1));  Put(LC(2));  Put(LC(3));
Accordingly, these types are changed so that they are derived from the type String and the constraints on the letters are then imposed by dynamic predicates. So we have
type Language_Code is new String(1 .. 3)
   with Dynamic_Predicate =>
      (for all E of Language_Code => E in 'a' .. 'z');
with a similar construction for Country_Code (AI12-37).
Readers might like to contemplate whether this is an excellent illustration of some of the new features of Ada 2012 or simply an illustration of static strong or maybe string typing going astray.
AI12-45 notes that pre- and postconditions are allowed on generic units but they are not allowed on instances. See Section 2.3 of the chapter on Contracts and Aspects where this topic should have been mentioned.
Another modification in this area is addressed by AI12-44 which states that type invariants are not checked on in parameters of functions but are checked on in parameters of procedures. See Section 2.4 of the chapter on Contracts and Aspects. This change was necessary to avoid infinite recursion which would arise if an invariant itself called a function with a parameter of the type. Note also that a class wide invariant could not be used at all without this modification.
A further aspect, Predicate_Failure, is defined by AI12-54-2. The expected type of the expression defined by this aspect is String and gives the message to be associated with a failure. So we can write
subtype Open_File_Type is File_Type
   with
      Dynamic_Predicate => Is_Open(Open_File_Type),
      Predicate_Failure =>  "File not open";
If the predicate fails then Assertion_Error is raised with the message "File not open". See Section 2.5 of the chapter on Contracts and Aspects.
We can also use a raise expression and thereby ensure that a more appropriate exception is raised. If we write
      Predicate_Failure =>
         raise Status_Error with "File not open";
then Status_Error is raised rather than Assertion_Error with the given message. We could of course explicitly mention Assertion_Error thus by writing
      Predicate_Failure =>
         raise Assertion_Error with "A message";
Finally, we could omit any message and just write
      Predicate_Failure => raise Status_Error;
in which case the message is null.
A related issue is discussed in AI12-71. If several predicates apply to a subtype which has been declared by a refined sequence then the predicates are evaluated in the order in which they occur. This is especially important if different exceptions are specified by the use of Predicate_Failure since without this rule the wrong exception might be raised. The same applies to a combination of predicates, null exclusions and old-fashioned subtypes.
This can be illustrated by an extension of the above example. Suppose we have
subtype Open_File_Type is File_Type
   with
      Dynamic_Predicate => Is_Open(Open_File_Type),
      Predicate_Failure => raise Status_Error;
subtype Read_File_Type is Open_File_Type
   with
      Dynamic_Predicate =>  Mode(Real_File_Type) = In_File,
      Predicate_Failure => raise Mode_Error with
                "Can't read file: " & Name(Read_File_Type);
The subtype Read_File_Type refines Open_File_Type. If the predicate for it were evaluated first and the file was not open then the call of Mode would raise Status_Error which we would not want to happen if we wrote
if F in Read_File_Type then ...
Care is needed with membership tests. The whole purpose of a membership test (and similarly the Valid attribute) is to find out whether a condition is satisfied. So if we write
if X in S then
   ...    -- do this
else
   ...    -- do that
end if;
we expect the membership test to be true or false. However, if the evaluation of S itself raises some exception then the purpose of the test is violated.
It is important to understand these related topics. Another example might clarify. Suppose we have a very simple predicate as in Section 2.5 of the chapter on Contracts and Aspects such as
subtype Winter is Month
   with Static_Predicate => Winter in Dec | Jan | Feb;
where
type Month is (Jan, Feb, Mar, Apr, ..., Nov, Dec);
and we declare a variable W thus
W: Winter := Jan;
If we now do
W := Mar;
then Assertion_Error will be raised because the value Mar is not within the subtype Winter (we assume that the assertion policy is Check). If, however, we would rather have Constraint_Error raised then we can modify the declaration of Winter to
subtype Winter is Month
   with Static_Predicate => Winter in Dec | Jan | Feb,
           Predicate_Failure => raise Constraint_Error;
and then obeying
W := Mar;
will raise Constraint_Error.
On the other hand suppose we declare a variable M thus
M: Month := Mar;
and then do a membership test
if M in Winter then
   ...        -- do this if M is a winter month
else
   ...        -- do this if M is not a winter month
end if;
then of course no exception is raised since this is a membership test and not a predicate check.
Note however, that we could write something odd such as
subtype Winter2 is Month
   with Dynamic_Predicate =>
     (if Winter2 in Dec | Jan | Feb then true else raise E);
then the very evaluation of the predicate might raise the exception E so that
in Winter2
will either be true or raise the exception E but will never be false. Note that in this silly example the predicate has to be a dynamic one because a static predicate cannot include a raise expression.
So this should clarify the reasons for introducing Predicate_Failure. It enables us to give a different behaviour for when the predicate is used in a membership test as opposed to when it is used in a check and it also allows us to add a message.
Finally, it should be noted that the predicate expression might involve the evaluation of some subexpression perhaps through the call of some function. We might have a predicate describing those months that have 30 days thus
subtype Month30 is Month
   with Static_Predicate =>
      Month30 in Sep | Apr | Jun | Nov;
which mimics the order in the nursery rhyme. However, suppose we decide to declare a function Days30 to do the check so that the subtype becomes
subtype Month30 is Month
   with Dynamic_Predicate => Days30(Month30);
and for some silly reason we code the function incorrectly so that it raises an exception (perhaps it accidentally runs into its end and always raises Program_Error). In this situation if we write
in Month30
then we will indeed get Program_Error and not false.
Perhaps this whole topic can be summarized by simply saying that a membership test is not a check. Indeed a membership test is often useful in ensuring that a subsequent check will not fail as was discussed in Section 6.4 of the chapter on Iterators, Pools etc.
On a rather different topic, AI12-28 discusses the import of variadic C functions (that is functions with a variable number of parameters). In Ada 95, it was expected that such functions would use the same calling conventions as normal C functions; however, that is not true for some targets today. Accordingly, this AI adds additional conventions to describe variadic C functions so that the Ada compiler can compile the correct calling sequence.
Finally, an important modification is made to the topic of dispatching domains by AI12-33. See Section 5.3 of the chapter on Tasking and Real-Time.
As defined originally, a dispatching domain consists of a set of processors whose CPU values are contiguous. However, this is unrealistic since CPUs are often grouped together in other ways. Accordingly, the package System.Multiprocessors.Dispatching_Domains is extended by the addition of a type CPU_Set and two further functions thus
type CPU_Set is array (CPU range <>) of Boolean;
function Create(Set: CPU_Set) return Dispatching_Domain;
function Get_CPU_Set(Domain: Dispatching_Domain) return CPU_Set;
So if we want to create a domain consisting of processors 2, 4, and 8 we can write
My_Set: CPU_Set(2 .. 8) :=
           (2 | 4 | 8 => trueothers => false);
and then
My_Domain: Dispatching_Domain := Create(My_Set);
and so on. The function Get_CPU_Set can be applied to any domain and returns the appropriate array representing the set of CPUs. Note that this function can be applied to any domain and not just to one created from a CPU_Set.

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