Страницы

четверг, 16 ноября 2023 г.

Простейшее воплощение параметрического модуля

Если нужно использовать модули, параметризированные по ряду объявлений (generics, шаблоны), то в оригинальном Oberon, как и в Modula-2, это всегда было достижимо в некотором виде за счёт самой модульности, требуя, впрочем, расширенного понимания, что стоит за IMPORT. В минимальном варианте для этого достаточно простых средств даже без поддержки со стороны основных инструментов. Этот способ не идеален и позволяет сделать не всё, но и совсем плохим его не назовёшь — в нём не используется никаких опасных и сомнительных средств. Конечно, при очень плотном использовании обобщённых типов лучше внести соответствующие изменения в язык, а ещё лучше перепроектировать его с 0[0]. Если шаблоны нужны как вспомогательный механизм, то этот подход может оказаться даже лучше других.

Здесь представлен схематичный пример. Дополнительные детали воплощения для достижения нужных качеств представить несложно.

«Параметризированный» модуль может ничем не отличаться от обычного модуля, так как все необходимые объявления он берёт из импортированного модуля, служащего ему параметром:
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.

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

Для получения фактического модуля для конкретного типа необходимо специализировать обобщённый модуь, связав его с фактическим параметром-модулем через IMPORT. Код для специализации модуля где-то в сборочной системе[1]:

FileTool.Replace("List.mod", "ListRec.mod",
  "'MODULE List;'->'MODULE ListRec', 'END List.'->'END ListRec.', 'IMPORT Param'->'IMPORT Param := ParamRec'")
Для POSIX-shell

ChatGPT4 понял смысл FileTool.Replace и предложил такой работающий код:

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

Эта команда показывает возможность нулевого воплощения, в котором обобщенные модули используются без какой-либо специализированной поддержки, задействуя исключительно общеприменимые инструменты. Гипотетически, при наличии отдельных утилит или поддержки со стороны самого транслятора специализация может[2] выглядеть так:

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.

Весь код в песочнице.

ДостоинстваНедостатки
0 изменений языкаНеочевидность внеязыковых средств
Простейшее воплощениеМеньшее удобство использования
Эффективное по скоростиВ простейшем воплощении раздувание сгенерированного кода там, где этого можно избежать
Без применения опасных средствСуженная область применения
Невозможно параметризовать по внутренним неимпортированным объявлениям

При поддержке таких обобщённых модулей со стороны транслятора избыточного раздувания сгенерированного кода можно избежать. Изменений в языке для этого по-прежнему не требуется.


Примечания:

[0] Обобщённые типы — это не то, что можно гармонично внести в уже устоявшийся язык, поскольку это свойство лежит где-то в основах языка. Например, ARRAY count OF Type — это предопределённый обобщённый тип в Oberon. Если же язык позволяет создавать новые обобщённые типы, то и массивы желательно выразить в этих терминах, а не оставлять в виде отдельной специальной сущности.
[1] Специализация модуля в сборочной системе означает, что если это и приводит к появлению дополнительного файла, что само по себе не обязательно, то он оказывается лишь внутренним делом самой сборочной системы как промежуточные данные перед трансляцией. Такой файл не должен помещаться рядом с исходным кодом как локально, так и в репозитории, и нет необходимости его редактировать.
[2] В целом, такая специализация подходит как частный случай маршрутизации модулей, которая способна решать куда больше задач.

Комментариев нет:

Отправить комментарий

odcey 0.2

Обновление утилиты odcey — преобразователя насыщенного текстового формата BlackBox Component Builder .odc в плоский UTF-8 текст. До...