Страницы

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

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

Если нужно использовать модули, параметризированные по ряду объявлений (generics, шаблоны), то в оригинальном Oberon, как и в Modula-2, это всегда было достижимо в некотором виде за счёт самой модульности. В минимальном варианте для этого достаточно простых средств даже без поддержки со стороны основных инструментов. Этот способ не идеален и позволяет сделать не всё, но и совсем плохим его не назовёшь — в нём не используется никаких опасных и сомнительных средств. Конечно, при очень плотном использовании обобщённых типов лучше внести соответствующие изменения в язык, а ещё лучше перепроектировать его с ноля[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 ParamRec;
 TYPE T* = RECORD r*: REAL; i*: INTEGER END;
END ParamRec.

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

Код для специализации модуля где-то в сборочной системе[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.

Следующий исходный(формальный) параметр-модуль не только служит болванкой для фактических параметров-модулей, но также позволяет напрямую использовать исходный список как динамически типизированный:

MODULE Param; IMPORT V;
 TYPE T* = POINTER TO V.Base;
END Param.

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

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

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


Примечания:

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

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

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

Обработка ошибок

Тема корректной обработки ошибок в программе является довольно сложным вопросом в программировании. Отчасти от того, что и она сама являет...