Rationale for Ada 2005: Structure and visibility

RUSTOP
BACKNEXT

ENG

5. Limited types and return statements

@ The general idea of a limited type is to restrict the operations that a user can do on the type to just those provided by the author of the type and in particular to prevent the user from doing assignment and thus making copies of objects of the type.

@ However, limited types have always been a problem. In Ada 83 the concept of limitedness was confused with that of private types. Thus in Ada 83 we only had limited private types (although task types were inherently limited).

@ Ada 95 brought significant improvement by two changes. It allowed limitedness to be separated from privateness. It also allowed the redefinition of equality for all types whereas Ada 83 forbade this for limited types. In Ada 95, the key property of a limited type is that assignment is not predefined and cannot be defined (equality is not predefined either but it can be defined). The general idea of course is that there are some types for which it would be wrong for the user to be able to make copies of objects. This particularly applies to types involved in resource control and types implemented using access types.

@ However, although Ada 95 greatly improved the situation regarding limited types, nevertheless two major difficulties have remained. One concerns the initialization of objects and the other concerns the results of functions.

@ The first problem is that Ada 95 treats initialization as a process of assigning the initial value to the object concerned (hence the use of := unlike some Algol based languages which use = for initialization and := for assignment). And since initialization is treated as assignment it is forbidden for limited types. This means that we cannot initialize objects of a limited type nor can we declare constants of a limited type. We cannot declare constants because they have to be initialized and yet initialization is forbidden. This is more annoying in Ada 95 since we can make a type limited but not private.

@ The following example was discussed in the Introduction

  1        type T is limited
  2                record
  3                        A : Integer;
  4                        B : Boolean;
  5                        C : Float;
  6                end record;

@ Note that this type is explicitly limited (but not private) but its components are not limited. If we declare an object of type T in Ada 95 then we have to initialize the components (by assigning to them) individually thus

  1                X : T;
  2        begin
  3                X.A := 10; X.B := True; X.C := 45.7;

@ Not only is this annoying but it is prone to errors as well. If we add a further component D to the type T then we might forget to initialize it. One of the advantages of aggregates is that we have to supply all the components which automatically provides full coverage analysis.

@ This problem did not arise in Ada 83 because we could not make a type limited without making it also private and so the individual components were not visible anyway.

@ Ada 2005 overcomes the difficulty by stating that initialization by an aggregate is not actually assignment even though depicted by the same symbol. This permits

  1        X : T := (A => 10, B => True, C => 45.7);

@ We should think of the individual components as being initialized individually in situ – an actual aggregated value is not created and then assigned.

@ The reader might recall that the same thing happens when an aggregate is used to initialize a controlled type; this was not as Ada 95 was originally defined but it was corrected in AI-83 and consolidated in the 2001 Corrigendum [2].

@ We can now declare a constant of a limited type as expected

  1        X : constant T := (A => 10, B => True, C => 45.7);

@ Limited aggregates can be used in a number of other contexts as well ? as the default expression in a component declaration, so if we nest the type T inside some other type (which itself then is always limited – it could be explicitly limited but there is a general rule that a type is implicitly limited if it has a limited component) we might have

  1        type Twrapper is
  2                record
  3                        Tcomp : T := (0, False, 0.0);
  4                end record;

@ ? as an expression in a record aggregate, so again using the type Twrapper as in

  1        XT : Twrapper := (Tcomp => (1, True, 1.0));

@ ? as an expression in an array aggregate similarly, so we might have

  1        type Tarr is array (1 .. 5) of T;
  2        Xarr : Tarr := (1 .. 5 => (2, True, 2.0));

@ ? as the expression for the ancestor part of an extension aggregate, so if TT were tagged as in

  1        type TT is tagged limited
  2                record
  3                        A : Integer;
  4                        B : Boolean;
  5                        C : Float;
  6                end record;
  7        type TTplus is new TT with
  8                record
  9                        D : Integer;
 10                end record;
 11        ...
 12        XTT : TTplus := ((1, True, 1.0) with 2);

@ ? as the expression in an initialized allocator, so we might have

  1        type T_Ptr is access T;
  2        XT_Ptr : T_Ptr;
  3        ...
  4        XT_Ptr := new T'(3, False, 3.0);

@ ? as the actual parameter for a subprogram parameter of a limited type of mode in

  1        procedure P (X : in T);
  2        ...
  3        P ((4, True, 4.0));

@ ? similarly as the default expression for a parameter

  1        procedure P (X : in T := (4, True, 4.0));

@ ? as the result in a return statement

  1        function F( ... ) return T is
  2        begin
  3                ...
  4                return (5, False, 5.0);
  5        end F;

@ this really concerns the other major change to limited types which we shall return to in a moment. ? as the actual parameter for a generic formal limited object parameter of mode in,

  1        generic
  2                FT : in T;
  3        package P is ...
  4        ...
  5        package Q is new P (FT => (7, True, 7.0));

@ The last example is interesting. Limited generic parameters were not allowed in Ada 95 at all because there was no way of passing an actual parameter because the generic parameter mechanism for an in parameter is considered to be assignment. But now the actual parameter can be passed as an aggregate. An aggregate can also be used as a default value for the parameter thus

  1        generic
  2                FT : in T := (0, False, 0.0);
  3        package P is ...

@ Remember that there is a difference between subprogram and generic parameters. Subprogram parameters were always allowed to be of limited types since they are mostly implemented by reference and no copying happens anyway. The only exception to this is with limited private types where the full type is an elementary type.

@ The change in Ada 2005 is that an aggregate can be used as the actual parameter in the case of a subprogram parameter of mode in whereas that was not possible in Ada 95.

@ Sometimes a limited type has components where an initial value cannot be given as in protected type Semaphore is ... ;

  1        type PT is
  2                record
  3                        Guard    : Semaphore;
  4                        Count    : Integer;
  5                        Finished : Boolean := False;
  6                end record;

@ Since a protected type is inherently limited the type PT is also limited because a type with a limited component is itself limited. Although we cannot give an explicit initial value for a Semaphore, we would still like to use an aggregate to get the coverage check. In such cases we can use the box symbol <> as described in the previous section to mean use the default value for the type (if any). So we can write

  1        X : PT := (Guard => <>, Count => 0, Finished => <>);

@ The major rule that must always be obeyed is that values of limited types can never be copied.

@ Consider nested limited types

  1        type Inner is limited
  2                record
  3                        L : Integer;
  4                        M : Float;
  5                end record;
  6        type Outer is limited
  7                record
  8                        X : Inner;
  9                        Y : Integer;
 10                end record;

@ If we declare an object of type Inner

  1        An_Inner : Inner := (L => 2, M => 2.0);

@ then we could not use An_Inner in an aggregate of type Outer

  1        An_Outer: Outer := (X => An_Inner, Y => 3); -- illegal

@ This is illegal because we would be copying the value. But we can use a nested aggregate as mentioned earlier

  1        An_Outer : Outer := (X => (2, 2.0), Y => 3);

@ The other major change to limited types concerns returning values from functions.

@ We have seen that the ability to initialize an object of a limited type with an aggregate solves the problem of giving an initial value to a limited type provided that the type is not private.

@ Ada 2005 introduces a new approach to returning the results from functions which can be used to solve this and other problems.

@ We will first consider the case of a type that is limited such as

  1        type T is limited
  2                record
  3                        A : Integer;
  4                        B : Boolean;
  5                        C : Float;
  6                end record;

@ We can declare a function that returns a value of type T provided that the return does not involve any copying. For example we could have

  1        function Init (X : Integer; Y : Boolean; Z : Float) return T is
  2        begin
  3                return (X, Y, Z);
  4        end Init;

@ This function builds the aggregate in place in the return expression and delivers it to the location specified where the function is called. Such a function can be called from precisely those places listed above where an aggregate can be used to build a limited value in place. For example

  1        V : T := Init(2, True, 3.0);

@ So the function itself builds the value in the variable V when constructing the returned value. Hence the address of V is passed to the function as a sort of hidden parameter.

@ Of course if T is not private then this achieves no more than simply writing

  1        V : T := (2, True, 3.0);

@ But the function Init can be used even if the type is private. It is in effect a constructor function for the type. Moreover, the function Init could be used to do some general calculation with the parameters before delivering the final value and this brings considerable flexibility.

@ We noted that such a function can be called in all the places where an aggregate can be used and this includes in a return expression of a similar function or even itself

  1        function Init_True (X : Integer; Z : Float) return T is
  2        begin
  3                return Init(X, True, Z);
  4        end Init_True;

@ It could also be used within an aggregate. Suppose we have a function to return a value of the limited type Inner thus

  1        function Make_Inner (X : Integer; Y : Float) return Inner is
  2        begin
  3                return (X, Y);
  4        end Make_Inner;

@ then not only could we use it to initialize an object of type Inner but we could use it in a declaration of an object of type Outer thus

  1        An_Inner : Inner := Make_Inner (2, 2.0);
  2        An_Outer : Outer := (X => Make_Inner (2, 2.0), Y => 3);

@ In the latter case the address of the component of An_Outer is passed as the hidden parameter to the function Make_Inner.

@ Being able to use a function in this way provides much flexibility but sometimes even more flexibility is required. New syntax permits the final returned object to be declared and then manipulated in a general way before finally returning from the function.

@ The basic structure is

  1        function Make ( ... ) return T is
  2        begin
  3                ...
  4                return R: T do  -- declare R to be returned
  5                        ... -- here we can manipulate R in the usual way
  6                        ... -- in a sequence of statements
  7                end return;
  8        end Make;

@ The general idea is that the object R is declared and can then be manipulated in an arbitrary way before being finally returned. Note the use of the reserved word do to introduce the statements in much the same way as in an accept statement. The sequence ends with end return and at this point the function passes control back to where it was called. Note that if the function had been called in a construction such as the initialization of an object X of a limited type T thus

  1        X : T := Make ( ... );

@ then the variable R inside the function is actually the variable X being initialized. In other words the address of X is passed as a hidden parameter to the function Make in order to create the space for R.

@ No copying is therefore ever performed.

@ The sequence of statements could have an exception handler

  1        return R: T do
  2                ... -- statements
  3        exception
  4                ... -- handlers
  5        end return;

@ If we need local variables within an extended return statement then we can declare an inner block in the usual way

  1        return R : T do
  2                declare
  3                        ... -- local declarations
  4                begin
  5                        ... -- statements
  6                end;
  7        end return;

@ The declaration of R could have an initial value

  1        return R : T := Init ( ... ) do
  2                ...
  3        end return;

@ Also, much as in an accept statement, the do ... end return part can be omitted, so we simply get

  1        return R : T;

@ or

  1        return R : T := Init ( ... );

@ which is handy if we just want to return the object with its default or explicit initial value.

@ Observe that extended return statements cannot be nested but could have simple return statements inside

  1        return R : T := Init ( ... ) do
  2                if ... then
  3                        ...
  4                        return;  -- result is R
  5                end if;
  6                ...
  7        end return;

@ Note that simple return statements inside an extended return statement do not have an expression since the result returned is the object R declared in the extended return statement itself.

@ Although extended return statements cannot be nested there could nevertheless be several in a function, perhaps in branches of an if statement or case statement. This would be quite likely in the case of a type with discriminants

  1        type Person (Sex : Gender) is ... ;
  2        function F ( ... ) return Person is
  3        begin
  4                if ... then
  5                        return R : Person (Sex => Male) do
  6                                ...
  7                        end return;
  8                else
  9                        return R : Person (Sex => Female) do
 10                                ...
 11                        end return;
 12                end if;
 13        end F;

@ This also illustrates the important point that although we introduced these extended return statements in the context of greater flexibility for limited types they can be used with any types at all such as the nonlimited type Person. The mechanism of passing a hidden parameter which is the address for the returned object of course only applies to limited types. In the case of nonlimited types, the result is simply delivered in the usual way.

@ We can also rename the result of a function call – even if it is limited.

@ The result type of a function can be constrained or unconstrained as in the case of the type Person but the actual object delivered must be of a definite subtype. For example suppose we have

  1        type UA is array (Integer range <>) of Float;
  2        subtype CA is UA (1 .. 10);

@ Then the type UA is unconstrained but the subtype CA is constrained. We can use both with extended return statements.

@ In the constrained case the subtype in the extended return statement has to statically match (typically it will be the same textually but need not) thus

  1        function Make ( ... ) return CA is
  2        begin
  3                ...
  4                return R : UA (1 .. 10) do  -- statically matches
  5                        ...
  6                end return;
  7        end Make;

@ In the unconstrained case the result R has to be constrained either by its subtype or by its initial value. Thus

  1        function Make ( ... ) return UA is
  2        begin
  3                ...
  4                return R : UA (1 .. N) do
  5                        ...
  6                end return;
  7        end Make;

@ or

  1        function Make ( ... ) return UA is
  2        begin
  3                ...
  4                return R : UA := (1 .. N => 0.0) do
  5                        ...
  6                end return;
  7        end Make;

@ The other important change to the result of functions which was discussed in the previous paper is that the result type can be of an anonymous access type. So we can write a function such as function Mate_Of(A: access Animal'Class) return access Animal'Class; The introduction of explicit access types for the result means that Ada 2005 is able to dispense with the notion of returning by reference.

@ This does, however, introduce a noticeable incompatibility between Ada 95 and Ada 2005. We might for example have a pool of slave tasks acting as servers. Individual slave tasks might be busy or idle. We might have a manager task which allocates slave tasks to different jobs. The manager might declare the tasks as an array

  1        Slaves : array (1 .. 10) of TT; -- TT is some task type

@ and then have another array of properties of the tasks such as

  1        type Task_Data is
  2                record
  3                        Active   : Boolean := False;
  4                        Job_Code : ... ;
  5                end record;
  6        Slave_Data : array (1 .. 10) of Task_Data;

@ We now need a function to find an available slave. In Ada 95 we write

  1        function Get_Slave return TT is
  2        begin
  3                ... -- find index K of first idle slave
  4                return Slaves (K); -- in Ada 95, not in Ada 2005
  5        end Get_Slave;

@ This is not permitted in Ada 2005. If the result type is limited (as in this case) then the expression in the return statement has to be an aggregate or function call and not an object such as Slaves(K).

@ In Ada 2005 the function has to be rewritten to honestly return an access value referring to the task type rather than invoking the mysterious concept of returning by reference.

@ So we have to write

  1        function Get_Slave return access TT is
  2        begin
  3                ... -- find index K of first idle slave
  4                return Slaves (K)'Access; -- in Ada 2005
  5        end Get_Slave;

@ and all the calls of Get_Slave have to be changed to correspond as well.

@ This is perhaps the most serious incompatibility between Ada 95 and Ada 2005. But then, at the end of the day, honesty is the best policy.

Rationale for Ada 2005: Structure and visibility

@ENGRUSTOPBACKNEXT

5. Ограниченные типы и операторы return

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

@ Однако, ограниченные типы всегда были проблемой. В Аде 83 понятие limitedness было тесно связано с private типом. Таким образом в Аде 83 мы могли ограничивать только private типы (хотя типы задачи были неотъемлемо ограничены).

@ В Аде 95 было сделано два существенных изменения. А именно, было позволено limitedness быть отделенным от private. А также было разрешено переопределение равенства для всех типов, тогда как в Аде 83 запрещалось это для ограниченных типов. В Аде 95, ключевое свойство ограниченного типа состоит в том, что присваивание не предопределено и не может быть определено (равенство не предопределено изначально, но это может быть определено). Основная идея состоит в том, что есть некоторые типы, для которых пользователю запрещено делать копии объектов. Это особенно относится к типам, вовлеченным в контроль за ресурсом, и типам использующим ссылочные типы.

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

@ Первая проблема состоит в том, что Ада 95 обрабатывает инициализацию как процесс назначения начального значения некоторому объекту (используя знак := в отличие от Алгол - подобных языков, где принято использовать = для инициализации а := для присваивания). Т.о. инициализация обрабатывается как присваивание, которое запрещено для ограниченных типов. Это означает, что мы не можем инициализировать объекты ограниченного типа, и при этом мы не можем объявить константы ограниченного типа. Мы не можем объявить константы, потому что они должны быть инициализированы, и в то же время инициализация запрещена. Это является весьма раздражающим в Аде 95, так как мы можем сделать тип ограниченным, но не приватным.

@ Следующий пример обсуждался во Введении:

  1        type T is limited
  2                record
  3                        A : Integer;
  4                        B : Boolean;
  5                        C : Float;
  6                end record;

@ Отметим, что этот тип явно ограниченный (но не частный), но его компоненты не ограничены. Если мы объявляем объект типа T на Аде 95 тогда, мы должны инициализировать компоненты (назначая их) индивидуально таким образом:

  1                X : T;
  2        begin
  3                X.A := 10; X.B := True; X.C := 45.7;

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

@ Эта проблема не возникала в Аде 83, потому что мы не могли сделать тип ограниченным, не делая его также частным и таким образом индивидуальные компоненты не были видимы так или иначе.

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

  1        X : T := (A => 10, B => True, C => 45.7);

@ Мы должны думать об отдельных компонентах, как инициализируемых индивидуально на месте - фактическое значение не создаётся перед назначением.

@ Читатель мог бы напомнить, что та же самая вещь случается, когда агрегат используется, чтобы инициализировать controlled тип; этого не было в первоначальном определении ada, но было в исправлении AI-83 в 2001 Опечатке [2].

@ Мы теперь можем объявить константу ограниченного типа как ожидается:

  1        X : constant T := (A => 10, B => True, C => 45.7);

@ Ограниченные агрегаты могут использоваться во многих других контекстах также? как заданное по умолчанию выражение в составляющем объявлении, так, если мы вкладываем тип T в некоторый другой тип (который тогда всегда ограничивается - это могло быть явно ограничено, но есть общее правило, что тип неявно ограничен, если у этого есть хотя бы один ограниченный компонент), мы могли бы иметь:

  1        type Twrapper is
  2                record
  3                        Tcomp : T := (0, False, 0.0);
  4                end record;

@ как выражение в рекордном агрегате, так новое использование типа Twrapper в:

  1        XT : Twrapper := (Tcomp => (1, True, 1.0));

@ как выражение в составном регулярном значении точно так же, таким образом мы могли бы иметь:

  1        type Tarr is array (1 .. 5) of T;
  2        Xarr : Tarr := (1 .. 5 => (2, True, 2.0));

@ как выражение для части предка агрегата расширения, так, если TT был отмечен как в:

  1        type TT is tagged limited
  2                record
  3                        A : Integer;
  4                        B : Boolean;
  5                        C : Float;
  6                end record;
  7        type TTplus is new TT with
  8                record
  9                        D : Integer;
 10                end record;
 11        ...
 12        XTT : TTplus := ((1, True, 1.0) with 2);

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

  1        type T_Ptr is access T;
  2        XT_Ptr : T_Ptr;
  3        ...
  4        XT_Ptr := new T'(3, False, 3.0);

@ как фактический параметр для параметра подпрограммы ограниченного типа режима в:

  1        procedure P (X : in T);
  2        ...
  3        P ((4, True, 4.0));

@ так же как заданное по умолчанию выражение для параметра:

  1        procedure P (X : in T := (4, True, 4.0));

@ так же как результат оператора return:

  1        function F( ... ) return T is
  2        begin
  3                ...
  4                return (5, False, 5.0);
  5        end F;

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

  1        generic
  2                FT : in T;
  3        package P is ...
  4        ...
  5        package Q is new P (FT => (7, True, 7.0));

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

  1        generic
  2                FT : in T := (0, False, 0.0);
  3        package P is ...

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

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

@ Иногда у ограниченного типа есть компоненты, где начальное значение не может быть дано, как в защищенном типе Semaphore:

  1        type PT is
  2                record
  3                        Guard    : Semaphore;
  4                        Count    : Integer;
  5                        Finished : Boolean := False;
  6                end record;

@ Так жек как защищенный тип неотъемлемо ограничен, тип PT также ограничен, потому что тип с ограниченным компонентом становится тоже ограниченным. Хотя мы не можем дать явное начальное значение для Semaphore, мы все еще хотели бы использовать агрегат, чтобы получить проверку охвата. В таких случаях мы можем использовать символ блока <> как описано в предыдущей секции, чтобы означать использование значение по умолчанию для типа (если любой). Таким образом мы можем написать:

  1        X : PT := (Guard => <>, Count => 0, Finished => <>);

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

@ Рассмотрим вложенные ограниченные типы:

  1        type Inner is limited
  2                record
  3                        L : Integer;
  4                        M : Float;
  5                end record;
  6        type Outer is limited
  7                record
  8                        X : Inner;
  9                        Y : Integer;
 10                end record;

@ Если мы объявим объект типа Inner:

  1        An_Inner : Inner := (L => 2, M => 2.0);

@ тогда мы не cможем использовать An_Inner в агрегате типа Outer:

  1        An_Outer: Outer := (X => An_Inner, Y => 3); -- illegal

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

  1        An_Outer : Outer := (X => (2, 2.0), Y => 3);

@ Другое главное изменение к проблемам ограниченных типов - возврат значения из функций.

@ Мы видели, что способность инициализировать объект ограниченного типа с агрегатом решает проблему предоставления начального значения к ограниченному типу при условии, что тип не является частным.

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

@ Рассмотрим сначала следующий случай ограниченного типа:

  1        type T is limited
  2                record
  3                        A : Integer;
  4                        B : Boolean;
  5                        C : Float;
  6                end record;

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

  1        function Init (X : Integer; Y : Boolean; Z : Float) return T is
  2        begin
  3                return (X, Y, Z);
  4        end Init;

@ Эта функция встраивает агрегат на месте в return выражении и поставляет его в точку вызова функции. Такую функцию можно вызывать в тех же вышеупомянутых местах, где агрегат может использоваться, чтобы встроить ограниченное значение на месте. Например:

  1        V : T := Init(2, True, 3.0);

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

@ Конечно, если T не является частным тогда, это достигает не больше, чем просто следующее:

  1        V : T := (2, True, 3.0);

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

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

  1        function Init_True (X : Integer; Z : Float) return T is
  2        begin
  3                return Init(X, True, Z);
  4        end Init_True;

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

  1        function Make_Inner (X : Integer; Y : Float) return Inner is
  2        begin
  3                return (X, Y);
  4        end Make_Inner;

@ тогда мы не только можем использовать это, чтобы инициализировать объект типа Inner, но мы можем использовать это в объявлении объекта типа Inner таким образом:

  1        An_Inner : Inner := Make_Inner (2, 2.0);
  2        An_Outer : Outer := (X => Make_Inner (2, 2.0), Y => 3);

@ В последнем случае адрес компонента An_Outer передаётся как скрытый параметр для функции Make_Inner.

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

@ Основная конструкция следующая:

  1        function Make ( ... ) return T is
  2        begin
  3                ...
  4                return R: T do  -- declare R to be returned
  5                        ... -- here we can manipulate R in the usual way
  6                        ... -- in a sequence of statements
  7                end return;
  8        end Make;

@ Основная идея состоит в том, что объект R объявлен и может тогда управляться произвольным способом прежде, чем будет наконец возвращен. Отметим, что использование зарезервированного слова do чтобы ввести операторы аналогичным способом как в операторе принятия. Последовательность завершается конструкцией end return и в этом месте функция возвращент управление туда откуда она была вызвана. Отметим что если функцию назвали в конструкции такой как инициализация объекта X ограниченного типа T следующим образом:

  1        X : T := Make ( ... );

@ тогда переменная R в функции является фактически инициализируемой переменной X. Другими словами, адрес X передаётся как скрытый параметр функции Make, чтобы создать пространство для R.

@ Т.е. никакое копирование не выполняется.

@ У последовательности операторов может также быть обработчик исключения:

  1        return R: T do
  2                ... -- statements
  3        exception
  4                ... -- handlers
  5        end return;

@ Если мы будем нуждаться в локаных переменных в пределах расширенного return оператора тогда, мы можем объявить внутренний блок обычным способом:

  1        return R : T do
  2                declare
  3                        ... -- local declarations
  4                begin
  5                        ... -- statements
  6                end;
  7        end return;

@ В объявлении R может быть назначено начальное значение:

  1        return R : T := Init ( ... ) do
  2                ...
  3        end return;

@ Кроме того, очень как в операторе принятия do ... end return часть может быть опущена, таким образом мы просто можем написать:

  1        return R : T;

@ или

  1        return R : T := Init ( ... );

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

@ Заметьте, что расширенные return операторы не могут быть вложены, но могen иметь простые return операторы внутри:

  1        return R : T := Init ( ... ) do
  2                if ... then
  3                        ...
  4                        return;  -- result is R
  5                end if;
  6                ...
  7        end return;

@ Заметим, что у простых операторов возвращения в расширенном операторе возвращения нет выражения, т.к. результатом возвращения является объект R, объявленный в расширенном операторе возвращения непосредственно.

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

  1        type Person (Sex : Gender) is ... ;
  2        function F ( ... ) return Person is
  3        begin
  4                if ... then
  5                        return R : Person (Sex => Male) do
  6                                ...
  7                        end return;
  8                else
  9                        return R : Person (Sex => Female) do
 10                                ...
 11                        end return;
 12                end if;
 13        end F;

@ Это также иллюстрирует важный момент, что хотя мы и вводили эти расширенные операторы возвращения в контексте большей гибкости для ограниченных типов, они могут использоваться с любыми другими типами во всем таком как неограниченный тип Person. Механизм прохождения скрытого параметра, который является адресом для возвращенного объекта конечно только, относится к ограниченным типам. В случае неограниченных типов результат просто поставляют обычным способом.

@ Мы можем также переименовать результат вызова функции - даже если он ограничен.

@ Тип результата функции может быть constrained или unconstrained как в случае типа Person, но фактический поставленный объект должен иметь определенный подтип. Например предположим, что мы имеем:

  1        type UA is array (Integer range <>) of Float;
  2        subtype CA is UA (1 .. 10);

@ Тогда тип, UA является unconstrained, но подтип CA - constrained. Мы можем использовать их оба с расширенными операторами возвращения.

@ В случае constrained подтип в расширенном операторе возвращения должен статически соответствовать (типично, это будет то же самое дословно, но нуждаться не), таким образом:

  1        function Make ( ... ) return CA is
  2        begin
  3                ...
  4                return R : UA (1 .. 10) do  -- statically matches
  5                        ...
  6                end return;
  7        end Make;

@ В unconstrained случае результат R должен быть constrained или его подтипом или его начальным значением. Таким образом:

  1        function Make ( ... ) return UA is
  2        begin
  3                ...
  4                return R : UA (1 .. N) do
  5                        ...
  6                end return;
  7        end Make;

@ or

  1        function Make ( ... ) return UA is
  2        begin
  3                ...
  4                return R : UA := (1 .. N => 0.0) do
  5                        ...
  6                end return;
  7        end Make;

@ Другое важное изменение к результату функций, который обсуждался в предыдущей статье состоит в том, что тип результата может иметь анонимный ссылочный тип. Таким образом мы можем написать функцию, такую как функция Mate_Of (A: access Animal'Class) return access Animal'Class; Введение явных ссылочных типов для результата означает, что Ада 2005 в состоянии обойтись без понятия возвращения ссылкой.

@ Это тем не менее, вводит значимую несовместимость между Адой 95 и Адой 2005. У нас мог бы например быть пул подчиненных задач, действующих как серверы. Индивидуальные подчиненные задачи могли бы быть заняты или свободны (idle). У нас могла бы быть задача менеджера, которая распределяет подчиненные задачи различным заданиям. Менеджер мог бы объявить задачи как массив:

  1        Slaves : array (1 .. 10) of TT; -- TT is some task type

@ и затем имеется другой массив свойств задач такой как:

  1        type Task_Data is
  2                record
  3                        Active   : Boolean := False;
  4                        Job_Code : ... ;
  5                end record;
  6        Slave_Data : array (1 .. 10) of Task_Data;

@ Пусть нам необходима функция для поиска свободной задачи. На Аде 95 мы бы написали:

  1        function Get_Slave return TT is
  2        begin
  3                ... -- find index K of first idle slave
  4                return Slaves (K); -- in Ada 95, not in Ada 2005
  5        end Get_Slave;

@ Это не допустимо на Аде 2005. Если тип результата ограничен (как в этом случае) тогда, выражение в операторе возвращения должно быть составным или функциональным запросом и не объектом, таким как Slaves (K).

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

@ Таким образом мы должны будем написать:

  1        function Get_Slave return access TT is
  2        begin
  3                ... -- find index K of first idle slave
  4                return Slaves (K)'Access; -- in Ada 2005
  5        end Get_Slave;

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

@ Это - возможно самая серьезная несовместимость между Адой 95 и Адой 2005. Но тогда, в конце дня, честность - лучшая политика.

@ ENG RUS

TOP BACK NEXT

2010-10-24 00:26:54

. .