Rationale for Ada 2012
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 [not] in 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
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 yet again to
relation ::=
simple_expression [relational_operator simple_expression]
| simple_expression [not] in 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
M 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
M 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 => true, others => 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.
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: