Rationale for Ada 2005: Exceptions, generics etc

RUSTOP
BACKNEXT

ENG

5. Generic units

@ There are a number of improvements in the area of generics many of which have already been outlined in earlier papers.

@ A first point concerns access types. The introduction of types that exclude null means that a formal access type parameter can take the form

  1        generic
  2                ...
  3                type A is not null access T;
  4                ...

@ The actual type corresponding to A must then itself be an access type that excludes null. A similar rule applies in reverse – if the formal parameter excludes null then the actual parameter must also exclude null. If the two did not match in this respect then all sorts of difficulties could arise.

@ Similarly if the formal parameter is derived from an access type

  1        generic
  2                ...
  3                type FA is new A; -- A is an access type
  4                ...

@ then the actual type corresponding to FA must exclude null if A excludes null and vice versa. Half of this rule is automatically enforced since a type derived from a type that excludes null will automatically exclude null. But the reverse is not true as mentioned in an earlier paper when discussing access types. If A has the declaration

  1        type A is access all Integer; -- does not exclude null

@ then we can declare

  1        type NA is new A; -- does not exclude null
  2        type NNA is new not null A; -- does exclude null

@ and then NA matches the formal parameter FA in the above generic but NNA does not.

@ There is also a change to formal derived types concerning limitedness. In line with the changes described in the paper on the object oriented model, the syntax now permits limited to be stated explicitly thus

  1        generic
  2                type T is limited new LT; -- untagged
  3                type TT is limited new TLT with private; -- tagged

@ However, this can be seen simply as a documentation aid since the actual types corresponding to T and TT must be derived from LT and TLT and so will be limited if LT and TLT are limited anyway.

@ Objects of anonymous access types are now also allowed as generic formal parameters so we can have

  1        generic
  2                A  : access T := null;
  3                AN : in out not null access T;
  4                F  : access function (X : Float) return Float;
  5                FN : not null access function (X : Float) return Float;

@ If the subtype of the formal object excludes null (as in AN and FN) then the actual must also exclude null but not vice versa. This contrasts with the rule for formal access types discussed above in which case both the formal type and actual type have to exclude null or not. Note moreover that object parameters of anonymous access types can have mode in out.

@ If the subprogram profile itself has access parameters that exclude null as in

  1        generic
  2                PN : access procedure (AN : not null access T);

@ then the actual subprogram must also have access parameters that exclude null and so on. The same rule applies to named formal subprogram parameters. If we have

  1        generic
  2                with procedure P (AN : not null access T);
  3                with procedure Q (AN : access T);

@ then the actual corresponding to P must have a parameter that excludes null but the actual corresponding to Q might or might not. The rule is similar to renaming – "not null must never lie".

@ Remember that the matching of object and subprogram generic parameters is defined in terms of renaming. Here is an example to illustrate why the asymmetry is important. Suppose we have

  1        generic
  2                type T is private;
  3                with procedure P (Z : in T);
  4        package G is

@ This can be matched by

  1        type A is access ...;
  2        procedure Q (Y : in not null A);
  3        ...
  4        package NG is new G (T => A; P => Q);

@ Note that since the formal type T is not known to be an access type in the generic declaration, there is no mechanism for applying a null exclusion to it. Nevertheless there is no reason why the instantiation should not be permitted.

@ There are some other changes to existing named formal subprogram parameters. The reader will recall from the discussion on interfaces in an earlier paper that the concept of null procedures has been added in Ada 2005. A null procedure has no body but behaves as if it has a body comprising a null statement. It is now possible to use a null procedure as a possible form of default for a subprogram parameter. Thus there are now three possible forms of default as follows

  1        with procedure P ( ... ) is <>; -- OK in 95
  2        with procedure Q ( ... ) is Some_Proc; -- OK in 95
  3        with procedure R ( ... ) is null; -- only in 2005

@ So if we have

  1        generic
  2                type T is (<>);
  3                with procedure R (X : in Integer; Y : in out T) is null;
  4                package PP ...

@ then an instantiation omitting the parameter for R such as

  1        package NPP is new PP (T => Colour);

@ is equivalent to providing an actual procedure AR thus

  1        procedure AR (X : in Integer; Y : in out Colour) is
  2        begin
  3                null;
  4        end AR;

@ Note that the profile of the actual procedure is conjured up to match the formal procedure.

@ Of course, there is no such thing as a null function and so null is not permitted as the default for a formal function.

@ A new kind of subprogram parameter was introduced in some detail when discussing object factory functions in the paper on the object oriented model. This is the abstract formal subprogram. The example given was the predefined generic function Generic_Dispatching_Constructor thus

  1        generic
  2                type T (<>) is abstract tagged limited private;
  3                type Parameters (<>) is limited private;
  4                with function Constructor (Params : not null access Parameters) return T is abstract;
  5                function Ada.Tags.Generic_Dispatching_Constructor
  6                        (The_Tag : Tag; Params : not null access Parameters) return T'Class;

@ The formal function Constructor is an example of an abstract formal subprogram. Remember that the interpretation is that the actual function must be a dispatching operation of a tagged type uniquely identified by the profile of the formal function. The actual operation can be concrete or abstract. Formal abstract subprograms can of course be procedures as well as functions. It is important that there is exactly one controlling type in the profile.

@ Formal abstract subprograms can have defaults in much the same way that formal concrete subprograms can have defaults. We write

  1        with procedure P (X : in out T) is abstract <>;
  2        with function F return T is abstract Unit;

@ The first means of course that the default has to have identifier P and the second means that the default is some function Unit. It is not possible to give null as the default for an abstract parameter for various reasons. Defaults will probably be rarely used for abstract parameters.

@ The introduction of interfaces in Ada 2005 means that a new class of generic parameters is possible.

@ Thus we might have

  1        generic
  2                type F is interface;

@ The actual type could then be any interface. This is perhaps unlikely.

@ If we wanted to ensure that a formal interface had certain operations then we might first declare an interface A with the required operations

  1        type A is interface;
  2        procedure Op1 (X : A; ... ) is abstract;
  3        procedure N1 (X : A; ... ) is null;
  4
  5

@ and then

  1        generic
  2                type F is interface and A;

@ and then the actual interface must be descended from A and so have operations which match Op1 and N1.

@ A formal interface might specify several ancestors

  1        generic
  2                type FAB is interface and A and B;

@ where A and B are themselves interfaces. And A and B or just some of them might themselves be further formal parameters as in

  1        generic
  2                type A is interface;
  3                type FAB is interface and A and B;

@ These means that FAB must have both A and B as ancestors; it could of course have other ancestors as well.

@ The syntax for formal tagged types is also changed to take into account the possibility of interfaces.

@ Thus we might have

  1        generic
  2                type NT is new T and A and B with private;

@ in which case the actual type must be descended both from the tagged type T and the interfaces A and B. The parent type T itself might be an interface or a normal tagged type. Again some or all of T, A, and B might be earlier formal parameters. Also we can explicitly state limited in which case all of the ancestor types must also be limited.

@ An example of this sort of structure occurred when discussing printable geometric objects in the paper on the object oriented model. We had

  1        generic
  2                type T is abstract tagged private;
  3                package Make_Printable is
  4                type Printable_T is abstract new T and Printable with private;
  5                ...
  6        end;

@ It might be that we have various interfaces all derived from Printable which serve different purposes (perhaps for different output devices, laser printer, card punch and so on). We would then want the generic package to take any of these interfaces thus

  1        generic
  2                type T is abstract tagged private;
  3                type Any_Printable is interface and Printable;
  4        package Make_Printable is
  5                type Printable_T is abstract new T and Any_Printable with private;
  6                ...
  7        end;

@ A formal interface can also be marked as limited in which case the actual interface must also be limited and vice versa.

@ As discussed in the previous paper, interfaces can also be synchronized, task, or protected. Thus we might have

  1        generic
  2                type T is task interface;

@ and then the actual interface must itself be a task interface. The correspondence must be exact. A formal synchronized interface can only be matched by an actual synchronized interface and so on.

@ Remember from the discussion in the previous paper that a task interface can be composed from a synchronized interface. This flexibility does not extend to matching actual and formal generic parameters.

@ Another small change concerns object parameters of limited types. In Ada 95 the following is illegal

  1        type LT is limited
  2                record
  3                        A : Integer;
  4                        B : Float;
  5                end record; -- a limited type
  6        generic
  7                X : in LT; -- illegal in Ada 95
  8                ...
  9        procedure P ...

@ It is illegal in Ada 95 because it is not possible to provide an actual parameter. This is because the parameter mechanism is one of initialization of the formal object parameter by the actual and this is treated as assignment and so is not permitted for limited types.

@ However, in Ada 2005, initialization of a limited object by an aggregate is allowed since the value is created in situ as discussed in an earlier paper. So an instantiation is possible thus

  1        procedure Q is new P (X => (A => 1, B => 2.0), ... );

@ Remember that an initial value can also be provided by a function call and so the actual parameter could also be a function call returning a limited type.

@ The final improvement to the generic parameter mechanism concerns package parameters.

@ In Ada 95 package parameters take two forms. Given a generic package Q with formal parameters F1, F2, F3, then we can have

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

@ and then the actual package corresponding to the formal P can be any instantiation of Q.

@ Alternatively

  1        generic
  2                with package R is new Q (P1, P2, P3);

@ and then the actual package corresponding to R must be an instantiation of Q with the specified actual parameters P1, P2, P3.

@ As mentioned in the Introduction, a simple example of the use of these two forms occurs with the package Generic_Complex_Arrays which takes instantiations of Generic_Real_Arrays and Generic_Complex_Types which in turn both have the underlying floating type as their single parameter. It is vital that both packages use the same floating point type and this is assured by writing

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

@ However, the mechanism does not work very well when several parameters are involved as will now be illustrated with some examples.

@ The first example concerns using the new container library which will be discussed in some detail in a later paper. There are generic packages such as

  1        generic
  2                type Index_Type is range <>;
  3                type Element_Type is private:
  4                with function "=" (Left, Right : Element_Type ) return Boolean is <>;
  5        package Ada.Containers.Vectors is ...

@ and

  1        generic
  2                type Key_Type is private;
  3                type Element_Type is private:
  4                with function Hash (Key : Key_Type) return Hash_Type;
  5                with function Equivalent_Keys (Left, Right : Key_Type) return Boolean;
  6                with function "=" (Left, Right : Element_Type ) return Boolean is <>;
  7        package Ada.Containers.Hashed_Maps is ...

@ We might wish to pass instantiations of both of these to some other package with the proviso that both were instantiated with the same Element_Type. Otherwise the parameters can be unrelated.

@ It would be natural to make the vector package the first parameter and give it the (<>) form. But we then find that in Ada 95 we have to repeat all the parameters other than Element_Type for the maps package. So we have

  1        with ... ; use Ada.Containers;
  2        generic
  3                with package V is new Vectors (<>);
  4                type Key_Type is private;
  5                with function Hash (Key : Key_Type) return Hash_Type;
  6                with function Equivalent_Keys (Left, Right : Key_Type) return Boolean;
  7                with function "=" (Left, Right : Element_Type ) return Boolean is <>;
  8                with package HM is new Hashed_Maps(
  9                        Key_Type => Key_Type,
 10                        Element_Type => V.Element_Type,
 11                        Hash => Hash,
 12                        Equivalent_Keys => Equivalent_Keys,
 13                        "=" => "=");
 14        package HMV is ...

@ This is a nuisance since when we instantiate HMV we have to provide all the parameters required by Hashed_Maps even though we must already have instantiated it elsewhere in the program. Suppose that instantiation was

  1        package My_Hashed_Map is new Hashed_Maps (My_Key, Integer, Hash_It, Equiv, "=");

@ and suppose also that we have instantiated Vectors

  1        package My_Vectors is new Vectors (Index, Integer, "=");

@ Now when we come to instantiate HMV we have to write

  1        package My_HMV is
  2                new HMV (My_Vectors, My_Key, Hash_It, Equiv, "=", My_Hashed_Maps);

@ This is very annoying. Not only do we have to repeat all the auxiliary parameters of Hashed_Maps but the situation regarding Vectors and Hashed_Maps is artificially made asymmetric. (Life would have been a bit easier if we had made Hashed_Maps the first package parameter but that just illustrates the asymmetry.) Of course we could more or less overcome the asymmetry by passing all the parameters of Vectors as well but then HMV would have even more parameters. This rather defeats the point of package parameters which were introduced into Ada 95 in order to avoid the huge parameter lists that had occurred in Ada 83.

@ Ada 2005 overcomes this problem by permitting just some of the actual parameters to be specified.

@ Any omitted parameters are indicated using the <> notation thus

  1        generic
  2                with package S is new Q (P1, F2 => <>, F3 => <>);

@ In this case the actual package corresponding to S can be any package which is an instantiation of Q where the first actual parameter is P1 but the other two parameters are left unspecified. We can also abbreviate this to

  1        generic
  2                with package S is new Q (P1, others => <>);

@ Note that the <> notation can only be used with named parameters and also that (<>) is now considered to be a shorthand for (others => <>).

@ As another example

  1        generic
  2                with package S is new Q (F1 => <>, F2 => P2, F3 => <>);

@ means that the actual package corresponding to S can be any package which is an instantiation of Q where the second actual parameter is P2 but the other two parameters are left unspecified. This can be abbreviated to

  1        generic
  2                with package S is new Q (F2 => P2, others => <>);

@ Using this new notation, the package HMV can now simply be written as

  1        with ... ; use Ada.Containers;
  2        generic
  3                with package V is new Vectors (<>);
  4                with package HM is new Hashed_Maps (Element_Type => V.Element_Type, others => <>);
  5        package HMV is ...

@ and our instantiation of HMV becomes simply

  1        package My_HMV is new HMV (My_Vectors, My_Hashed_Maps);

@ Some variations on this example are obviously possible. For example it is likely that the instantiation of Hashed_Maps must use the same definition of equality for the type Element_Type as Vectors. We can ensure this by writing

  1        with ... ; use Ada.Containers;
  2        generic
  3                with package V is new Vectors (<>);
  4                with package HM is new Hashed_Maps (Element_Type => V.Element_Type, "=" => V."=", others => <>);
  5        package HMV is ...

@ If this seems rather too hypothetical, a more concrete example might be a generic function which converts a vector into a list provided they have the same element type and equality. Note first that the specification of the container package for lists is

  1        generic
  2                type Element_Type is private;
  3                with function "=" (Left, Right : Element_Type) return Boolean is <>;
  4        package Ada.Containers.Doubly_Linked_Lists is ...

@ The specification of a generic function Convert might be

  1        generic
  2                with package DLL is new Doubly_Linked_Lists (<>);
  3                with package V is new Vectors (Index_Type => <>, Element_Type => DLL.Element_Type, "=" => DLL."=");
  4        function Convert (The_Vector : V.Vector) return DLL.List;

@ On the other hand if we only care about the element types matching and not about equality then we could write

  1        generic
  2                with package DLL is new Doubly_Linked_Lists (<>);
  3                with package V is new Vectors (Element_Type => DLL.Element_Type, others => <>);
  4        function Convert (The_Vector : V.Vector) return DLL.List;

@ Note that if we had reversed the roles of the formal packages then we would not need the new <> notation if both equality and element type had to match but it would be necessary for the case where only the element type had to match.

@ Other examples might arise in the numerics area. Suppose we have two independently written generic packages Do_This and Do_That which both have a floating point type parameter and several other parameters as well. For example

  1        generic
  2                type Real is digits <>;
  3                Accuracy : in Real;
  4                type Index is range <>;
  5                Max_Trials : in Index;
  6        package Do_This is ...
  7        generic
  8                type Floating is digits <>;
  9                Bounds : in Floating;
 10                Iterations : in Integer;
 11                Repeat : in Boolean;
 12        package Do_That is ...

@ (This is typical of much numerical stuff. Authors are cautious and unable to make firm decisions about many aspects of their algorithms and therefore pass the buck back to the user in the form of a turgid list of auxiliary parameters.) We now wish to write a package Super_Solver which takes instantiations of both Do_This and Do_That with the requirement that the floating type used for the instantiation is the same in each case but otherwise the parameters are unrelated. In Ada 95 we are again forced to repeat one set of parameters thus

  1        generic
  2                with package This is new Do_This (<>);
  3                S_Bounds : in This.Real;
  4                S_Iterations : in Integer;
  5                S_Repeat : in Boolean;
  6                with package That is new Do_That (This.Real, S_Bounds, S_Iterations, S_Repeat);
  7        package Super_Solver is ...

@ And when we come to instantiate Super_Solver we have to provide all the auxiliary parameters required by Do_That even though we must already have instantiated it elsewhere in the program.

@ Suppose the instantiation was

  1        package That_One is new Do_That (Float, 0.01, 7, False);

@ and suppose also that we have instantiated Do_This

  1        package This_One is new Do_This ( ... );

@ Now when we instantiate Super_Solver we have to write

  1        package SS is new Super_Solver (This_One, 0.01, 7, False, That_One);

@ Just as with HMV we have all these duplicated parameters and an artificial asymmetry between This and That.

@ In Ada 2005 the package Super_Solver can be written as

  1        generic
  2                with package This is new Do_This (<>);
  3                with package That is new Do_That (This.Real, others => <>);
  4        package Super_Solver is ...

@ and the instantiation of Super_Solver becomes simply

  1        package SS is new Super_Solver (This_One, That_One);

@ Other examples occur with signature packages. Remember that a signature package is one without a specification. It can be used to ensure that a group of entities are related in the correct way and an instantiation can then be used to identify the group as a whole. A trivial example might be

  1        generic
  2                type Index is (<>);
  3                type item is private;
  4                type Vec is array (Index range <>) of Item;
  5        package General_Vector is end;

@ An instantiation of General_Vector just asserts that the three types concerned have the appropriate relationship. Thus we might have

  1        type My_Array is array (Integer range <>) of Float;

@ and then

  1        package Vector is new General_Vector (Integer, Float, My_Array);

@ The package General_Vector could then be used as a parameter of other packages thereby reducing the number of parameters.

@ Another example might be the signature of a package for manipulating sets. Thus

  1        generic
  2                type Element is private;
  3                type Set is private;
  4                with function Empty return Set;
  5                with function Unit (E : Element) return Set;
  6                with function Union (S, T : Set) return Set;
  7                with function Intersection (S, T : Set) return Set;
  8                ...
  9        package Set_Signature is end;

@ We might then have some other generic package which takes an instantiation of this set signature.

@ However, it is likely that we would need to specify the type of the elements but possibly not the set type and certainly not all the operations. So typically we would have

  1        generic
  2                type My_Element is private;
  3                with package Sets is new Set_Signature (Element => My_Element, others => <>);

@ An example of this technique occurred when considering the possibility of including a system of units facility within Ada 2005. Although it was considered not appropriate to include it, the use of signature packages was almost essential to make the mechanism usable. The interested reader should consult AI-324.

@ We conclude by noting a small change to the syntax of a subprogram instantiation in that an overriding indicator can be supplied as mentioned in Section 7 of the paper on the object oriented model. Thus (in appropriate circumstances) we can write

  1        overriding procedure This is new That ( ... );

@ This means that the instantiation must be an overriding operation for some type.

Rationale for Ada 2005: Exceptions, generics etc

@ENGRUSTOPBACKNEXT

5. Настраиваемые модули

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

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

  1        generic
  2                ...
  3                type A is not null access T;
  4                ...

@ Тогда фактический тип, соответствующий A должен быть ссылочным типом, который исключает пустой указатель. Подобное правило применяется и в обратном направлении - если формальный параметр исключает пустой указатель тогда, фактический параметр должен также исключать пустой указатель. Если бы они оба не соответствовали в этом отношении тогда мог бы возникнуть целый ряд трудностей.

@ Так же, если формальный параметр получен из ссылочного типа

  1        generic
  2                ...
  3                type FA is new A; -- A is an access type
  4                ...

@ тогда фактический тип, соответствующий FA должен исключать пустой указатель, если A исключает пустой указатель и наоборот. Половина этого правила реализуется автоматически, так как тип наследуется из типа который исключает пустой указатель, автоматически исключит пустой указатель. Но обратное не верно как упомянуто в более ранней статье где обсуждались ссылочные типы. Если у A есть объявление

  1        type A is access all Integer; -- does not exclude null

@ тогда мы можем объявить

  1        type NA is new A; -- does not exclude null
  2        type NNA is new not null A; -- does exclude null

@ где NA соответствует формальному параметру FA в вышеупомянутой настройке, а NNA нет.

@ Есть также некоторое изменение к формальным производным типам относительно ограниченности (limitedness). В соответствии с изменениями, описанными в статье посвящённой объектно-ориентированной модели, синтаксис теперь разрешает limited быть заявленным явно таким образом

  1        generic
  2                type T is limited new LT; -- untagged
  3                type TT is limited new TLT with private; -- tagged

@ Однако, это может рассматриваться просто как документационная помощь т.к. фактические типы T, и TT полученые из LT и TLT и так будут ограничены, если типы LT и TLT ограничены.

@ Объекты анонимных ссылочных типов теперь также разрешены как универсальные формальные параметры, таким образом, мы можем написать

  1        generic
  2                A  : access T := null;
  3                AN : in out not null access T;
  4                F  : access function (X : Float) return Float;
  5                FN : not null access function (X : Float) return Float;

@ Если подтип формального объекта исключает пустой указатель (как в AN и в FN) тогда фактический объект должен также исключать пустой указатель, но не наоборот. Это контрастирует с правилом для формальных ссылочных типов, рассмотренных выше, когда и формальный тип и фактический тип должны оба одновременно либо исключать пустой указатель либо нет. Отметим кроме того, что у объектных параметров анонимных ссылочных типов может быть режим in out.

@ Если в спецификации самой подпрограммы есть ссылочные параметры, которые исключают пустой указатель как в

  1        generic
  2                PN : access procedure (AN : not null access T);

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

  1        generic
  2                with procedure P (AN : not null access T);
  3                with procedure Q (AN : access T);

@ тогда у фактического соответствия P должен быть параметр, который исключает пустой указатель, но фактическое соответствие Q может либо иметь либо не иметь исключение пустого указателя. Правило подобно переименованию - "not null" всегда должно выполняться.

@ Напомним, что соответствие объекта и параметра настраиваемой подпрограммы определено в терминах переименования. Рассмотрим пример иллюстрирующий важность асимметрии.

  1        generic
  2                type T is private;
  3                with procedure P (Z : in T);
  4        package G is

@ Это может быть использовано так

  1        type A is access ...;
  2        procedure Q (Y : in not null A);
  3        ...
  4        package NG is new G (T => A; P => Q);

@ Формальный тип T в универсальном объявлении, как видим, не является ссылочным типом, и нет никакого механизма для того, чтобы применить нулевое исключение к нему. Однако, нет никаких причин по которым при реализация нельзя было бы использовать нулевое исключение.

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

  1        with procedure P ( ... ) is <>; -- OK in 95
  2        with procedure Q ( ... ) is Some_Proc; -- OK in 95
  3        with procedure R ( ... ) is null; -- only in 2005

@ Таким образом, если мы имеем

  1        generic
  2                type T is (<>);
  3                with procedure R (X : in Integer; Y : in out T) is null;
  4        package PP ...

@ тогда при реализации, пропуск параметра R

  1        package NPP is new PP (T => Colour);

@ эквивалентен автоматическому заданию фактической процедуры следующего вида

  1        procedure AR (X : in Integer; Y : in out Colour) is
  2        begin
  3                null;
  4        end AR;

@ Отметим, что конфигурация фактической процедуры конфигурирована до соответствия формальной процедуре.

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

@ Новый вид параметра подпрограммы частично был введен при обсуждении функций фабрик объектов в статье посвящённой объектно-ориентированной модели. Это - абстрактная формальная подпрограмма. Был приведён пример предопределённой универсальной функции Generic_Dispatching_Constructor

  1        generic
  2                type T (<>) is abstract tagged limited private;
  3                type Parameters (<>) is limited private;
  4                with function Constructor (Params : not null access Parameters)
  5                        return T is abstract;
  6        function Ada.Tags.Generic_Dispatching_Constructor
  7                (The_Tag : Tag; Params : not null access Parameters)
  8                        return T'Class;

@ Здесь формальная функция Constructor - пример абстрактной формальной подпрограммы. Помните, что интерпретация это то, что фактическая функция должна быть операцией диспетчеризации тегового типа, однозначно определенного конфигурацией формальной функции. Фактическая операция может быть конкретной или абстрактной. Формальные абстрактные подпрограммы могут конечно быть процедурами так же как и функциями. Важно то, что имется точно один управляемый (controlled) тип в конфигурации.

@ У формальных абстрактных подпрограмм могут быть значения по умолчанию заданные аналогичным способом. Мы пишем

  1        with procedure P (X : in out T) is abstract <>;
  2        with function F return T is abstract Unit;

@ Первое конечно, что у значения по умолчанию должны быть идентификатор P и второе, что значение по умолчанию - некоторая функция Unit. Не возможно дать пустой указатель как значение по умолчанию для абстрактного параметра по различным причинам. Значения по умолчанию будут вероятно редко использоваться для абстрактных параметров.

@ Введение интерфейсов в Аде 2005 означает что возможен новый класс универсальных параметров.

@ Таким образом, мы могли бы иметь

  1        generic
  2                type F is interface;

@ Фактический тип мог бы тогда быть любым интерфейсом. Это весьма маловероятно.

@ Если бы мы хотели гарантировать чтобы у формального интерфейса были определенные операции тогда, то мы могли бы сначала объявить интерфейс с необходимыми операциями

  1        type A is interface;
  2        procedure Op1 (X : A; ... ) is abstract;
  3        procedure N1 (X : A; ... ) is null;
  4
  5

@ и тогда

  1        generic
  2                type F is interface and A;

@ и тогда фактический интерфейс должен наследоваться из A а так же иметь операции, которые соответствуют Op1 и N1.

@ Формальный интерфейс мог бы определить несколько предков

  1        generic
  2                type FAB is interface and A and B;

@ где A и B - самостоятельные интерфейсы. И A и B или только один из них могли бы самостоятельно быть дальнейшими формальными параметрами как в

  1        generic
  2                type A is interface;
  3                type FAB is interface and A and B;

@ Эти средства FAB должны быть и A и B как предки; у него могут конечно быть также и другие предки.

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

@ Таким образом, мы могли бы иметь

  1        generic
  2                type NT is new T and A and B with private;

@ когда фактический тип должен наследоваться и от тегового типа T и от интерфейсов A и B. Родительский тип T мог бы быть интерфейсом или нормальным теговым типом. Снова некоторые или даже все T, A, и B могли бы быть более ранними формальными параметрами. Также мы можем явно заявить limited (ограниченный), когда все типы предка должны также быть ограничены.

@ Пример этого вида структуры рссматривался при обсуждении печати геометрических объектов в статье посвящённой объектно-ориентированной модели. Мы имели

  1        generic
  2                type T is abstract tagged private;
  3                package Make_Printable is
  4                type Printable_T is abstract new T and Printable with private;
  5                ...
  6        end;

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

  1        generic
  2                type T is abstract tagged private;
  3                type Any_Printable is interface and Printable;
  4        package Make_Printable is
  5                type Printable_T is abstract new T and Any_Printable with private;
  6                ...
  7        end;

@ Формальный интерфейс также может быть отмечен как ограниченный, в этом случае фактический интерфейс также должен быть ограниченым и наоборот.

@ Как обсуждалось в предыдущей статье, интерфейсы могут также быть synchronized, task, или protected. Таким образом мы могли бы иметь

  1        generic
  2                type T is task interface;

@ и тогда фактический интерфейс должен самостоятельно быть интерфейсом задачи. Корреспонденция должна быть точной. Формальный синхронизированный интерфейс может быть согласован только фактическим синхронизированным интерфейсом и так далее.

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

@ Другая мелочь касается объектных параметров ограниченных типов. В Аде 95 следующее незаконно

  1        type LT is limited
  2                record
  3                        A : Integer;
  4                        B : Float;
  5                end record; -- a limited type
  6        generic
  7                X : in LT; -- illegal in Ada 95
  8                ...
  9        procedure P ...

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

@ Однако, в Аде 2005 инициализация ограниченного объекта агрегатом разрешена, так как значение создаётся на месте, как обсуждалось в более ранней статье. Таким образом, реализация возможна таким образом

  1        procedure Q is new P (X => (A => 1, B => 2.0), ... );

@ Напомним, что начальное значение также может быть назначено вызовом функции и, таким образом, фактический параметр также может быть функциональным запросом, возвращающим ограниченный тип.

@ Конечное усовершенствование на универсальный механизм передачи параметров касается параметров пакета.

@ В Аде 95 параметры пакета принимают две формы. Имея настраиваемый пакет Q с формальными параметрами F1, F2, F3 мы можем иметь

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

@ и затем фактический пакет, соответствующий формальному P, может быть любой реализацией Q.

@ Альтернативно

  1        generic
  2                with package R is new Q (P1, P2, P3);

@ и тогда фактический пакет, соответствующий R, должен быть реализацией Q с указанными фактическими параметрами P1, P2, P3.

@ Во Введении расматривался простой пример использования этих двух форм где пакет Generic_Complex_Arrays берет реализации Generic_Real_Arrays и Generic_Complex_Types, у которых, в свою очередь, есть основной тип с плавающей точкой как их единственный параметр. Жизненно важно, чтобы оба пакета использовали один и тот же тип с плавающей запятой, и это уверено при письме

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

@ Однако, когда имеется несколько параметров, механизм работает не совсем хорошо, как будет показано далее.

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

  1        generic
  2                type Index_Type is range <>;
  3                type Element_Type is private:
  4                with function "=" (Left, Right : Element_Type ) return Boolean is <>;
  5        package Ada.Containers.Vectors is ...

@ и

  1        generic
  2                type Key_Type is private;
  3                type Element_Type is private:
  4                with function Hash (Key : Key_Type) return Hash_Type;
  5                with function Equivalent_Keys (Left, Right : Key_Type) return Boolean;
  6                with function "=" (Left, Right : Element_Type ) return Boolean is <>;
  7        package Ada.Containers.Hashed_Maps is ...

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

@ Было бы естественно заставить пакет Vectors упаковать первый параметр и дать его в форме (<>). Но мы тогда находим, что в Аде 95 мы должны повторить все параметры кроме Element_Type для пакета Hashed_Maps. Таким образом, мы имеем

  1        with ... ; use Ada.Containers;
  2        generic
  3                with package V is new Vectors (<>);
  4                type Key_Type is private;
  5                with function Hash (Key : Key_Type) return Hash_Type;
  6                with function Equivalent_Keys (Left, Right : Key_Type) return Boolean;
  7                with function "=" (Left, Right : Element_Type ) return Boolean is <>;
  8                with package HM is new Hashed_Maps
  9                (       Key_Type => Key_Type,
 10                        Element_Type => V.Element_Type,
 11                        Hash => Hash,
 12                        Equivalent_Keys => Equivalent_Keys,
 13                        "=" => "=");
 14        package HMV is ...

@ Это - неприятность с тех пор, когда мы иллюстрируем HMV, мы должны обеспечить все параметры, требуемые Hashed_Maps даже при том, что мы, должно быть, уже иллюстрировали ихо в другом месте в программе. Предположим, что реализация была

  1        package My_Hashed_Map is new Hashed_Maps
  2                (My_Key, Integer, Hash_It, Equiv, "=");

@ и предположим также, что мы имеем инстанцию Vectors

  1        package My_Vectors is new Vectors (Index, Integer, "=");

@ Теперь, когда при реализации инстанции HMV, мы должны написать

  1        package My_HMV is new HMV
  2                (My_Vectors, My_Key, Hash_It, Equiv, "=", My_Hashed_Maps);

@ Это является весьма раздражающим. Мало того, что мы должны повторить все вспомогательные параметры Hashed_Maps, но ситуации относительно Vectors и Hashed_Maps искусственно сделаны асимметричными. (Жизнь была бы немного более простой, если бы мы сделали Hashed_Maps первым параметром пакета, но который только иллюстрирует асимметрию). Конечно мы могли более или менее преодолеть асимметрию, передавая все параметры Vectors также, но тогда у HMV будет даже больше параметров. Это скорее побеждает пункт параметров пакета, которые были введены в Аду 95 чтобы избежать огромных списков параметров, которые были в Аде 83.

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

@ Любые опущенные параметры обозначеначаются использованием <> нотации таким образом

  1        generic
  2                with package S is new Q (P1, F2 => <>, F3 => <>);

@ В этом случае фактический пакет, соответствующий S, может быть любым пакетом, который является реализацией Q, где первый фактический параметр - P1, но другие два параметра оставляют неопределенными. Мы можем также сократить это к

  1        generic
  2                with package S is new Q (P1, others => <>);

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

@ Другой пример

  1        generic
  2                with package S is new Q (F1 => <>, F2 => P2, F3 => <>);

@ Показывает, что фактический пакет, соответствующий S может быть любым пакетом, который является реализацией Q, где второй фактический параметр - P2, но другие два параметра оставляют неопределенными. Это можно сократить до

  1        generic
  2                with package S is new Q (F2 => P2, others => <>);

@ Используя это новую нотацию, пакет HMV может теперь просто быть описан так

  1        with ... ; use Ada.Containers;
  2        generic
  3                with package V is new Vectors (<>);
  4                with package HM is new Hashed_Maps (Element_Type => V.Element_Type, others => <>);
  5        package HMV is ...

@ и наша реализация HMV становится просто

  1        package My_HMV is new HMV (My_Vectors, My_Hashed_Maps);

@ Некоторые варианты этого примера очевидно возможны. Например вероятно, что реализация Hashed_Maps должна использовать то же самое определение равенства для типа Element_Type что и Vectors. Мы можем гарантировать это следующим образом

  1        with ... ; use Ada.Containers;
  2        generic
  3                with package V is new Vectors (<>);
  4                with package HM is new Hashed_Maps (Element_Type => V.Element_Type, "=" => V."=", others => <>);
  5        package HMV is ...

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

  1        generic
  2                type Element_Type is private;
  3                with function "=" (Left, Right : Element_Type) return Boolean is <>;
  4        package Ada.Containers.Doubly_Linked_Lists is ...

@ Спецификация настраиваемой функции Convert могла бы быть

  1        generic
  2                with package DLL is new Doubly_Linked_Lists (<>);
  3                with package V is new Vectors (Index_Type => <>, Element_Type => DLL.Element_Type, "=" => DLL."=");
  4        function Convert (The_Vector : V.Vector) return DLL.List;

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

  1        generic
  2                with package DLL is new Doubly_Linked_Lists (<>);
  3                with package V is new Vectors (Element_Type => DLL.Element_Type, others => <>);
  4        function Convert (The_Vector : V.Vector) return DLL.List;

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

@ Другие примеры могли бы возникнуть в области численных данных. Предположим, что у нас есть два независимо написанных настраиваемых пакета Do_This и Do_That, которые оба имеют параметры типа с плавающей запятой и несколько других параметров также. Например

  1        generic
  2                type Real is digits <>;
  3                Accuracy : in Real;
  4                type Index is range <>;
  5                Max_Trials : in Index;
  6        package Do_This is ...
  7
  8        generic
  9                type Floating is digits <>;
 10                Bounds : in Floating;
 11                Iterations : in Integer;
 12                Repeat : in Boolean;
 13        package Do_That is ...

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

  1        generic
  2                with package This is new Do_This (<>);
  3                S_Bounds : in This.Real;
  4                S_Iterations : in Integer;
  5                S_Repeat : in Boolean;
  6                with package That is new Do_That (This.Real, S_Bounds, S_Iterations, S_Repeat);
  7        package Super_Solver is ...

@ И когда мы берёмся реализовывать Super_Solver мы должны обеспечить все вспомогательные параметры требуемые для Do_That даже при том, что мы, должно быть, уже реализовали её в другом месте программы.

@ Предположим, что реализация была

  1        package That_One is new Do_That (Float, 0.01, 7, False);

@ и предположим также, что мы реализовали Do_This так:

  1        package This_One is new Do_This ( ... );

@ Теперь при реализации Super_Solver мы должны написать:

  1        package SS is new Super_Solver (This_One, 0.01, 7, False, That_One);

@ Так же как с HMV у нас есть все эти дублированные параметры и искусственная асимметрия между Тем и Этим.

@ На Аде 2005 пакет Super_Solver может быть написан как:

  1        generic
  2                with package This is new Do_This (<>);
  3                with package That is new Do_That (This.Real, others => <>);
  4        package Super_Solver is ...

@ и реализация Super_Solver становится просто

  1        package SS is new Super_Solver (This_One, That_One);

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

  1        generic
  2                type Index is (<>);
  3                type item is private;
  4                type Vec is array (Index range <>) of Item;
  5        package General_Vector is end;

@ Реализация General_Vector только утверждает, что у трех указанных типов есть соответствующее отношение. Таким образом мы могли бы написать

  1        type My_Array is array (Integer range <>) of Float;

@ и тогда

  1        package Vector is new General_Vector (Integer, Float, My_Array);

@ Пакет General_Vector мог тогда использоваться как параметр других пакетов, таким образом сокращая количество параметров.

@ Другим примером может быть сигнатурный пакет для управления наборами. Рассмотрим:

  1        generic
  2                type Element is private;
  3                type Set is private;
  4                with function Empty return Set;
  5                with function Unit (E : Element) return Set;
  6                with function Union (S, T : Set) return Set;
  7                with function Intersection (S, T : Set) return Set;
  8                ...
  9        package Set_Signature is end;

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

@ Однако, вероятно, что мы должны были бы определить типы элементов, но возможно не тип набора и конечно не всех операций. Так обычно мы имели бы:

  1        generic
  2                type My_Element is private;
  3                with package Sets is new Set_Signature (Element => My_Element, others => <>);

@ Пример этой методики приводился при рассмотрении возможности включения системы модульных средств в пределах Ады 2005. Хотя считали не соответствующим включать это, использование пакетов сигнатуры было почти существенным, чтобы сделать механизм пригодным для использования. Заинтересованный читатель должен консультироваться с AI-324.

@ В заключении отметим небольшое изменение в синтаксисе реализации подпрограмм состоящее в этом, что индикатор замены (overriding) может использоваться как указано в Секции 7 статьи посвящённой объектно-ориентированной модели. Таким образом (при соответствующих обстоятельствах) мы можем написать

  1        overriding procedure This is new That ( ... );

@ Это означает, что реализация должна быть заменяемой операцией для некоторого типа.

@ ENG RUS

TOP BACK NEXT

2010-10-24 00:26:56

. .