> -- Цитата:
> Variables of a procedure type T have a procedure (or NIL) as value. If a procedure P is assigned to a variable of type T, the formal parameter lists (see Ch. 10.1) of P and T must match (see App. A). P must not be a predeclared procedure or a method nor may it be local to another procedure.

Часто в обычных апи процедурные переменные используются как функции обратного вызова (коллбэки):
## Код:
```
TYPE Action = PROCEDURE (element: Element);

PROCEDURE Iterate (list: ElementList; action: Action);
  VAR e: Element; ind: INTEGER;
BEGIN
  ASSERT(action # NIL, 20);
  ...
  e := ...; ind := ...;
  action(e);
END Iterate;

PROCEDURE Do (e: Element); BEGIN ... END Do;

PROCEDURE P;
  VAR proc: Action;
BEGIN
  proc := Do; ...  (* процедуры, кстати, могут присваиваться переменным, при условии совпадения типов *)
  Iterate(list, Do)
END P;
```

Также часто нужно, чтобы Action имела больше параметров, определяемых задачей, чем известно процедуре Iterate. Например, Action должна окрасить элементы в заданный цвет color. Как передать этот параметр, так чтобы не менять Iterate? Вероятно, нужно добавить некий универсальный параметр процедуре Action, который тж передается Iterate, которая, в свою очередь, передает его в Action. Этот универсальный параметр в прочих ЯВУ/библиотеках обычно трактуется как адрес чего-то в памяти, что неизвестно процедуре Iterate, и что будет истолковано процедурой Action. В КП, к счастью, нет адресов и памяти, а есть универсальный (т.е. всеобъемлющий) тип ANYREC/ANYPTR, который можно уточнить (расширить):

TYPE Action = PROCEDURE (element: Element; VAR par: ANYREC);
Par = RECORD col: INTEGER END;

PROCEDURE Do (e: Element; VAR par: ANYREC);
VAR c: INTEGER;
BEGIN
WITH par: Par DO e.color := par.col ELSE HALT(20) END;
END Do;

PROCEDURE Iterate (list: ElementList; do: Action; VAR par: ANYREC);
BEGIN
... do (e, par) ...
END Itetate;

Таким образом процедуре Action можно передать произвольное количество произвольных параметров - как поля записи Par.

Однако КП дает возможность решить такую задачу еще элегантнее (ИМХО), явно указав на связь Action и Par:

## Код:
```
TYPE
  Par = RECORD col: INTEGER END;

PROCEDURE (VAR par: Par) Do (e: Element), NEW;
BEGIN
  e.color := par.col
END Do;

PROCEDURE Iterate (list: ElementList; VAR par: Par);
BEGIN
  ... par.Do(e)
END Iterate;
```


Еще бОльшее преимущество - когда действия с элементом списка нужно предусмотреть разные, и параметризуемые по-разному:

## Код:
```
TYPE
  Action = ABSTRACT RECORD END;
  A1 = RECORD (Action) col: INTEGER END;
  A2 = RECORD (Action) s: String; pos: INTEGER END;

PROCEDURE (VAR a: Action) Do (e: Element), NEW, ABSTRACT;
PROCEDURE (VAR a: A1) Do (e: Element); BEGIN ... END Do;
PROCEDURE (VAR a: A2) Do (e: Element); BEGIN ... END Do;

PROCEDURE P;
  VAR a1: A1; a2: A2;
BEGIN
  a1.col := ...; Iterate(list, a1);
  a2.s := '...'; a2.pos := ...; Iterate(list, a2)
END P;
```


Отмечу, что "между строк": КП на i386 в приведенном примере совершенно не использует динамическую память: a1 a2 создаются в автоматической памяти (в стеке), стоимость их размещения и освобождения ~0.

Тем не менее, мне не до конца ясно, почему рекомендуется минимизировать использование процедурных типов, как процитировал ComDiv. Это средство языка менее выразительное, но, кажется, вполне безопасное.