О "шаблонной магии". Полиморфная параметризация модулей в стиле A2 сохраняет однородность модульности по-паскалевски, гораздо меньше "срывает крышу". Параметризация именно типов ("по месту") в общем случае может заставить, как минимум, ввести в язык вложенные типы. Да и "протаскивание" параметров по коду отнюдь нелёгкое занятие, мороки хватает. К тому же при наличии локальной параметризации всё равно тяжело без "ассоциированных типов", например:
https://rurust.github.io/rust-by-example-ru/generics/assoc_items/types.htmlОднако, родовые модули имеют некоторую ограниченность в возможностях. Частично (собственно то, не мало) ограниченность снимается с помощью тех же тегов типа.
Ключевое -- параметры-типы модуля есть те же метатипы. А также для удовлетворения и "мета-runtime" необходима возможность "инстанцирования" модуля не только в IMPORT (в разрезе всего модуля), но и по месту.
Реализация полиморфного "вектора":
Код:
MODULE Containers(TYPE T);
TYPE
Handle = ...;
Vector = RECORD
buf: ARRAY OF T;
...
END;
(* инициализатор/конструктор как функция *)
PROCEDURE &Init(capacity: INTEGER = 10): Vector;
BEGIN
( * применяется расширенная форма оператора RETURN из Ада -- см. далее * )
RETURN v: Vector DO
(* как-то используем capacity и прочие настройки *)
v.capacity := capacity;
...
END;
END;
(* поиск. Здесь могут быть задействованы некие операции аля "<" для типа T *)
PROCEDURE find(v: Vector; a: T): Handle;
BEGIN
...
END;
(* Поиск по элементу внутри структуры в предположении, что тип T является структурным.
Параметр U определён как входящий в T, он может быть любой вложенности в иерархии RECORD-ов
или где-то внутри массивов из "TYPE OF" в зависимости от фактического определения типа T.
Тип ANYTYPE -- синоним для "TYPE OF ANY" -- предельно универсальный базовый метатип.
Тип для "a" указан как "зависимый тип", т.е. в данном случае тип U введён локально.
Если бы была локальная полиморфная параметризация, то процедура имела бы примерно такой вид:
PROCEDURE findBy[TYPE T; TYPE U IN T](v: Vector(T); a: U): Handle;
*)
PROCEDURE findBy(v: Vector; U: ANYTYPE IN T; a: U): Handle;
VAR
e: T;
h: Handle;
BEGIN
...
(* используем переменную U как полноценный метатип *)
e := v.buf[...];
IF U[e] = a THEN
...
END;
(* Или альтернативный вариант алгоритма.
Глобальный параметр-тип модуля T также является метатипом *)
h := buf.handles[...];
e := T[h];
IF U[h] = a THEN
...
END;
END;
(* Перегрузка оператора "индекс" (чтение в данном случае) для метатипа,
т.е. для возможности применять так:
VAR v: Containers(INTEGER).Vector;
h: Handle;
t: TYPE OF INTEGER;
i: INTEGER;
...
h := v.find(100);
i := t[h]; (* вызов PROCEDURE "[]" ...*)
*)
PROCEDURE "[]"(tt: TYPE OF T; h: Handle): T;
BEGIN
(* Для простоты пусть тип Handle есть некий указатель
на тип T (тип элементов вектора). Здесь компилятор "разрулит"
оператор "индекс" уже по типу T, в том числе если фактический "TYPE OF T"
является тегом на вложенные элементы в структуре *)
RETURN tt[h^];
END;
(* применяем тип: *)
MODULE MyModule;
IMPORT CR := Containers(Rec); (* глобальное инстанцирование *)
...
TYPE
Rec = RECORD
a, b: INTEGER
END;
...
VAR
v: CR.Vector;
h: Handle;
...
BEGIN
(* инициализация с вызовом &Init(capacity) *)
v := NEW(100);
(* поиск по полю "a" *)
h := v.findBy(Rec.a, 23);
...
(* применяем тип с использованием метатипов в runtime *)
MODULE MyModule;
IMPORT C := Containers; (* нет глобального инстанцирования *)
...
VAR
v: C.Vector;
ti: TYPE OF INTEGER;
tr: TYPE OF REAL;
h: Handle;
...
BEGIN
(* Инициализируем с "runtime-инстанцированием", в т.ч. есть вызов &Init(capacity)
как при использовании NEW *)
v := C([ti, tr]).Vector(100);
...
h := v.find([23, 4.6]); (* поиск по "полному" значению *)
h := v.findBy(ti, 30); (* поиск по "полю" ti *)
(* применяем тип с использованием метатипов в compiletime *)
MODULE MyModule;
IMPORT C := Containers;
...
VAR
h: Handle;
ti: TYPE OF INTEGER;
tr: TYPE OF REAL;
(* статическое инстанцирование и инициализация по месту *)
v: C([ti, tr]).Vector := NEW(100);
...
BEGIN
...
h := v.find([23, 4.6]); (* поиск по "полному" значению *)
h := v.findBy(ti, 30); (* поиск по "полю" ti *)
Таким образом, сохраняется однородность между полиморфными параметрами и метатипами. Гипотетически компилятор может осуществлять все необходимые оптимизации, в т.ч. и inline (регулируемо). Для параметра типа вида динамического массива из тегов "TYPE OF" (статически не известного типа в полном объёме) возникает вариант "runtime-инстанцирования", с соответствующими последующими runtime-операциями доступа к данным и т.д.
В общем случае определение полиморфного (и не только) типа является распределенным. Например, в ином модуле можем задать новый инициализатор:
Код:
MODULE ExContainers(TYPE T);
IMPORT C:= Containers(T);
...
PROCEDURE &FromArray(a: ARRAY OF T): C.Vector;
BEGIN
(* предварительно применяем Init из C *)
RETURN v: C.Vector := C.Init() DO
...
END;
END;
Применять понятие "методов" с неявным SELF, располагая процедуры внутри "тела" типа, неудобно (в широком масштабе). Пусть все методы есть обычные процедуры (первый параметр может быть интерпретирован как "ресивер" для "объектной" формы вызова вида "o.met()"). Конструкторы являются функциями, возвращающими объект целевого типа. В данном случае необходимо предполагать нечто вроде паскалевской переменной Result или мол имя функции как результат (реальный объект уже предварительно создан). Но удобнее применить (для конструкторов -- обязать) аналог расширенной формы оператора return из Ада (RETURN ... DO ... END), семантика которого предполагает отсутствие копирования при "возврате" и пр. (для тамошних "лимитированных" типов, кстати, полезных с запретом копирования).
Стиль инициализаторов типа предполагается как в A2, в т.ч. операция NEW (или runtime "обращение к типу" при инициализации) осуществляет подбор нужной процедуры-конструктора, как для указателей, так и для типов-значений (по аналогии с "математическими" массивами).
Для параметров процедур, особенно полиморфного типа, полезен стиль из "Оберон 3" -- для структурных типов "IN"-модификатор (передача по ссылке) подразумевается по умолчанию.