О нужности/ненужности, достоинствах/недостатках концепции модулей в языках программирования есть очень много публикаций и обсуждений, поэтому я просто расскажу о реализации системы модулей в языках Оберон-семейства.
Модуль в Оберонах — это не только единица компиляции, загрузки и связывания, это ещё и механизм инкапсуляции. При обращении к сущностям подключенного( импортированного ) модуля, требуется обязательная квалификация этого модуля. Например, если модуль A импортирует модуль B, и использует его переменную v, то обращение к этой переменной должно иметь форму B.v, что снижает количество трудноотcлеживаемых ошибок использования совершенно других сущностей с тем же именем в немодульных языках, зависящих от последовательности подключения файлов и поведения компилятора.
Как я уже говорил, инкапсуляция в Оберонах также построена на концепции модуля — все типы, объявленные в модуле прозрачны друг для друга, а доступ внешних клиентов к сущностям модуля осуществляется посредством спецификаторов доступа. На текущий момент в Активном Обероне имеются следующие спецификаторы доступа:
* спецификатор «полный доступ» — идентификатор помечается знаком «звёздочка» (*);
* спецификатор «доступ только для чтения» — идентификатор помечается знаком минус (-);
Идентификаторы с отсутствующими спецификаторами недоступны внешним клиентам.
Например:
Описывает тип записи Example1, экспортированный за пределы модуля. Поле a доступно для чтения и записи клиентам модуля, в котором объявлен тип, поле b доступно только для чтения и поле c скрыто от внешних клиентов.
Имя модуля( итогового объектного файла ), указываемое после ключевого слова MODULE, может не совпадать с именем файла, что используется, например, для разделения реализаций модуля. Данный механизм может использоваться вместо механизма директив условной компиляции — мы всегда подключаем модуль с известным и фиксированным именем, а средства сборки генерируют необходимый модуль в соответствии с условиями сборки — различные ОС, процессоры и т.п.
В Активном Обероне подключаемые( импортированные ) модули могут иметь псевдонимы, различные модули могут иметь один и тот же псевдоним, формируя некое подобие пространства имён или псевдомодуль, обращение к сущностям пространства имён осуществляется по его имени, реальные имена модулей не доступны. Имена сущностей, находящихся в таком пространстве имён не должны пересекаться.
Исторически сложилось, что обычно, операционная среда Оберон предоставляет интерфейс для динамической загрузки и выгрузки модулей, хотя, возможно и статическое связывание, как, например, в Pow! или OO2C. Сам язык предоставляет лишь секцию импорта модулей и секцию инициализации модуля. В некоторых реализациях есть также секция финализации модуля, но, в общем случае, среда времени выполнения предоставляет программисту интерфейс для регистрации процедур-финализаторов, которые автоматически вызываются при выгрузке модуля или завершении работы программы при статическом связывании.
Типичная структура модуля в Активном Обероне:
(Да, в Активном Обероне ключевые слова могут быть в нижнем регистре, а не только КАПСом, выбор набора осуществляется по форме записи первого значимого идентификатора в модуле — ключевого слова MODULE. Если он записан в нижнем регистре, значит все ключевые слова модуля должны быть также в нижнем регистре, если — MODULE, то в верхнем.)
Если модуль загружен динамически, то выгрузить его можно только в том случае, если на него нет ссылок из секции импорта других модулей, т.е. выгрузка должна производиться в обратном порядке. Выгрузка модулей в ОС A2 производится командами SystemTools.Free принимающая список выгружаемых модулей и SystemTools.FreeDownTo, принимающая список модулей, которые должны быть выгружены после выгрузки всех ( рекурсивно ) ссылающихся на них.
Операционная среда старается не выгружать модули, предоставляя программисту выбирать время выгрузки( в том числе все зависящие от требуемого ), так как у динамической загрузки кроме плюсов есть существенные минусы — при неаккуратном подходе к выгрузке можно получить ситуацию, когда на модуль нет ссылок из секций импорта и он выгружен пользователем, а установленные им колбэки, например, остались, что вызовет исключительную ситуацию. Потому, что, как говорил Маленький Принц, «есть такое твёрдое правило — встал поутру, умылся, привёл себя в порядок — и сразу же приведи в порядок свою планету». Иными словами, если мы наставили колбеков, то нам их и убирать, что и производится в финализаторах модуля. Хорошим тоном считается создание для процедурных переменных реализаций по умолчанию — заглушек, которые и должны быть установлены при первоначальной инициализации модуля, в котором находится такая переменная и при финализации модуля, который установил эту переменную, присвоив свою реализацию.
Труднее обстоит дело с экземплярами ссылочных типов ибо ссылка может находится в модуле, который не подулючал модуль, в котором реализован тип, и формально на него нет ссылок в секциях импорта. Частично, с этим можно бороться применяя фабрики.
При выгрузке модуля, выгружаются код и данные, за исключением дескрипторов типов, так как в системе могут остаться экземпляры этих типов. Всем указателям в дескрипторах типа, включая указатели на методы в VMT присваивается значение NIL. При обращении к таким сущностям произойдёт исключительная ситуация.
Как видим, аскетичность реализации Оберон-Систем имеет как плюсы, так и минусы, которые следует учитывать в своих разработках. Никаких реальных проблем для устранения этих недостатков нет, кроме усложнения среды времени выполнения и компилятора.
Возможно, что с помощью сообщества эти проблемы получится решить, выведя Оберон на новую орбиту.
Модуль в Оберонах — это не только единица компиляции, загрузки и связывания, это ещё и механизм инкапсуляции. При обращении к сущностям подключенного( импортированного ) модуля, требуется обязательная квалификация этого модуля. Например, если модуль 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. При обращении к таким сущностям произойдёт исключительная ситуация.
Как видим, аскетичность реализации Оберон-Систем имеет как плюсы, так и минусы, которые следует учитывать в своих разработках. Никаких реальных проблем для устранения этих недостатков нет, кроме усложнения среды времени выполнения и компилятора.
Возможно, что с помощью сообщества эти проблемы получится решить, выведя Оберон на новую орбиту.