Rationale for Ada 2005

John Barnes
Contents   Index   References   Search   Previous   Next 

1.3.5 Overview: Exceptions, numerics, generics etc

As well as the major features discussed above there are also a number of improvements in various other areas.
There are two small changes concerning exceptions. One is that we can give a message with a raise statement, thus 
raise Some_Error with "A message";
This is a lot neater than having to write (as in Ada 95) 
Ada.Exceptions.Raise_Exception(Some_Error'Identity, "A message");
The other change concerns the detection of a null exception occurrence which might be useful in a package analysing a log of exceptions. The problem is that exception occurrences are of a limited private type and so we cannot compare an occurrence with Null_Occurrence to see if they are equal. In Ada 95 applying the function Exception_Identity to a null occurrence unhelpfully raises Constraint_Error. This has been changed in Ada 2005 to return Null_Id so that we can now write 
procedure Process_Ex(X: Exception_Occurrence) is
begin
   if Exception_Identity(X) = Null_Id then
      -- process the case of a Null_Occurrence
   ...
end Process_Ex;
Ada 95 introduced modular types which are of course unsigned integers. However it has in certain cases proved very difficult to get unsigned integers and signed integers to work together. This is a trivial matter in fragile languages such as C but in Ada the type model has proved obstructive. The basic problem is converting a value of a signed type which happens to be negative to an unsigned type. Thus suppose we want to add a signed offset to an unsigned address value, we might have 
type Offset_Type is range –(2**31) .. 2**31–1;
type Address_Type is mod 2**32;
Offset: Offset_Type;
Address: Address_Type;
We cannot just add Offset to Address because they are of different types. If we convert the Offset to the address type then we might get Constraint_Error and so on. The solution in Ada 2005 is to use a new functional attribute S'Mod which applies to any modular subtype S and converts a universal integer value to the modular type using the corresponding mathematical mod operation. So we can now write 
Address := Address + Address_Type'Mod(Offset);
Another new attribute is Machine_Rounding. This enables high-performance conversions from floating point types to integer types when the exact rounding does not matter.
The third numeric change concerns fixed point types. It was common practice for some Ada 83 programs to define their own multiply and divide operations, perhaps to obtain saturation arithmetic. These programs ran afoul of the Ada 95 rules that introduced universal fixed operations and resulted in ambiguities. Without going into details, this problem has been fixed in Ada 2005 so that user-defined operations can now be used.
Ada 2005 has several new pragmas. The first is 
pragma Unsuppress(Identifier);
where the identifier is that of a check such as Range_Check. The general idea is to ensure that checks are performed in a declarative region irrespective of the use of a corresponding pragma Suppress. Thus we might have a type My_Int that behaves as a saturated type. Writing 
function "*" (Left, Right: My_Int) return My_Int is
   pragma Unsuppress(Overflow_Check);
begin
   return Integer(Left) * Integer(Right);
exception
   when Constraint_Error =>
      if (Left>0 and Right>0) or (Left<0 and Right<0) then
         return My_Int'Last;
      else
         return My_Int'First;
      end if;
end "*";
ensures that the code always works as intended even if checks are suppressed in the program as a whole. Incidentally the On parameter of pragma Suppress which never worked well has been banished to Annex J.
Many implementations of Ada 95 support a pragma Assert and this is now consolidated into Ada 2005. The general idea is that we can write pragmas such as 
pragma Assert(X >50);
pragma Assert(not Buffer_Full, "buffer is full");
The first parameter is a Boolean expression and the second (and optional) parameter is a string. If at the point of the pragma at execution time, the expression is False then action can be taken. The action is controlled by another pragma Assertion_Policy which can switch the assertion mechanism on and off by one of
pragma Assertion_Policy(Check);
pragma Assertion_Policy(Ignore);
If the policy is to check then the exception Assertion_Error is raised with the message, if any. This exception is declared in the predefined package Ada.Assertions. There are some other facilities as well.
The pragma No_Return also concerns exceptions. It can be applied to a procedure (not to a function) and indicates that the procedure never returns normally but only by propagating an exception (it might also loop for ever). Thus
procedure Fatal_Error(Message: in String);
pragma No_Return(Fatal_Error);
And now whenever we call Fatal_Error the compiler is assured that control is not returned and this might enable some optimization or better diagnostic messages.
Note that this pragma applies to the predefined procedure Ada.Exceptions.Raise_Exception.
Another new pragma is Preelaborable_Initialization. This is used with private types and indicates that the full type will have preelaborable initialization. A number of examples occur with the predefined packages such as 
pragma Preelaborable_Initialization(Controlled);
in Ada.Finalization.
Finally, there is the pragma Unchecked_Union. This is useful for interfacing to programs written in C that use the concept of unions. Unions in C correspond to variant types in Ada but do not store any discriminant which is entirely in the mind of the C programmer. The pragma enables a C union to be mapped to an Ada variant record type by omitting the storage for the discriminant.
If the C program has
union {
   double spvalue;
   struct {
      int length;
      double* first;
      } mpvalue;
} number;
then this can be mapped in the Ada program by 
type Number(Kind: Precision) is
   record
      case Kind is
         when Single_Precision =>
            SP_Value: Long_Float;
         when Multiple_Precision =>
            MP_Value_Length: Integer;
            MP_Value_First: access Long_Float;
      end case;
   end record;
pragma Unchecked_Union(Number);
One problem with pragmas (and attributes) is that many implementations have added implementation defined ones (as they are indeed permitted to do). However, this can impede portability from one implementation to another. To overcome this there are further Restrictions identifiers so we can write 
pragma Restrictions(No_Implementation_Pragmas, No_Implementation_Attributes);
Observe that one of the goals of Ada 2005 has been to standardize as many of the implementation defined attributes and pragmas as possible.
Readers might care to consider the paradox that GNAT has an (implementation-defined) restrictions identifier No_Implementation_Restrictions.
Another new restrictions identifier prevents us from inadvertently using features in Annex J thus
pragma Restrictions(No_Obsolescent_Features);
Similarly we can use the restrictions identifier No_Dependence to state that a program does not depend on a given library unit. Thus we might write 
pragma Restrictions(No_Dependence => Ada.Command_Line);
Note that the unit mentioned might be a predefined library unit as in the above example but it can also be used with any library unit.
The final new general feature concerns formal generic package parameters. Ada 95 introduced the ability to have formal packages as parameters of generic units. This greatly reduced the need for long generic parameter lists since the formal package encapsulated them.
Sometimes it is necessary for a generic unit to have two (or more) formal packages. When this happens it is often the case that some of the actual parameters of one formal package must be identical to those of the other. In order to permit this there are two forms of generic parameters. One possibility is
generic
   with package P is new Q(<>);
package Gen is ...
and then the package Gen can be instantiated with any package that is an instantiation of Q. On the other hand we can have 
generic
   with package R is new S(P1, P2, ... );
package Gen is ...
and then the package Gen can only be instantiated with a package that is an instantiation of S with the given actual parameters P1, P2 etc.
These mechanisms are often used together as in 
generic
   with package P is new Q(<>);
   with package R is new S(P.F1);
package Gen is ...
This ensures that the instantiation of S has the same actual parameter (assumed only one in this example) as the parameter F1 of Q used in the instantiation of Q to create the actual package corresponding to P.
There is an example of this in one of the packages for vectors and matrices in ISO/IEC 13813 which is now incorporated into Ada 2005 (see Section 1.3.6). The generic package for complex arrays has two package parameters. One is the corresponding package for real arrays and the other is the package Generic_Complex_Types from the existing Numerics annex. Both of these packages have a floating type as their single formal parameter and it is important that both instantiations use the same floating type (eg both Float and not one Float and one Long_Float) otherwise a terrible mess will occur. This is assured by writing (using some abbreviations) 
with ... ;
generic
   with package Real_Arrays is new Generic_Real_Arrays(<>);
   with package Complex_Types is new Generic_Complex_Types(Real_Arrays.Real);
package Generic_Complex_Arrays is ...
Well this works fine in simple cases (the reader may wonder whether this example is simple anyway) but in more elaborate situations it is a pain. The trouble is that we have to give all the parameters for the formal package or none at all in Ada 95.
Ada 2005 permits only some of the parameters to be specified, and any not specified can be indicated using the box. So we can write any of 
with package Q is new R(P1, P2, F3 => <>);
with package Q is new R(P1, others => <>);
with package Q is new R(F1 => <>, F2 => P2, F3 => P3);
Note that the existing form (<>) is now deemed to be a shorthand for (others => <>). As with aggregates, the form <> is only permitted with named notation.
Examples using this new facility will be given in a later chapter (see 6.5).

Contents   Index   References   Search   Previous   Next 
© 2005, 2006, 2007 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association and its member companies: ARA Members AdaCore Polyspace Technologies Praxis Critical Systems IBM Rational Sofcheck and   Ada-Europe:
Ada-Europe