Rationale for Ada 2005: Introduction

RUSTOP
BACKNEXT

ENG

3.5 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

  1        raise Some_Error with "A message";

@ This is a lot neater than having to write (as in Ada 95)

  1        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

  1        procedure Process_Ex (X : Exception_Occurrence) is
  2        begin
  3                if Exception_Identity(X) = Null_Id then
  4                -- process the case of a Null_Occurrence
  5                ...
  6        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

  1        type Offset_Type is range -(2**31) .. 2**31-1;
  2        type Address_Type is mod 2**32;
  3        Offset  : Offset_Type;
  4        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

  1        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

  1        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

  1        function "*" (Left, Right : My_Int) return My_Int is
  2                pragma Unsuppress (Overflow_Check);
  3        begin
  4                return Integer (Left) * Integer (Right);
  5        exception
  6                when Constraint_Error =>
  7                if (0 < Left and 0 < Right) or (Left < 0 and Right < 0) then
  8                        return My_Int'Last;
  9                else
 10                        return My_Int'First;
 11                end if;
 12        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

  1        pragma Assert (50 < X);
  2        pragma Assert (not Buffer_Full, "buffer is full");

@ The first parameter is a Boolean expression and the second 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

  1        pragma Assertion_Policy (Check);
  2        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 is also related to 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. Thus we might have

  1        procedure Fatal_Error (Message : in String);
  2        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

  1        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

  1        union
  2        {
  3                double spvalue;
  4                struct
  5                {
  6                        int length;
  7                        double* first;
  8                } mpvalue;
  9        } number;

@ then this can be mapped in the Ada program by

  1        type Number (Kind : Precision) is
  2                record
  3                        case Kind is
  4                                when Single_Precision =>
  5                                        SP_Value        : Long_Float;
  6                                when Multiple_Precision =>
  7                                        MP_Value_Length : Integer;
  8                                        MP_Value_First  : access Long_Float;
  9                        end case;
 10                end record;
 11        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

  1        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

  1        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

  1        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

  1        generic
  2        with package P is new Q (<>);
  3        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

  1        generic
  2        with package R is new S (P1, P2, ... );
  3        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

  1        generic
  2        with package P is new Q (<>);
  3        with package R is new S (P.F1);
  4        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 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)

  1        with ... ;
  2        generic
  3        with package Real_Arrays is new Generic_Real_Arrays (<>);
  4        with package Complex_Types is new Generic_Complex_Types (Real_Arrays.Real);
  5        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

  1        with package Q is new R (P1, P2, F3 => <>);
  2        with package Q is new R (P1, others => <>);
  3        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 paper.

Rationale for Ada 2005: Introduction

ENGRUSTOP
BACKNEXT

3.5 Исключения, численные данные, настраиваемые средства и т.д.

@ Наряду с основными особенностями, обсуждаемыми выше, есть ряд усовершенствований в некоторых других областях.

@ Есть два небольших изменения относительно исключений. Первое, это то, что мы теперь можем выдать некое сообщение вместе с raise оператором:

  1        raise Some_Error with "A message";

@ Это гораздо более удобно чем писать на Аде 95:

  1        Ada.Exceptions.Raise_Exception (Some_Error'Identity, "A message");

@ Другое изменение касается обнаружения нулевого вхождения исключения, которое могло бы быть полезным в пакете анализа логики исключений. Проблема состоит в том, что вхождение исключений имеет ограниченный частный тип и таким образом мы не можем сравнить его с Null_Occurrence, чтобы определить, равны ли они. На Аде 95 применение функции Exception_Identity к нулевому вхождению бесполезно и возбуждает исключение Constraint_Error. Это было изменено в Аде 2005, чтобы возвратить Null_Id так, чтобы мы могли теперь написать:

  1        procedure Process_Ex (X : Exception_Occurrence) is
  2        begin
  3                if Exception_Identity (X) = Null_Id then
  4                        -- process the case of a Null_Occurrence
  5                        ...
  6        end Process_Ex;

@ В Аде 95 были введены модульные типы, которые являются целыми числами без знака. Однако, практика показала, что очень трудно заставить сотрудничать знаковые и беззнаковые числа. Этот тривиальный вопрос в таких языках как C в Аде с её тотальным контролем преобразования типов оказалася труднопреодолимым. Основная проблема - необходимость преобразования типов, который случается когда отрицательное число приводится к числу без знака. Пусть мы хотим добавить некоторое смещение к значению адреса без знака, тогда мы могли бы написать:

  1        type Offset_Type is range -(2**31) .. 2**31-1;
  2        type Address_Type is mod 2**32;
  3        Offset  : Offset_Type;
  4        Address : Address_Type;

@ Мы не можем просто добавить Offset к Address, потому что они имеют различные типы. При преобразовании Offset в тип Addres мы можем получить Constraint_Error. Теперь на Аде 2005 мы можем использовать новый функциональный атрибут S'Mod, который относится к любому модульному подтипу S и преобразовывает универсальное целочисленное значение в модульный тип, используя соответствующую математическую ультрасовременную операцию. Таким образом, мы можем теперь написать:

  1        Address := Address + Address_Type'Mod (Offset);

@ Другой новый атрибут - Machine_Rounding. Он разрешает высокоэффективное преобразование чисел с плавающей запятой в целочисленный тип, когда точное округление не имеет значения.

@ Третья числовая проблема связана с типами чисел с фиксированной точкой. Это была обычная практика для программ на Аде 83, чтобы определить их собственные операции умножения и деления, возможно получить арифметическое переполнение. На Аде 95 эти возможности вступили в конфликт с правилами, которые ввели универсальные установленные операции и привели к двусмысленностям. Без того, чтобы сообщать подробности эта проблема была решена на Аде 2005 так, чтобы определяемые пользователем операции могли бы теперь использоваться.

@ У Ады 2005 есть несколько новых прагм. Первая:

  1        pragma Unsuppress (Identifier);

@ где Identifier - идентификатор проверки, такой как Range_Check. Общее представление должно гарантировать, что проверки выполнены в декларативной области независимо от использования соответствующей прагмы Supress. Таким образом, у нас мог бы быть тип My_Int, который ведет себя как saturated тип. Текст:

  1        function "*" (Left, Right : My_Int) return My_Int is
  2                pragma Unsuppress (Overflow_Check);
  3        begin
  4                return Integer (Left) * Integer (Right);
  5        exception
  6                when Constraint_Error =>
  7                        if (0 < Left and 0 < Right) or (Left < 0 and Right < 0) then
  8                                return My_Int'Last;
  9                        else
 10                                return My_Int'First;
 11                        end if;
 12        end "*";

@ гарантирует, что код всегда работает как предназначено, даже если проверки подавлены в программе в целом. Случайно параметр On прагмы Supress, который никогда не использовался был помещён в Приложение J.

@ Большинство реализаций ada поддерживают прагму Assert, она теперь включена в стандарт Ады 2005. Теперь мы можем написать:

  1        pragma Assert (50 < X);
  2        pragma Assert (not Buffer_Full, "buffer is full");

@ Первый параметр прагмы - булевское выражение, второй дополнительный параметр - строка. Если при выполнении прагмы первый параметр принимает значение - False тогда некое действие может быть предпринято. Этим действием управляет другая прагма Assertion_Policy, которая может переключить механизм утверждения в положение ВКЛ. или ВЫКЛ. одним из выражений:

  1        pragma Assertion_Policy (Check);
  2        pragma Assertion_Policy (Ignore);

@ Если политика установлена Check, тогда исключение Assertion_Error возбуждается с сообщением, если любой. Это исключение объявлено в предопределенной пакете Ada.Assertions. Также есть некоторые другие средства.

@ Прагма No_Return также связана с исключениями. Она может быть применена к процедуре (не к функции) и указывает, что процедура никогда не возвращает управление обычным образом, только размножая исключение. Таким образом, мы могли бы написать:

  1        procedure Fatal_Error (Message : in String);
  2        pragma No_Return (Fatal_Error);

@ И теперь всякий раз, когда мы вызываем процедуру Fatal_Error, компилятор уверен, что управление не будет возвращено, и может выполнить соответствующую оптимизацию и/или повысить адекватность сообщений при исключениях.

@ Отметим, что эта прагма относится к предопределенной процедуре Ada.Exceptions.Raise_Exception.

@ Другая новая прагма - Preelaborable_Initialization. Она используется с частными типами и указывает, что у полного типа будет preelaborable инициализация. Большинство примеров можно найти в предопределенных пакетах, например в таком как

  1        pragma Preelaborable_Initialization (Controlled);

@ в Ada.Finalization.

@ Наконец, есть прагма Unchecked_Union. Она полезна для интерфейса с программами, написанным на C, которые используют понятие объединений. Объединения в C соответствуют вариантным записям Ады, но без сохранения дискриминантов, который находится полностью в уме программиста C. Эта прагма дает возможность объединению C отображаться в тип вариантной записи Ады, опуская память для дискриминанта. Если программа C имеет следующий код:

  1        union
  2        {
  3                double spvalue;
  4                struct
  5                {
  6                        int length;
  7                        double* first;
  8                } mpvalue;
  9        } number;

@ тогда соответствующий код на Adе будет выглядеть как:

  1        type Number (Kind : Precision) is
  2                record
  3                        case Kind is
  4                                when Single_Precision =>
  5                                        SP_Value        : Long_Float;
  6                                when Multiple_Precision =>
  7                                        MP_Value_Length : Integer;
  8                                        MP_Value_First  : access Long_Float;
  9                        end case;
 10                end record;
 11        pragma Unchecked_Union (Number);

@ Одна проблема с прагмами (и атрибутами) состоит в том, что много реализаций добавили свои специфические прагмы (поскольку это действительно разрешено стандартом). Однако, это может препятствовать переносимости программ из одной реализации в другую. Для преодоления этой проблемы имеется идентификатор Restrictions, и теперь мы можем написать:

  1        pragma Restrictions (No_Implementation_Pragmas, No_Implementation_Attributes);

@ Заметим, что одна из целей Ады 2005 стандартизировать все атрибуты и прагмы насколько возможно.

@ Внимательный читатель мог бы обратить внимание на парадокс, что у GNAT есть (определенный реализацией) идентификатор ограничений No_Implementation_Restrictions.

@ Другой новый идентификатор ограничений предотвращает нас от небрежного использования особенностей внесённых в Приложение J следующим образом:

  1        pragma Restrictions (No_Obsolescent_Features);

@ Так же мы можем использовать идентификатор ограничений No_Dependence, чтобы заявить, что программа не зависит от данного библиотечного модуля. Таким образом мы могли бы написать:

  1        pragma Restrictions (No_Dependence => Ada.Command_Line);

@ Отметим, что упомянутый модуль мог бы быть предопределенным библиотечным модулем как в вышеупомянутом примере, но этот идентификатор может также использоваться с любым другим библиотечным модулем.

@ Последняя новая особенность касается формальных параметров настраиваемых пакетов. Ада 95 ввела возможность иметь формальные пакеты в виде параметров универсальных модулей. Это значительно уменьшало потребность в длинных универсальных списках параметров, так как формальный пакет формировал их.

@ Иногда для настраиваемого модуля необходимо иметь два (или больше) формальных пакета. При этом, часто необходимо чтобы некоторые из фактических параметров одного формального пакета должны быть идентичны таковым из другого. Чтобы разрешить это имеется две формы настраиваемых параметров. Первая:

  1        generic
  2                with package P is new Q (<>);
  3        package Gen is ...

@ и затем пакет Gen может быть проиллюстрирован с любым пакетом, который является реализацией Q. Другим способом это делается так:

  1        generic
  2                with package R is new S (P1, P2, ... );
  3        package Gen is ...

@ и затем пакет Gen может только быть проиллюстрирован с пакетом, который является реализацией S с данными фактическими параметрами P1, P2 и т.д.

@ Эти механизмы часто используются вместе:

  1        generic
  2                with package P is new Q (<>);
  3                with package R is new S (P.F1);
  4        package Gen is ...

@ Это гарантирует, что у реализации S есть тот же самый фактический параметр (принимал только один в этом примере) как параметр F1 Q, используемого в реализации Q, чтобы создать фактический пакет, соответствующий P.

@ Есть пример этого в одном из пакетов для векторов и матриц в ISO/IEC 13813, который теперь включен в Аду 2005 (см. Раздел 3.6). У настраиваемого пакета для сложных массивов есть два параметра пакета. Первый - соответствующий пакет для реальных массивов, другой - пакет Generic_Complex_Types от существующего приложения Численных данных. У обоих из этих пакетов есть тип с плавающей точкой как единственный формальный параметр, и важно, что обе реализации используют один и тот же тип с плавающей точкой (оба с плавающей точкой, а не один с плавающей точкой, а другой Long_Float) иначе, произойдет нечто ужасное. Это гарантируется написанием (здесь мы опускаем некоторые подробности):

  1        with ... ;
  2        generic
  3                with package Real_Arrays is new Generic_Real_Arrays (<>);
  4                with package Complex_Types is new Generic_Complex_Types (Real_Arrays.Real);
  5        package Generic_Complex_Arrays is ...

@ Это прекрасно работает в простых случаях (читатель может задаться вопросом, прост ли этот пример или нет), но в более сложных ситуациях это - большая головная боль. Неприятность состоит в том, что мы должны дать все параметры для формального пакета или ни одного вообще в Аде 95.

@ Ада 2005 разрешает быть определенными только части параметров, а любой не определенный параметр может быть обозначен блоком <>. Таким образом, мы можем написать любой из следующих вариантов:

  1        with package Q is new R (P1, P2, F3 => <>);
  2        with package Q is new R (P1, others => <>);
  3        with package Q is new R (F1 => <>, F2 => P2, F3 => P3);

@ Отметим, что существующая форма (<>) является сокращением для (others => <>). Во всех остальных случаях применение формы <> разрешено только с именоваными агрегатами.

@ Примеры использования этого нового средства будут даны в более поздней публикации.

ENG RUS

TOP BACK NEXT

2010-10-24 00:26:52

. .