Тривиль: доработка обобщенных модулей

Обобщенные модули — это одна из нетривиальных конструкций Тривиля, добавленная для того, чтобы делать обобщенные контейнеры.

Конструкция эта нетривиальна только в том смысле, что проще некуда. Кажется, что я первый изобретатель этого способа. Задачу она решает — обобщенный словарь (hash map) написан и многократно использован в компиляторе.

Увы, есть несколько недостатков. Прежде чем перейти к ним, напомню, как это выглядит сейчас:

  1. Есть сам обобщенный модуль — это модуль, в котором несколько имен не определены. Для словаря это: Ключ (тип ключа), Значение (тип значения), хеш (функция). И размер хеша — но это временно, руки не доходят написать расширение.
  2. Перед использованием Словаря его надо настроить. Это можно сделать в отдельном модуле или прямо в том модуле, где настройка нужна. Но, в любом случае настройка делается в отдельном файле.

Вот пример настройки, которая в привычной нотации выглядела бы как Словарь<Строка, Строка>:

настройка «стд::контейнеры/словарь»
модуль стр-стр

импорт «стд::хеши/fnv»

тип Ключ = Строка
тип Значение = Строка
конст хеш = fnv.строка
конст размер = 307

Компилятор, обрабатывая настройку, делает реплику кода словаря. Код словаря становится частью кода модуля, при этом из модуля экспортируется все, что было экспортировано из словаря.

Если настройка сделана в отдельном модуле, то для использования надо его импортировать:

модуль x

импорт «стд::контейнеры/словарь/стр-стр» // модуль с настройкой

пусть мой-словарь = стр-стр.Словарь{} // далее мой-словарь.добавить(«а», «б»)

Теперь к недостаткам:

  • Имя настроенного словаря — это всегда Словарь
  • Неявность настройки: чтобы понять, на что произошла настройка, надо смотреть в то месте, где сделана настройка (в другой файл или модуль)
  • В одном модуле нельзя сделать две настройки — так как имена пересекаются
  • Экспорт (в этом случае типа Словарь) происходит автоматически, а он может быть не нужен (вреден), если настройка нужна только внутри модуля

Дополнительно:

  • Каждая настройка (даже на те же самые аргументы), приводит к копии кода. Впрочем, в Тривиле меня это не волнует, так что этот недостаток я не рассматриваю.

Как можно устранить эти недостатки? Для начала надо определиться вот с чем: можно уйти от обобщенных модулей к обобщенным типам и функциям, как это делают все «нормальные» языки. Это потребует гораздо более сложной реализации и на это я мог бы пойти. Но есть нюанс. В Аркоде нужны  (мне кажется) обобщенные компоненты, то есть обобщенные единицы компиляции. То есть отказываться от обобщенных модулей нет смысла в Тривиле, если они нужны в Аркоде. Тогда надо искать решение в рамках обобщенных единиц компиляции.

Вот примерное решение, которое я продолжаю обдумывать. Добавить конструкцию на верхнем уровне модуля:

настроить с2 = «стд::контейнеры/словарь» {
тип Ключ = Строка
тип Значение = Цел64
конст хеш = fnv.строка
}

тип МойСловарь = с2.Словарь

В чем преимущества:

  • не нужен отдельный файл, настройка происходит в месте использования (хоть она и заметно длиннее, чем в привычной нотации, но зато явная)
  • все имена, определенные в настроенном обобщенном модуле, локальны в настройке (под-модуле) и доступны только через квалификацию (с2.Словарь)
  • несколько настроек можно делать в одном модуле
  • есть явное управление экспортом, если «МойСловарь» не экспортирован, то он локален

Как видно, недостатки устраняются, насколько хорошо — покажет практика.

С точки реализации, решение сложнее, чем текущее, требуется изменение не только в парсере, но и в семантическом анализе (в областях видимости и поиске имен) и в генерации. Впрочем, сложность все равно на порядки меньше, чем сложность реализации обобщенных типов и функций.

Пока не начинал делать, собираюсь с мыслями.

10 комментариев


  1. А разве в OCaml не то же самое с параметризованными модулями? Можно и решения обозначенных вопросов там подглядеть

    Ответить

      1. Что-то поломалось в нотификациях, уже пару месяцев не получал сообщений от сайта, сейчас случайно заглянул.

        Гуглится легко про параметризованные модули в 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

        Ответить

  2. Да, на первый взгляд кажется, что схожие в целом простые воплощения обобщённых модулей использовались и в других системах, например в Active Oberon и ECS Oberon. И есть даже 0-воплощение — «Простейшее воплощение параметрического модуля» практически из ничего.

    Ответить

      1. Могу дать только ориентиры.
        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

        Ответить

        1. В AO при параметризации сломали само понятие модуля. Но да, делать это было не очень сложно (и поэтому в ЯОС параметризованных модулей из AO нет и скорее всего не будет). Кстати, я уже сомневаюсь в том, что понятие модуля вообще применимо к Оберонам (и большинству других систем, где они есть). Если мы говорим о «модульных зданиях», то мы можем взять N идентичных модулей и составить из них здание. В компьютер мы можем вставить два модуля памяти. А вот два модуля Log в Оберон-системы мы не можем иметь. При параметризации мы это снова можем, или не можем, в зависимости от того, как устроен механизм подстановки параметров. Т.е. получается, что модуль — это не модуль в общетехническом смысле слова, а ломтик программы, единица горячей замены, область инкапсуляции данных, что хотите. Из набора ломтиков можно собрать разные программы, но вот два одинаковых ломтика в одной программе — это уже извините. Для этого нужны уже объекты или что-то подобное. Соответствует ли это ограничение понятию «модуля» вообще, если мы его рассматриваем в других областях техники. Видите, Константин, я пошёл стезёй оберонщика, т.е. решил заняться понятийным аппаратом и его извращением (или наоборот).

          Ответить

          1. Нужно различить два понятия — «модуль» и «компонент».
            Модуль — это из мира design-time, кусок инструкции (например, параметризованный).
            Компонент — это кусок программной системы в runtime (потенциально со своим состоянием).
            По инструкциям модуля можно сделать много компонент в runtime, скомпоновать, связать, и т.п.
            В физических системах модуль — это part number.
            В чертежах автомобиля (design time) один part number колеса. В реальном автомобиле (runtime) четыре экземпляра колеса, все с одним part number, но в разном состоянии изношенности.
            С таким понятийным/терминологическим различением все становится гораздо проще.
            Но увы, в отрасли эти термины иногда используются иначе, и почему «компонентный Pascal» компонентный, а не модульный — мне, лично, неясно.


  3. Ну вообще если надо именно «проще некуда» — это с помощью #define (изолированно и в сочетании с #include). Менее безопасно и управляемо, но проще именно некуда — идея шаблона и подстановки тут в почти чистом виде.

    Так что заявка на самую простую реализацию через меня бы не прошла. Вероятно, кроме простоты есть ещё какие-то требования, не сформулированные в явном виде, хотя мы должны до них догадаться из знания C++ и Java. А кто C++ и Java не знает, тот упал с парохода современности.

    Ответить

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *