Оберон умер, да здравствует Оберон! Часть 2. Модули
О нужности/ненужности, достоинствах/недостатках концепции модулей в языках программирования есть очень много публикаций и обсуждений, поэтому я просто расскажу о реализации системы модулей в языках Оберон-семейства.Модуль в Оберонах — это не только единица компиляции, загрузки и связывания, это ещё и механизм инкапсуляции. При обращении к сущностям подключенного (импортированного) модуля, требуется обязательная квалификация этого модуля. Например, если модуль A импортирует модуль B, и использует его переменную v, то обращение к этой переменной должно иметь форму B.v, что снижает количество трудноотcлеживаемых ошибок использования совершенно других сущностей с тем же именем в немодульных языках, зависящих от последовательности подключения файлов и поведения компилятора.Как я уже говорил, инкапсуляция в Оберонах также построена на концепции модуля — все типы, объявленные в модуле прозрачны друг для друга, а доступ внешних клиентов к сущностям модуля осуществляется посредством спецификаторов доступа. На текущий момент в Активном Обероне имеются следующие спецификаторы доступа:* спецификатор «полный доступ» — идентификатор помечается знаком «звёздочка» (*);* спецификатор «доступ только для чтения» — идентификатор помечается знаком минус (-); Идентификаторы с отсутствующими спецификаторами недоступны внешним клиентам.Например:
TYPE Example* = RECORD a*, b-, c: LONGINT; END; Описывает тип записи Example1, экспортированный за пределы модуля. Поле a доступно для чтения и записи клиентам модуля, в котором объявлен тип, поле b доступно только для чтения и поле c скрыто от внешних клиентов.Имя модуля (итогового объектного файла), указываемое после ключевого слова MODULE, может не совпадать с именем файла, что используется, например, для разделения реализаций модуля. Данный механизм может использоваться вместо механизма директив условной компиляции — мы всегда подключаем модуль с известным и фиксированным именем, а средства сборки генерируют необходимый модуль в соответствии с условиями сборки — различные ОС, процессоры и т.п.
В Активном Обероне подключаемые (импортированные) модули могут иметь псевдонимы, различные модули могут иметь один и тот же псевдоним, формируя некое подобие пространства имён или псевдомодуль, обращение к сущностям пространства имён осуществляется по его имени, реальные имена модулей не доступны. Имена сущностей, находящихся в таком пространстве имён не должны пересекаться.
Исторически сложилось, что обычно, операционная среда Оберон предоставляет интерфейс для динамической загрузки и выгрузки модулей, хотя, возможно и статическое связывание, как, например, в Pow! или OO2C. Сам язык предоставляет лишь секцию импорта модулей и секцию инициализации модуля. В некоторых реализациях есть также секция финализации модуля, но, в общем случае, среда времени выполнения предоставляет программисту интерфейс для регистрации процедур-финализаторов, которые автоматически запускаются при выгрузке модуля или завершении работы программы при статическом связывании.Типичная структура модуля в Активном Обероне:
module Name; import Modules, …;
procedure Finalize*; begin … end Finalize;
begin (* инициализация модуля *) Modules.InstallTermHandler (Finalize); (* регистрация финализатора модуля *) … end Name. (Да, в Активном Обероне ключевые слова могут быть в нижнем регистре, а не только КАПСом, выбор набора осуществляется по форме записи первого значимого идентификатора в модуле — ключевого слова MODULE. Если он записан в нижнем регистре, значит все ключевые слова модуля должны быть также в нижнем регистре, если — MODULE? то в верхнем.)Если модуль загружен динамически, то выгрузить его можно только в том случае, если на него нет ссылок из секции импорта других модулей, т.е. выгрузка должна производиться в обратном порядке. Выгрузка модулей в ОС A2 производится командами SystemTools.Free принимающая список выгружаемых модулей и SystemTools.FreeDownTo, принимающая список модулей, которые должны быть выгружены после выгрузки всех (рекурсивно) ссылающихся на них.
Операционная среда старается не выгружать модули, предоставляя программисту выбирать время выгрузки (в том числе все зависящие от требуемого), так как у динамической загрузки кроме плюсов есть существенные минусы — при неаккуратном подходе к выгрузке можно получить ситуацию, когда на модуль нет ссылок из секций импорта и он выгружен пользователем, а установленные им колбэки, например, остались, что вызовет исключительную ситуацию. Поэтому, что, как говорил Маленький Принц, «есть такое твёрдое правило — встал поутру, умылся, привёл себя в порядок — и сразу же приведи в порядок свою планету». Иными словами, если мы наставили колбеков, то нам их и убирать, что и производится в финализаторах модуля. Хорошим тоном считается создание для процедурных переменных реализаций по умолчанию — заглушек, которые и должны быть установлены при первоначальной инициализации модуля, в котором находится такая переменная и при финализации модуля, который установил эту переменную, присвоив свою реализацию.
Труднее обстоит дело с экземплярами ссылочных типов ибо ссылка может находится в модуле, который не экспортировал модуль, в котором реализован тип, и формально на него нет ссылок в секциях импорта. Частично, с этим можно бороться применяя фабрики.
При выгрузке модуля, выгружаются код и данные, за исключением дескрипторов типов, так как ни них могут быть ссылки. Всем указателям, включая указатели на методы в VMT присваивается значение NIL. При обращении к таким сущностям произойдёт исключительная ситуация.
Как видим, аскетичность реализации Оберон-Систем имеет как плюсы, так и минусы, которые следует учитывать в своих разработках. Никаких реальных проблем для устранения этих недостатков нет, кроме усложнения среды времени выполнения и компилятора.
Возможно, что с помощью сообщества эти проблемы получится решить, выведя Оберон на новую орбиту.