Rationale for Ada 2005: Access types

RUSTOP
BACKNEXT

ENG

4. Downward closures

@ This section is really about access to subprogram types in general but the title downward closures has come to epitomize the topic.

@ The requirements for Ada 83, (Strawman .. Steelman) were strangely silent about whether parameters of subprograms could themselves be subprograms as was the case in Algol 60 and Pascal. Remember that Pascal was one of the languages on which the designs for the DoD language were to be based.

@ The predictability aspects of the requirements were interpreted as implying that all subprogram calls should be identified at compilation time on the grounds that if you didn't know what was being called than you couldn't know what the program was going to do. This was a particularly stupid attitude to take. The question of predictability (presumably in some safety or security context) really concerns the behaviour of particular programs rather than the universe of all programs that can be constructed in a language.

@ In any event the totality of subprograms that might be called in a program is finite and closed. It simply consists of the subprograms in the program. Languages such as Ada are not able to construct totally new subprograms out of lesser components in the way that they can create say floating point values.

@ So the world had to use generics for many applications that were natural for subprograms as parameters of other subprograms. Thankfully many implementers avoided the explosion that might occur with generics by clever code sharing which in a sense hid the parameterization behind the scenes.

@ The types of applications for which subprograms are natural as parameters are any where one subroutine is parameterized by another. They include many mathematical applications such as integration and maximization and more logical applications such as sorting and searching and iterating.

@ As outlined in the Introduction, the matter was partly improved in Ada 95 by the introduction of named access-to-subprogram types. This was essentially done to allow program call back to be implemented.

@ Program call back is when one program passes the "address" of a subprogram within it to another program so that this other program can later respond by calling back to the first program using the subprogram address supplied. This is often used for communication between an Ada application program and some other software such as an operating system which might even be written in another language such as C.

@ Named access to subprogram types certainly work for call back (especially with languages such as C that do not have nested subprograms) but the accessibility rules which followed those for general access to object types were restrictive. For example, suppose we have a general library level function for integration using a named access to subprogram type to pass the function to be integrated thus

  1        type Integrand is access function (X : Float) return Float;
  2        function Integrate (Fn : Integrand; Lo, Hi : Float) return Float;

@ then we cannot even do the simplest integration of our own function in a natural way. For example, suppose we wish to integrate a function such as Exp(X**2). We can try with Integrate;

  1        procedure Main is
  2                function F (X : Float) return Float is
  3                begin
  4                        return Exp (X**2);
  5                end F;
  6                Result, L, H : Float;
  7        begin
  8                ... -- set bounds in L and H say
  9                Result := Integrate (F'Access, L, H); -- illegal in 95
 10                ...
 11        end Main

@ But this is illegal because of the accessibility check necessary to prevent us from writing something like

  1        Evil : Integrand;
  2        X    : Float;
  3        ...
  4        declare
  5                Y : Float;
  6                function F (X : Float) return Float is
  7                        ...
  8                        Y := X; -- assign to Y in local block
  9                        ...
 10                end F;
 11        begin
 12                Evil := F'Access: -- illegal
 13        end;
 14        X := Evil (X); -- call function out of context

@ Here we have attempted to assign an access to the local function F in the global variable Evil. If this assignment had been permitted then the call of Evil would indirectly have called the function F when the context in which F was declared no longer existed; F would then have attempted to assign to the variable Y which no longer existed and whose storage space might now be used for something else.

@ We can summarise this perhaps by saying that we are attempting to call F when it no longer exists.

@ Ada 2005 overcomes the problem by introducing anonymous access to subprogram types. This was actually considered during the design of Ada 95 but it was not done at the time for two main reasons. Firstly, the implementation problems for those who were using display vectors rather than static links were considered a hurdle. And secondly, a crafty technique was available using the newly introduced tagged types. And of course one could continue to use generics. But further thought showed that the implementation burden was not so great after all and nobody understood the tagged type technique which was really incredibly contorted. Moreover, the continued use of generics when other languages forty years ago had included a more natural mechanism was tiresome. So at long last Ada 2005 includes anonymous access to subprogram types.

@ We rewrite the integration function much as follows

  1        function Integrate (Fn : access function (X : Float) return Float; Lo, Hi : Float) return Float is
  2                Total : Float;
  3                N     : constant Integer := ... ; -- no of subdivisions
  4                Step  : Float := (HiLo) / Float (N);
  5                X     : Float := Lo; -- current point
  6        begin
  7                Total := 0.5 * Fn (Lo); -- value at low bound
  8                for I in 1 .. N1 loop
  9                        X := X + Step; -- add values at
 10                        Total := Total + Fn (X); -- intermediate points
 11                end loop;
 12                Total := Total + 0.5 * Fn (Hi); -- add final value
 13                return Total * Step; -- normalize
 14        end Integrate;

@ The important thing to notice is the profile of Integrate in which the parameter Fn is of an anonymous access to subprogram type. We have also shown a simple body which uses the trapezium/trapezoid method and so calls the actual function corresponding to Fn at the two end points of the range and at a number of equally spaced intermediate points. (NB It is time for a linguistic interlude. Roughly speaking English English trapezium equals US English trapezoid. They both originate from the Greek t?ape?a meaning a table (literally with four feet). Both originally meant a quadrilateral with no pairs of sides parallel. In the late 17th century, trapezium came to mean having one pair of sides parallel. In the 18th century trapezoid came to mean the same as trapezium but promptly faded out of use in England whereas in the US it continues in use. Meanwhile in the US, trapezium reverted to its original meaning of totally irregular. Trapezoid is rarely used in the UK but if used has reverted to its original meaning of totally irregular. A standard language would be useful. Anyway, the integration is using quadrilateral strips with one pair of sides parallel.) With this new declaration of Integrate, the accessibility problems are overcome and we are allowed to write Integrate(F'Access, ... ) just as we could write P(X'Access) in the example in the previous section where we discussed anonymous access to object types.

@ We still have to consider how a type conversion which would permit an assignment to a global variable is prevented. The following text illustrates both access to object and access to subprogram parameters.

  1        type AOT is access all Integer;
  2        type APT is access procedure (X : in out Float);
  3        Evil_Obj  : AOT;
  4        Evil_Proc : APT;
  5        procedure P (Objptr : access Integer; Procptr : access procedure (X : in out Float)) is
  6        begin
  7                Evil_Obj := AOT (Objptr);   -- fails at run time
  8                Evil_Proc := APT (Procptr); -- fails at compile time
  9        end P;
 10        declare
 11                An_Obj : aliased Integer;
 12                procedure A_Proc (X : in out Float) is
 13                begin ... end A_Proc;
 14        begin
 15                P (An_Obj'Access, A_Proc'Access); -- legal
 16        end;
 17        Evil_Obj.all := 0;     -- assign to nowhere
 18        Evil_Proc.all ( ... ); -- call nowhere

@ This repeats some of the structure of the previous section. The procedure P has an access to object parameter Objptr and an access to subprogram parameter Procptr; they are both of anonymous type.

@ The call of P in the local block passes the addresses of a local object An_Obj and a local procedure A_Proc to P. This is permitted. We now attempt to assign the parameter values from within P to global objects Evil_Obj and Evil_Proc with the intent of assigning indirectly via Evil_Obj and calling indirectly via Evil_Proc after the object and procedure referred to no longer exist.

@ Both of these wicked deeds are prevented by the accessibility rules.

@ In the case of the object parameter Objptr it knows the accessibility level of the actual An_Obj and this is seen to be greater than that of the type AOT and so the conversion is prevented at run time and in fact Program_Error is raised. But if An_Obj had been declared at the same level as AOT and not within an inner block then the conversion would have been permitted.

@ However, somewhat different rules apply to anonymous access to subprogram parameters. They do not carry an indication of the accessibility level of the actual parameter but simply treat it as if it were infinite (strictly – deeper than anything else). This of course prevents the conversion to the type APT and all is well; this is detected at compile time. But note that if the procedure A_Proc had been declared at the same level as APT then the conversion would still have failed because the accessibility level is treated as infinite.

@ There are a number of reasons for the different treatment of anonymous access to subprogram types.

@ A big problem is that named access to subprogram types are implemented in the same way as C *func in almost all compilers. Permitting the conversion from anonymous access to subprogram types to named ones would thus have caused problems because that model does not work especially for display based implementations. Carrying the accessibility level around would not have prevented these conversions. The key goal was simply to provide a facility corresponding to that in Pascal and not to encourage too much fooling about with access to subprogram types. Recall that the attribute Unchecked_Access is permitted for access to object types but was considered far too dangerous for access to subprogram types for similar reasons.

@ The reader may be feeling both tired and that there are other ways around the problems of accessibility anyway. Thus the double integration presented in the Introduction can easily be circumvented in many cases. We computed (1 (1

@ ¦ ¦ xy dy dx )0)0 using the following program with Integrate;

  1        procedure Main is
  2                function G (X : Float) return Float is
  3                        function F (Y : Float) return Float is
  4                        begin
  5                                return X*Y;
  6                        end F;
  7                begin
  8                        return Integrate (F'Access, 0.0, 1.0);
  9                end G;
 10                Result : Float;
 11        begin
 12                Result := Integrate (G'Access, 0.0, 1.0);
 13                ...
 14        end Main;

@ The essence of the problem was that F had to be declared inside G because it needed access to the parameter X of G. But the astute reader will note that this example is not very convincing because the integrals can be separated and the functions both declared at library level thus

  1        function F (Y : Float) return Float is
  2        begin
  3                return Y;
  4        end F;
  5
  6        function G (X : Float) return Float is
  7        begin
  8                return X;
  9        end G;
 10        Result := Integrate (F'Access, 0.0, 1.0) * Integrate(G'Access, 0.0, 1.0);

@ and so it all works using the Ada 95 version of Integrate anyway.

@ However, if the two integrals had been more convoluted or perhaps the region had not been square but triangular so that the bound of the inner integral depended on the outer variable as in (1 (x

@ ¦ ¦ xy dy dx )0)0 then nested functions would be vital.

@ We will now consider a more elegant example which illustrates how we might integrate an arbitrary function of two variables F(x, y) over a rectangular region.

@ Assume that we have the function Integrate for one dimension as before

  1        function Integrate (Fn : access function (X : Float) return Float; Lo, Hi : Float) return Float;

@ Now consider

  1        function Integrate (Fn : access function (X, Y : Float) return Float;
  2                LoX, HiX : Float; LoY, HiY : Float) return Float is
  3                function FnX (X : Float) return Float is
  4                        function FnY (Y : Float) return Float is
  5                        begin
  6                                return Fn (X, Y);
  7                        end FnY;
  8                begin
  9                        return Integrate (FnY'Access, LoY, HiY);
 10                end FnX;
 11        begin
 12                return Integrate (FnX'Access, LoX, HiX);
 13        end integrate;

@ The new function Integrate for two dimensions overloads and uses the function Integrate for one dimension (a good example of overloading). With this generality it is again impossible to arrange the structure in a manner which is legal in Ada 95.

@ We might use the two-dimensional integration routine to solve the original trivial problem as follows

  1        function F (X, Y : Float) return Float is
  2        begin
  3                return X*Y;
  4        end F;
  5        ...
  6        Result := Integrate (F'Access, 0.0, 1.0, 0.0, 1.0);

@ As an exercise the reader might like to rewrite the two dimensional function to work on a non- rectangular domain. The trick is to pass the bounds of the inner integral also as functions. The profile then becomes

  1        function Integrate
  2                (Fn       : access function (X, Y : Float) return Float;
  3                 LoX, HiX : Float;
  4                 LoY, HiY : access function (X : Float) return Float)
  5                        return Float;

@ In case the reader should think that this topic is all too mathematical it should be pointed out that anonymous access to subprogram parameters are widely used in the new container library thereby saving the unnecessary use of generics.

@ For example the package Ada.Containers.Vectors declares procedures such as

  1        procedure Update_Element
  2                (Container : in Vector; Index: in Index_Type;
  3                 Process   : not null access procedure (Element : in out Element_Type));

@ This updates the element of the vector Container whose index is Index by calling the procedure Process with that element as parameter. Thus if we have a vector of integers V and we need to double the value of those with index in the range 5 to 10, then we would first declare a procedure such as

  1        procedure Double (E : in out Integer) is
  2        begin
  3                E := 2 * E;
  4        end Double;

@ and then write

  1        for I in 5 .. 10 loop
  2                Update_Element (V, I, Double'Access);
  3        end loop;

@ Further details of the use of access to subprogram types with containers will be found in a later paper.

@ Finally it should be noted that anonymous access to subprogram types can also be used in all those places where anonymous access to object types are allowed. That is as stand-alone objects, as components of arrays and records, as function results, in renamings, and in access discriminants.

@ The reader who likes long sequences of reserved words should realise by now that there is no limit in Ada 2005. This is because a function without parameters can return an access to function as its result and this in turn could be of a similar kind. So we would have type FF is access function return access function return access function ...

@ Attempts to compile such an access to function type will inevitably lead to madness.

Rationale for Ada 2005: Access types

@ENGRUSTOPBACKNEXT

4. Нисходящие замкнутые выражения

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

@ Возможности Ады 83 по передаче подпрограмм в качестве параметров других подпрограмм были на удивление слабыми по сравнению с такими языками как Алгол 60 и Паскаль. Напомним, что Паскаль был одним из основных базовых языков Министерсва Обороны.

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

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

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

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

@ Как было уже сказано во Введении, вопрос был частично улучшен в Аде 95 введением именованных ссылочных типов к подпрограмме. Это было сделано в основном, для реализации механизма обратного вызова (callback).

@ Обратный вызов - это когда одна программа передает "адрес" подпрограммы другой программе так, чтобы эта другая программа могла позже вызвать обратно первую программу, используя полученный адрес. Это часто используется для коммуникации между прикладной программой Ады и некоторым другим программным обеспечением, типа операционной системы, которая могла быть написана на другом языке, например на C.

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

  1        type Integrand is access function (X : Float) return Float;
  2        function Integrate (Fn : Integrand; Lo, Hi : Float) return Float;

@ Но мы не можем сделать даже самой простой интеграции нашей собственной функции естественным способом. Например, предположим, что мы желаем интегрировать функцию Exp (X ** 2):

  1        procedure Main is
  2                function F (X : Float) return Float is
  3                begin
  4                        return Exp (X**2);
  5                end F;
  6                Result, L, H : Float;
  7        begin
  8                ... -- set bounds in L and H say
  9                Result := Integrate (F'Access, L, H); -- illegal in 95
 10                ...
 11        end Main

@ Но это незаконно из-за проверки области видимости, которая предназначена для того чтобы препятствовать использованию например такого кода:

  1        Evil : Integrand;
  2        X    : Float;
  3        ...
  4        declare
  5                Y : Float;
  6                function F (X : Float) return Float is
  7                        ...
  8                        Y := X; -- assign to Y in local block
  9                        ...
 10                end F;
 11        begin
 12                Evil := F'Access: -- illegal
 13        end;
 14        X := Evil (X); -- call function out of context

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

@ Мы можем подвести итог этого говоря, что мы пытаемся вызвать F когда его уже не существует.

@ Ада 2005 преодолевает эту проблему, вводя анонимные ссылки на типы подпрограмм. Эта возможность рассматривалась ещё при разработке дизайна ada, но в это время это не было сделано по двум основным причинам. Во-первых, при реализации необходимость ведения display-векторов, а не статических ссылок посчитали препятствием. И во-вторых, соображения лёгкой поддержки недавно введенных теговых типов. Конечно можно было продолжить использовать generics. Но при дальнейшем анализе оказалось что предполагаемые трудности оказались не так уж велики. Кроме того, чрезмерное использование generics, когда другие языки сорок лет назад включили более естественный механизм, было утомительно. Так в конце концов Ада 2005 включает анонимные ссылки на типы подпрограмм.

@ Мы перезаписываем функцию интеграции следующим образом:

  1        function Integrate (Fn : access function (X : Float) return Float; Lo, Hi : Float) return Float is
  2                Total : Float;
  3                N     : constant Integer := ... ; -- no of subdivisions
  4                Step  : Float := (HiLo) / Float (N);
  5                X     : Float := Lo; -- current point
  6        begin
  7                Total := 0.5 * Fn (Lo); -- value at low bound
  8                for I in 1 .. N1 loop
  9                        X := X + Step; -- add values at
 10                        Total := Total + Fn (X); -- intermediate points
 11                end loop;
 12                Total := Total + 0.5 * Fn (Hi); -- add final value
 13                return Total * Step; -- normalize
 14        end Integrate;

@ Обратите внимание на спецификацию функции Integrate в которой параметр Fn имеет анонимную ссылку на тип подпрограммы. Внутри процедуры реализован метод трапеций, там же через параметрическую ссылку Fn вызывается интегрируемая функция. Таким объявлением в Integrate преодолены проблемы видимости, и нам разрешено написать Integrate (F'Access...) так же, как мы могли написать P (X'Access) в примере предыдущего раздела, где мы обсуждали анонимные ссылки на объектовые типы.

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

  1        type AOT is access all Integer;
  2        type APT is access procedure (X : in out Float);
  3        Evil_Obj  : AOT;
  4        Evil_Proc : APT;
  5        procedure P (Objptr : access Integer; Procptr : access procedure (X : in out Float)) is
  6        begin
  7                Evil_Obj := AOT (Objptr);   -- fails at run time
  8                Evil_Proc := APT (Procptr); -- fails at compile time
  9        end P;
 10        declare
 11                An_Obj : aliased Integer;
 12                procedure A_Proc (X : in out Float) is
 13                begin ... end A_Proc;
 14        begin
 15                P (An_Obj'Access, A_Proc'Access); -- legal
 16        end;
 17        Evil_Obj.all := 0;     -- assign to nowhere
 18        Evil_Proc.all ( ... ); -- call nowhere

@ Это повторяет часть структуры предыдущего раздела. Процедура P имеет в качестве параметров ссылку на объект Objptr и ссылку на подпрограмму Procptr; они - оба анонимного типа.

@ Вызов P в локальном блоке передает адреса локального объекта An_Obj и локальной процедуры A_Proc. Здесь всё корректно. Теперь попытаемся внутри процедуры P присвоить значения параметра глобальным объектам Evil_Obj и Evil_Proc с намерением косвенного присваивания через Evil_Obj и косвенного вызова через Evil_Proc после того, как объект и процедура, упомянутая больше не существуют.

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

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

@ Однако, несколько иные правила относительно анонимных ссылок на подпрограммный тип в качестве параметра. Они не несут индикацию относительно уровня видимости фактического параметра, и они обрабатывается как будто область видимости бесконечна (строго - глубже чем что - нибудь еще). Это правило предотвращает преобразование в APT тип, которое будет обнаружено во время компиляции. Но заметим, что если бы процедура A_Proc была объявлена на том же самом уровне как APT тогда, преобразование всё равно было бы незаконным, потому что уровень видимости продолжал бы рассматривается как бесконечный.

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

@ Имеется много проблем с именованными ссылочными типами на подпрограммы, такими как *func в языке С в почти всех компиляторах. Разрешение преобразования от анонимного ссылочного типа подпрограммы к именованному таким образом вызвало бы проблемы, потому что эта модель не работает специально для ??? базируемого выполнения дисплея ???. Нести уровень видимости не предотвратило бы эти преобразования. Ключевая цель состояла в том, чтобы просто обеспечить средство, соответствующее этому на Паскале а не поощрять чрезмерные вольности с ссылочными типами на подпрограммы. Напомним, что признак Unchecked_Access разрешается для ссылочных типов на объекты, но это считается слишком опасным для ссылочных типов на подпрограммы по целому ряду причин.

@ Есть альтернативные пути решения проблем видимости так или иначе. Так двойная интеграция, представленная во Введении может быть легко обойдена во многих случаях. Вычислим двумерный интеграл на интервале от 0 до 1 по x и y при помощи следующей программы:

  1        procedure Main is
  2                function G (X : Float) return Float is
  3                        function F (Y : Float) return Float is
  4                        begin
  5                                return X*Y;
  6                        end F;
  7                begin
  8                        return Integrate (F'Access, 0.0, 1.0);
  9                end G;
 10                Result : Float;
 11        begin
 12                Result := Integrate (G'Access, 0.0, 1.0);
 13                ...
 14        end Main;

@ Сущность проблемы была в том, что F должен был быть объявлен в G, потому что необходимо обратиться к параметру X из G. Но проницательный читатель отметит, что этот пример не очень убедителен, потому что интегралы могут быть отделены и функции могут быть объявлены на библиотечном уровне следующим образом:

  1        function F (Y : Float) return Float is
  2        begin
  3                return Y;
  4        end F;
  5
  6        function G (X : Float) return Float is
  7        begin
  8                return X;
  9        end G;
 10        Result := Integrate (F'Access, 0.0, 1.0) * Integrate(G'Access, 0.0, 1.0);

@ и это работает в Аде 95.

@ Однако, если эти два интеграла были бы более замысловатыми, или область была бы не квадратной, а треугольной так, чтобы граница внутреннего интеграла зависела от внешней переменной как в (1 (x¦ ¦ xy дуплекс дю), 0) 0 тогда вложение функций было бы жизненно важно.

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

@ Предположим, что мы имеем функцию Integrate для одного измерения как раньше:

  1        function Integrate (Fn : access function (X : Float) return Float; Lo, Hi : Float) return Float;

@ Рассмотрим:

  1        function Integrate (Fn : access function (X, Y : Float) return Float;
  2                LoX, HiX : Float; LoY, HiY : Float) return Float is
  3                function FnX (X : Float) return Float is
  4                        function FnY (Y : Float) return Float is
  5                        begin
  6                                return Fn (X, Y);
  7                        end FnY;
  8                begin
  9                        return Integrate (FnY'Access, LoY, HiY);
 10                end FnX;
 11        begin
 12                return Integrate (FnX'Access, LoX, HiX);
 13        end integrate;

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

@ Мы могли бы использовать двумерную подпрограмму интеграции, чтобы решить первоначальную тривиальную проблему следующим образом:

  1        function F (X, Y : Float) return Float is
  2        begin
  3                return X*Y;
  4        end F;
  5        ...
  6        Result := Integrate (F'Access, 0.0, 1.0, 0.0, 1.0);

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

  1        function Integrate
  2                (Fn       : access function (X, Y : Float) return Float;
  3                 LoX, HiX : Float;
  4                 LoY, HiY : access function (X : Float) return Float)
  5                        return Float;

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

@ Например, пакет Ada.Containers.Vectors объявляет процедуру:

  1        procedure Update_Element
  2                (Container : in Vector; Index: in Index_Type;
  3                 Process   : not null access procedure (Element : in out Element_Type));

@ Здесь обновляется элемент векторного Контейнера, индекс которого - Index вызовом процедуры Process с тем элементом как параметр. Таким образом, если мы имеем вектор целых чисел V, и мы должны удвоить значение тех с индексом в диапазоне 5 - 10, тогда мы сначала объявили бы процедуру:

  1        procedure Double (E : in out Integer) is
  2        begin
  3                E := 2 * E;
  4        end Double;

@ и тогда мы имеем право написать:

  1        for I in 5 .. 10 loop
  2                Update_Element (V, I, Double'Access);
  3        end loop;

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

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

@ Читатель, который любит длинные последовательности зарезервированных слов, должен понять к настоящему времени, что нет никаких ограничений в Аде 2005 по этому поводу. Например, функция без параметров может возвратить ссылку на функцию как результат, а та, в свою очередь, может быть подобного вида. Таким образом, мы будем иметь нечто вроде: type FF is access function return access function...

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

@ ENG RUS

TOP BACK NEXT

2010-10-24 00:26:54

. .