Процедурный тип

Тип, предназначенный для хранения ссылок на процедуры или функции, называется процедурным, а переменная такого типа - процедурной переменной. Основное назначение процедурных переменных - хранение и косвенный вызов действий (функций) в ходе выполнения программы и передача их в качестве параметров.

Описание процедурного типа

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

type
  ProcI = procedure (i: integer);
  FunI = function (x,y: integer): integer;

Процедурной переменной можно присвоить процедуру или функцию с совместимым типом, например:

function Mult(x,y: integer): integer;
begin
  Result := x*y;
end;

var f: FunI := Mult;

Процедурной переменной можно также присвоить лямбда-выражение с соответствующим количеством параметров и типом возвращаемого значения:

var f2: FunI := (x,y) -> x+2*y;

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

write(f(2));  // 8
write(f2(3)); // 10

Cинонимы для процедурных типов

Для наиболее распространенных процедурных типов в системном модуле определен ряд синонимов. Приведем примеры с их использованием:

var f3: IntFunc := x -> 2*x-1;
var f4: Func<integer,real> := x -> 2.5*x;
var f3: Action<real> := x -> write(x,'  ');
var pr: Predicate<string> := s -> s.Length>0;

Cокращенные конструкции для процедурных типов

Для процедурных типов определены также сокращенные конструкции:

() -> T;         // функция без параметров, возвращающая T
T1 -> T;         // функция c параметром T1, возвращающая T
(T1,T2) -> T     // функция c параметрами T1 и T2, возвращающая T
(T1,T2,T3) -> T  // функция c параметрами T1, T2 и T3, возвращающая T
  и т.д.
() -> ();        // процедура без параметров
T1 -> ();        // процедура c параметром T1
(T1,T2) -> ()    // процедура c параметрами T1 и T2
(T1,T2,T3) -> () // процедура c параметрами T1, T2 и T3
  и т.д.

Сокращенные конструкции не могут описывать процедурные переменные с параметрами, передаваемыми по ссылке.

Для процедурных переменных принята структурная эквивалентность типов: можно присваивать друг другу и передавать в качестве параметров процедурные переменные, совпадающие по структуре (типы и количество параметров, тип возвращаемого значения).

Процедурные переменные в качестве параметров

Обычно процедурные переменные передаются как параметры для реализации обратного вызова - вызова подпрограммы через процедурную переменную, переданную в качестве параметра в другую подпрограмму:

procedure forall(a: array of real; f: real->real);
begin
  for var i := 0 to a.Length-1 do
    a[i] := f(a[i]);
end;

...
forall(a,x->x*2); // умножение элементов массива на 2
forall(a,x->x+3); // увеличение элементов массива на 3

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

Операции += и -= для процедурных переменных

Процедурные переменные реализуются через делегаты .NET. Это означает, что они могут хранить несколько подпрограмм. Для добавления/отсоединения подпрограмм используются операторы += и -=:

p1 += mult2;
p1 += add3;
forall(a,p1);  

Подпрограммы в этом случае вызываются в порядке прикрепления: вначале умножение, потом сложение.

Отсоединение неприкрепленных подпрограмм не выполняет никаких действий:

p1 -= print;

Кроме того, к процедурной переменной можно прикреплять/откреплять классовые и экземплярные методы классов. В последнем случае процедурная переменная в полях объекта запоминает некоторое состояние, которое меняется между вызовами метода, связанного с этой процедурной переменной.

Пример

type
  A = class
    x0: integer := 1;
    h: integer := 2; 
    procedure PrintNext;
    begin
      Print(x0);
      x0 *= h; 
    end;
  end;

begin
  var
p: procedure;
  var a1 := new A();
  p := a1.PrintNext;
  for var i:=1 to 10 do
    p;
  // 1 2 4 8 16 32 64 128 256 512
end.

Подобное поведение гораздо проще реализовать с помощью захвата переменной лямбда-выражением:

begin
  var
x0 := 1;
  var
p: Action0 := procedure -> begin Print(x0); x0 *= 2 end;
  for var i:=1 to 10 do
    p;
end.