Цитата:
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.
Код:
ProcedureType = PROCEDURE [FormalParameters].
Часто в обычных апи процедурные переменные используются как функции обратного вызова (коллбэки):
Код:
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. Это средство языка менее выразительное, но, кажется, вполне безопасное.