Обобщенные модули — это одна из нетривиальных конструкций Тривиля, добавленная для того, чтобы делать обобщенные контейнеры.
Конструкция эта нетривиальна только в том смысле, что проще некуда. Кажется, что я первый изобретатель этого способа. Задачу она решает — обобщенный словарь (hash map) написан и многократно использован в компиляторе.
Увы, есть несколько недостатков. Прежде чем перейти к ним, напомню, как это выглядит сейчас:
- Есть сам обобщенный модуль — это модуль, в котором несколько имен не определены. Для словаря это: Ключ (тип ключа), Значение (тип значения), хеш (функция). И размер хеша — но это временно, руки не доходят написать расширение.
- Перед использованием Словаря его надо настроить. Это можно сделать в отдельном модуле или прямо в том модуле, где настройка нужна. Но, в любом случае настройка делается в отдельном файле.
Вот пример настройки, которая в привычной нотации выглядела бы как Словарь<Строка, Строка>:
настройка «стд::контейнеры/словарь»
модуль стр-стримпорт «стд::хеши/fnv»
тип Ключ = Строка
тип Значение = Строка
конст хеш = fnv.строка
конст размер = 307
Компилятор, обрабатывая настройку, делает реплику кода словаря. Код словаря становится частью кода модуля, при этом из модуля экспортируется все, что было экспортировано из словаря.
Если настройка сделана в отдельном модуле, то для использования надо его импортировать:
модуль x
импорт «стд::контейнеры/словарь/стр-стр» // модуль с настройкой
пусть мой-словарь = стр-стр.Словарь{} // далее мой-словарь.добавить(«а», «б»)
Теперь к недостаткам:
- Имя настроенного словаря — это всегда Словарь
- Неявность настройки: чтобы понять, на что произошла настройка, надо смотреть в то месте, где сделана настройка (в другой файл или модуль)
- В одном модуле нельзя сделать две настройки — так как имена пересекаются
- Экспорт (в этом случае типа Словарь) происходит автоматически, а он может быть не нужен (вреден), если настройка нужна только внутри модуля
Дополнительно:
- Каждая настройка (даже на те же самые аргументы), приводит к копии кода. Впрочем, в Тривиле меня это не волнует, так что этот недостаток я не рассматриваю.
Как можно устранить эти недостатки? Для начала надо определиться вот с чем: можно уйти от обобщенных модулей к обобщенным типам и функциям, как это делают все «нормальные» языки. Это потребует гораздо более сложной реализации и на это я мог бы пойти. Но есть нюанс. В Аркоде нужны (мне кажется) обобщенные компоненты, то есть обобщенные единицы компиляции. То есть отказываться от обобщенных модулей нет смысла в Тривиле, если они нужны в Аркоде. Тогда надо искать решение в рамках обобщенных единиц компиляции.
Вот примерное решение, которое я продолжаю обдумывать. Добавить конструкцию на верхнем уровне модуля:
настроить с2 = «стд::контейнеры/словарь» {
тип Ключ = Строка
тип Значение = Цел64
конст хеш = fnv.строка
}тип МойСловарь = с2.Словарь
В чем преимущества:
- не нужен отдельный файл, настройка происходит в месте использования (хоть она и заметно длиннее, чем в привычной нотации, но зато явная)
- все имена, определенные в настроенном обобщенном модуле, локальны в настройке (под-модуле) и доступны только через квалификацию (с2.Словарь)
- несколько настроек можно делать в одном модуле
- есть явное управление экспортом, если «МойСловарь» не экспортирован, то он локален
Как видно, недостатки устраняются, насколько хорошо — покажет практика.
С точки реализации, решение сложнее, чем текущее, требуется изменение не только в парсере, но и в семантическом анализе (в областях видимости и поиске имен) и в генерации. Впрочем, сложность все равно на порядки меньше, чем сложность реализации обобщенных типов и функций.
Пока не начинал делать, собираюсь с мыслями.
Постоянная ссылка
А разве в OCaml не то же самое с параметризованными модулями? Можно и решения обозначенных вопросов там подглядеть
Постоянная ссылка
Увы, не знаю как в OCaml. Можешь ссылку дать, где прочитать.
Постоянная ссылка
Что-то поломалось в нотификациях, уже пару месяцев не получал сообщений от сайта, сейчас случайно заглянул.
Гуглится легко про параметризованные модули в OCaml:
* https://caml.inria.fr/pub/docs/oreilly-book/html/book-ora132.html
* https://www.cs.cornell.edu/courses/cs3110/2012sp/lectures/lec09-functors/lec09.html
У них еще и «типы модулей» есть, не разбирался:
* https://v2.ocaml.org/manual/modtypes.html
Постоянная ссылка
Да, на первый взгляд кажется, что схожие в целом простые воплощения обобщённых модулей использовались и в других системах, например в Active Oberon и ECS Oberon. И есть даже 0-воплощение — «Простейшее воплощение параметрического модуля» практически из ничего.
Постоянная ссылка
А можно ссылку, где прочитать?
Постоянная ссылка
Могу дать только ориентиры.
ECS — https://ecs.openbrace.org/
другой диалект — https://oberon-lang.github.io/
Пример на AO — https://t.me/ModularSys/121
цитата — «добавление поддержки шаблонов модулей в инфраструктуру fox compiler suite почти ничего не стоило…
ну так в фоксе сейчас сделано так — шаблон модуля обрабатывается компилятором как обычный модуль, но генерируется только символьный файл (который копия исходного модуля + комментарии для челокека, выставленные компилятором).
При импорте-смециализации происходит подстановка аргументов и формируется модуль в памяти, который в дальнейшем и обрабатывается.»
Название заметки с 0-решением — это практически ссылка, если искать через ya.ru — https://vostok-space.blogspot.com/2023/11/primitive-generic.html
Постоянная ссылка
Спасибо!
Постоянная ссылка
В AO при параметризации сломали само понятие модуля. Но да, делать это было не очень сложно (и поэтому в ЯОС параметризованных модулей из AO нет и скорее всего не будет). Кстати, я уже сомневаюсь в том, что понятие модуля вообще применимо к Оберонам (и большинству других систем, где они есть). Если мы говорим о «модульных зданиях», то мы можем взять N идентичных модулей и составить из них здание. В компьютер мы можем вставить два модуля памяти. А вот два модуля Log в Оберон-системы мы не можем иметь. При параметризации мы это снова можем, или не можем, в зависимости от того, как устроен механизм подстановки параметров. Т.е. получается, что модуль — это не модуль в общетехническом смысле слова, а ломтик программы, единица горячей замены, область инкапсуляции данных, что хотите. Из набора ломтиков можно собрать разные программы, но вот два одинаковых ломтика в одной программе — это уже извините. Для этого нужны уже объекты или что-то подобное. Соответствует ли это ограничение понятию «модуля» вообще, если мы его рассматриваем в других областях техники. Видите, Константин, я пошёл стезёй оберонщика, т.е. решил заняться понятийным аппаратом и его извращением (или наоборот).
Постоянная ссылка
Нужно различить два понятия — «модуль» и «компонент».
Модуль — это из мира design-time, кусок инструкции (например, параметризованный).
Компонент — это кусок программной системы в runtime (потенциально со своим состоянием).
По инструкциям модуля можно сделать много компонент в runtime, скомпоновать, связать, и т.п.
В физических системах модуль — это part number.
В чертежах автомобиля (design time) один part number колеса. В реальном автомобиле (runtime) четыре экземпляра колеса, все с одним part number, но в разном состоянии изношенности.
С таким понятийным/терминологическим различением все становится гораздо проще.
Но увы, в отрасли эти термины иногда используются иначе, и почему «компонентный Pascal» компонентный, а не модульный — мне, лично, неясно.
Постоянная ссылка
Ну вообще если надо именно «проще некуда» — это с помощью #define (изолированно и в сочетании с #include). Менее безопасно и управляемо, но проще именно некуда — идея шаблона и подстановки тут в почти чистом виде.
Так что заявка на самую простую реализацию через меня бы не прошла. Вероятно, кроме простоты есть ещё какие-то требования, не сформулированные в явном виде, хотя мы должны до них догадаться из знания C++ и Java. А кто C++ и Java не знает, тот упал с парохода современности.