Обобщённые модули (дженерики) — модули, параметризированные по константам, процедурам и типам без требования к генетичекой связи через расширение записей. Такое обобщение кода позволяет повысить либо его переиспользуемость, либо эффективность вместе со статическими гарантиями.
Существуют диалекты Oberon с явной поддержкой параметризуемых модулей, но сам по себе исходный язык не предусматривает явных конструкций параметризации. Но что интересно, даже без явной поддержки обобщённые модули всё равно могут использоваться как принцип за счёт самой модульности, требуя лишь расширенного понимания того, что стоит за IMPORT. Подход настолько прост, что может использоваться даже без инструментальной поддержки, хотя и не исключает её. Если при явном введении дженериков в Oberon используют обобщение понятия модуля, то здесь, наоборот, обобщённые модули рассматриваются как частный случай использования обычных модулей.
Здесь представлен схематичный пример. Дополнительные детали воплощения для достижения нужных качеств представить несложно.
«Параметризированный» модуль может ничем не отличаться от обычного модуля, так как все необходимые объявления он берёт из импортированного модуля, который и служит ему параметром:
MODULE List; IMPORT Param; TYPE T* = POINTER TO R; R = RECORD next: T; val*: Param.T END; PROCEDURE Insert*(VAR list: T; val: Param.T); VAR l: T; BEGIN NEW(l); l.next := list; l.val := val; list := l END Insert; PROCEDURE Next*(VAR item: T): BOOLEAN; BEGIN item := item.next RETURN item # NIL END Next; END List.
Следующий исходный(формальный) параметр-модуль не только служит заготовкой для фактических параметров-модулей, но также позволяет напрямую использовать исходный список как динамически типизированный, что тоже имеет смысл:
MODULE Param; IMPORT V; TYPE T* = POINTER TO V.Base; END Param.
Фактический параметр-модуль с нужными объявлениями:
MODULE ParamRec; TYPE T* = RECORD r*: REAL; i*: INTEGER END; END ParamRec.
При должном обобщённом подходе модули-параметры могут использоваться вместе с разными параметризированными модулями.
Псевдо-код для специализации модуля где-то в сборочной системе:
FileTool.Replace("List.mod", "ListRec.mod", "'MODULE List;'->'MODULE ListRec', 'END List.'->'END ListRec.', 'IMPORT Param'->'IMPORT Param := ParamRec'")
ChatGPT4 понял смысл FileTool.Replace и предложил такой работающий код для POSIX-систем:
modspec() { sed -e "s/MODULE $1;/MODULE $2;/g" -e "s/END $1\./END $2\./g" -e "s/IMPORT $3/IMPORT $3 := $4/g" "$1.mod" > "gen/$2.mod"; } modspec List ListRec Param ParamRec
Эта команда показывает возможность нулевого воплощения, в котором обобщенные модули используются без какой-либо специализированной поддержки, задействуя исключительно общеприменимые инструменты. При наличии отдельных утилит специализация может выглядеть так:
Modules.Spec("ListRec := List(Param := ParamRec)")
MODULE UseList; IMPORT ListRec, Rec := ParamRec, log; PROCEDURE Go*; VAR list, item: ListRec.T; v: Rec.T; BEGIN list := NIL; v.i := 1; v.r := 2.3; ListRec.Insert(list, v); v.i := 0; v.r := 1.2; ListRec.Insert(list, v); item := list; REPEAT log.i(item.val.i); log.s(" "); log.rn(item.val.r) UNTIL ~ListRec.Next(item) END Go; END UseList.