Организующей программной единицей является раздел. В ряде языков, например, в Oberon их называют модулями [0].
section name { /* Это место перечисления используемых разделов и упорядоченных объявлений в строгом порядке: констант, типов, переменных, функций, отдела начализации переменных */ }.
Oberon
MODULE name; (* Это место перечисления используемых разделов и упорядоченных объявлений в строгом порядке: констант, типов, переменных, функций, отдела начализации переменных *) END name.
Раздел не привязан к понятию файла как излишне платформоспецифичному. Конечно, для обычной разработки удобно провести соответствие, но возможны и другие варианты, когда, например, код встраивается в интерактивный документ и запускается прямо оттуда, что может пригодиться в литературе и документации.
Для обеспечения доступности объявлений раздела другим разделам, они должны быть экспортированы. Упорядоченная совокупность экспортируемых объявлений составляет интерфейс раздела. Для работы с экспортированными объявлениями требуется произвести импорт разделов, в которых они объявлены.
section m1 { const (+a = 0;/* экспорт обозначен «+» перед именем */ b = a + 1) ... }. section m2 { import (m1) /* доступ к элементам раздела через двоеточие */ const (a = m1:a;/* a = 0 */ b = m1:b)/* ошибка - константа "b" не экспортирована в "m1" */ ... }.
Прямой или косвенный циклический импорт разделов запрещён. Это требует большего размышления над архитектурой, но способствует её улучшению и уменьшает сцепление кода.
section m1 { import (m2) }. section m2 { import (m1)/* ошибка - m1 уже ссылается на m2 */ }.
Назначение разделов
Смысл использования разделов может существенно отличаться, что желательно отобразить в языке для ясности и возможности дополнительной защиты со стороны транслятора.
- Раздел может использоваться исключительно как вспомогательный, не влияя на интерфейс импортирующего раздела. Использование такого раздела может быть заменёно другим кодом, выполняющим те же задачи.
- Раздел, являющийся частью интерфейса импортирующего его раздела. Объявленные в нём типы могут становиться видимой частью экспортируемых объявлений импортирующего.
- Обобщённый раздел-заготовка, чьё определение задаётся не только его непосредственным содержимым, но также и импортированными разделами-параметрами.
- Опциональные разделы, наличие которых можно проверить в коде. Могут использоваться либо для создания более ограниченной версии раздела, либо для задействования более эффективных средств, не приводя к жёсткой зависимости от них.
- Декоративные разделы, служащие для удобства использования. Они каким-либо образом перестраивают взаимодействие с основным функционалом, практически не меняя его основной сути, например, объединяя интерфейсы разных разделов.
Поскольку разделов, особенно с учётом сторонних библиотек, может быть много, то возникает необходимость в создании иерархий:
section a/m { const (+c1 = 1) }. section a/m2 { const (+c2 = 2) }. section b/m { const (+c3 = 3) }. section m { import ( a/m; /* в качестве идентификатора раздела после импорта */ a/m2; /* используется только вторая часть имени после точки */ bm = b/m/* переименование, чтобы избежать ошибки совпадения имён */ ) const (c4 = m:c1 + m2:c2 + bm:c3) }.
Версии разделов
Система в целом может одновременно поддерживать разные версии одного и того же раздела. С её точки зрения это просто разные разделы, их типы считаются разными даже при полном совпадении объявлений. Таким образом разрешается проблема несовместимых зависимостей. Возможна и более тонкая настройка, когда учитывается не столько раздел целиком, сколько его отдельные объявления. Если объявления совпадают полностью, включая поведение, обеспечиваемое тем же самым исполняемым кодом, то их всё же можно считать совместимыми, не переводя на них несовместимость других объявлений.
В примере версии разделов указаны напрямую, но также их можно задать в проекте маршрутизации разделов[1], продолжая оперировать только именами в коде самих разделов.
section lib.0.1 { ... }
section lib.0.2 { ... }
section a { import (lib.0.1) }
section b { import (lib.0.2) }
section u { import (a; b; lib1 = lib.0.1; lib2 = lib.0.2) }
Разграничение, подразделы
Для удобства разграничения возможностей, предоставляемых импортирующим разделам, раздел может быть разбит на части, ограниченно владеющие функционалом. Раздел может сохранять высокую связность, поэтому такие части не могут быть разнесены по разным разделам.
/* path — это имя раздела, а name, clear и all — имена подразделов, открывающие дополнительный функционал пользователям path, которым дан доступ к этим подразделам и если они их указывают при импорте */ section path +name+clear+all { type (+t) /* можно обозначить, не раскрывая содержимое */ section +name+clear+all: type (+t = { +name string }) section +clear+all: type (+t = { +up (*)t }) section +all: /* Только в этом подразделе позволено задавать путь, и без доступа к нему путь к произвольным ресурсам закрыт по определению */ proc +new(name string, indir (*)t) (path *t) { ...; path.name = name; path.up = indir } proc +str(spath string) (path *t) { ... } } path. section editor { import ( /* path+all; */ /* после импорта должен быть доступен по имени path, но у editor нет доступа к полному разделу, и здесь была бы ошибка трансляции */ path; /* эта часть позволяет получать доступ к ресурсам, пути к которым явно переданы параметрами, но не позволяет самостоятельно указывать, с какими ресурсами можно работать, как и не даёт доступа к самому имени */ io ) proc +do(file path:t) { ... io.open(file) ... // io.open(path.str("~/.ssh/id_rsa")) // ошибка трансляции } }.
Подразделы могут вносить следующие изменения относительно предыдущих подразделов:
- Добавление объявлений, включая элементы в ранее объявленные типы.
- Добавление экспортированности ранее закрытым объявлениям, включая отдельные элементы.
По сравнению с некоторыми другими способами обособления такое разделение наглядней и позволяет легче доказывать отсутствие в меньших интерфейсах предоставления лишних возможностей. Нет смешивания, в котором было бы легко потерять включение чего-то ненужного. Чего нет в подразделе, то точно недоступно. Вынесение разделения на пользовательский уровень вместе с другими мерами позволит получить гарантии отсутствия нежелательного доступа у произвольного исполнимого кода уже на этапе проверки кода, что даст гораздо лучшую предсказумость и понятность системы в сравнении с устаревшим подходом.
Трансляция
При трансляции правильного раздела может создаваться два файла: один с исполнимым кодом, другой - интерфейсный для возможности независимой сборки импортирующих его разделов. Лучше, если исполнимый код будет не специфичным для машины, хотя и с возможностью внедрения машинного кода, выступающего как оптимизированная альтернатива основному коду. Для обоих файлов должны быть предусмотрены контроль целостности и подпись.
Чтобы избежать случайного изменения интерфейса раздела, транслятор по умолчанию должен выдавать ошибку, если вновь созданный интерфейсный файл не совпадает с предыдущим. В инструментарии для больших команд следует предусмотреть возможность распределения ролей, в которых некоторым разработчикам запрещается менять интерфейс целевых, не вспомогательных разделов, над которыми они работают.
В операционных системах, где были бы воплощены идеи хотя бы 80-х, а не только 60-70-х, раздел может служить главной и единственной единицей загрузки вместо россыпи таких понятий как программа, статическая и динамическая библиотеки. В морально устаревших же операционных системах нужно будет выполнить наподобие этого:
$ sc build util.do
Что приведёт к созданию исполнимого файла util, полученного из одноименного раздела с точкой входа do — экспортированной процедуры без параметров.
Необходимо ответить на вопрос: «Если в разделе несовместимо изменилось экспортированное объявление, должна ли потеряться совместимость с разделами, использующими только неизменившиеся его объявления?» Если ответ — «не должен», значит истинными модулями являются отдельные объявления раздела, а не сам раздел. Раздел же служит организационной цели, так как отдельные объявления слишком мелки для этого. Объявления пронизаны неявным импортом других объявлений раздела для избежания чрезмерного перечисления используемых связей, что было бы нужно в случае раздельного их оформления.
Сноски:
[0] Почему раздел — это не модуль/unit?
[2]
Оберон умер, да здравствует Оберон! Часть 2. Модули
[3]
Михаэль Франц Динамическая кодогенерация: ключ к разработке переносимого программного обеспечения
[4]
Webassembly: Design Rationale
Комментариев нет:
Отправить комментарий