Как слова связаны в модуле Rebol?
Я понимаю что module!
Тип обеспечивает лучшую структуру для защищенных пространств имен, чем object!
или 'use
функция. Как слова связаны в модуле - я замечаю некоторые ошибки, связанные с несвязанными словами:
REBOL [Type: 'module] set 'foo "Bar"
Кроме того, как Ребол различает слово, локальное для модуля ('foo
) и функции системы ('set
)?
Незначительное обновление, вскоре после:
Я вижу, что есть переключатель, который меняет метод привязки:
REBOL [Type: 'module Options: [isolate]] set 'foo "Bar"
Что это делает по-другому? Какие есть недостатки в использовании этого метода по умолчанию?
1 ответ
ОК, это будет немного сложно.
В Rebol 3 нет таких вещей, как системные слова, есть только слова. Некоторые слова были добавлены в библиотеку времени выполнения lib
, а также set
это одно из тех слов, которым, как оказалось, назначена функция. Модули импортируют слова из lib
Однако, что означает "импорт", зависит от опций модуля. Это может быть сложнее, чем вы ожидали, поэтому позвольте мне объяснить.
Обычные модули
Для начала я расскажу, что означает импорт для "обычных" модулей, для которых не указаны какие-либо параметры. Давайте начнем с вашего первого модуля:
REBOL [Type: 'module] set 'foo "Bar"
Прежде всего, у вас ошибочное предположение: слово foo
не является локальным для модуля, это так же, как set
, Если вы хотите определить foo
в качестве локального слова вы должны использовать тот же метод, что и для объектов, используйте слово в качестве набора слов на верхнем уровне, например:
REBOL [Type: 'module] foo: "Bar"
Единственная разница между foo
а также set
является то, что вы не экспортировали или добавили слово foo
в lib
еще. Когда вы ссылаетесь на слова в модуле, который вы не объявили как локальные слова, он должен откуда-то получать их значения и / или привязки. Для обычных модулей он связывает код с lib
во-первых, затем переопределяет это, снова связывая код с локальным контекстом модуля. Любые слова, определенные в локальном контексте, будут связаны с ним. Любые слова, не определенные в локальном контексте, сохранят свои старые привязки, в этом случае lib
, Вот что означает "импорт" для обычных модулей.
В вашем первом примере, предполагая, что вы сами этого не сделали, слово foo
не был добавлен в библиотеку времени выполнения раньше времени. Это означает, что foo
не был связан с lib
и так как он не был объявлен как локальное слово, он также не был связан с локальным контекстом. В результате foo
не был связан ни с чем вообще. В вашем коде это было ошибкой, но в другом коде это может быть не так.
Изолированные модули
Существует опция "изолировать", которая изменяет способ импорта модулей, превращая его в "изолированный" модуль. Давайте использовать ваш второй пример здесь:
REBOL [Type: 'module Options: [isolate]] set 'foo "Bar"
Когда создается изолированный модуль, каждое слово в модуле, даже во вложенном коде, собирается в локальном контексте модуля. В данном случае это означает, что set
а также foo
местные слова. Начальные значения этих слов установлены на любые значения, которые они имеют в lib
в момент создания модуля. То есть, если слова определены в lib
совсем. Если слова не имеют значений в lib
у них изначально не будет значений в модуле.
Важно отметить, что этот импорт значений является разовым. После этого первоначального импорта любые изменения этих слов, сделанные вне модуля, не влияют на слова в модуле. Вот почему мы говорим, что модуль "изолирован". В случае вашего примера кода это означает, что кто-то может изменить lib/set
и это не повлияет на ваш код.
Но есть еще один важный тип модуля, который вы пропустили...
Сценарии
В Rebol 3 скрипты являются еще одним видом модуля. Вот ваш код в виде скрипта:
REBOL [] set 'foo "Bar"
Или, если хотите, заголовки скриптов не являются обязательными в Rebol 3:
set 'foo "Bar"
Скрипты также импортируют свои слова из lib
и они импортируют их в изолированный контекст, но с изюминкой: все сценарии имеют один и тот же изолированный контекст, известный как "пользовательский" контекст. Это означает, что когда вы изменяете значение слова в скрипте, следующий скрипт, который будет использовать это слово, увидит изменение при запуске. Так что если после запуска вышеуказанного скрипта вы попытаетесь запустить этот:
print foo
Тогда он будет печатать "Бар", а не иметь foo
быть неопределенным, даже если foo
до сих пор не определен в lib
, Возможно, вам будет интересно узнать, что если вы используете Rebol 3 в интерактивном режиме, вводите команды в консоль и получаете результаты, то каждая введенная вами командная строка представляет собой отдельный скрипт. Так что если ваша сессия выглядит так:
>> x: 1
== 1
>> print x
1
x: 1
а также print x
строки представляют собой отдельные сценарии, вторая использует преимущества, внесенные первой в пользовательский контекст.
Пользовательский контекст фактически должен быть локальным для задачи, но на данный момент давайте проигнорируем это.
Почему разница?
Вот где мы возвращаемся к понятию "системная функция", и что у Rebol их нет. set
Функция, как и любая другая функция. Это может быть реализовано по-другому, но это все еще нормальное значение, назначенное обычному слову. Приложению придется управлять многими этими словами, поэтому у нас есть модули и библиотека времени выполнения.
В приложении будут вещи, которые нужно изменить, и другие вещи, которые не должны меняться, и какие вещи зависят от приложения. Вы захотите сгруппировать свои вещи, чтобы держать вещи организованными или для контроля доступа. Там будут глобально определенные вещи и локально определенные вещи, и вы захотите иметь организованный способ доставки глобальных вещей в локальные места и наоборот, и разрешать любые конфликты, когда более чем одна вещь хочет определить вещи с помощью одно и то же имя
В Rebol 3 мы используем модули для группировки вещей, для удобства и контроля доступа. Мы используем библиотеку времени выполнения lib
как место для сбора экспорта модулей и разрешения конфликтов, чтобы контролировать то, что импортируется в локальные места, такие как другие модули и пользовательский контекст (ы). Если вам нужно переопределить некоторые вещи, вы делаете это путем изменения библиотеки времени выполнения и, если необходимо, распространяете свои изменения в контексте (ах) пользователя. Вы можете даже обновить модули во время выполнения, и новая версия модуля переопределит слова, экспортированные старой версией.
Для обычных модулей, когда все будет переопределено или обновлено, ваш модуль получит выгоду от таких изменений. Предполагая, что эти изменения являются преимуществом, это может быть хорошо. Обычный модуль взаимодействует с другими обычными модулями и сценариями для создания общей среды для работы.
Однако иногда вам нужно держаться отдельно от подобных изменений. Возможно, вам нужна конкретная версия какой-либо функции и вы не хотите обновляться. Возможно, ваш модуль будет загружен в менее надежной среде, и вы не хотите, чтобы ваш код был взломан. Возможно, вам просто нужно, чтобы вещи были более предсказуемыми. В таких случаях вы можете изолировать свой модуль от подобных внешних изменений.
Недостатком изолированности является то, что если вы захотите внести изменения в библиотеку времени выполнения, вы не получите их. Если ваш модуль каким-то образом доступен (например, если он был импортирован с именем), кто-то может передать вам эти изменения, но если вы недоступны, вам не повезло. Надеюсь, вы думали контролировать lib
за изменения, которые вы хотите, или ссылаться на материал через lib
непосредственно.
Тем не менее, мы упустили еще одну важную проблему...
Экспорт
Другая часть управления библиотекой времени выполнения и всеми этими локальными контекстами - экспорт. Вы должны как-то достать свои вещи. И самым важным фактором является то, что вы не подозреваете: есть ли у вашего модуля имя.
Имена являются необязательными для модулей Rebol 3. Поначалу это может показаться простым способом написания модулей (и именно в первоначальном предложении Карла именно поэтому). Однако оказывается, что есть много вещей, которые вы можете сделать, когда у вас есть имя, которое вы не можете, когда у вас его нет, просто из-за того, что такое имя: способ ссылаться на что-то. Если у вас нет имени, у вас нет способа сослаться на что-то.
Это может показаться тривиальной вещью, но вот некоторые вещи, которые имя позволяет вам делать:
- Вы можете сказать, загружен ли модуль.
- Вы можете убедиться, что модуль загружен только один раз.
- Вы можете сказать, была ли ранее более старая версия модуля, и, возможно, обновить ее.
- Вы можете получить доступ к модулю, который был загружен ранее.
Когда Карл решил сделать имена необязательными, он дал нам ситуацию, когда можно было бы создавать модули, для которых вы не могли бы делать ничего из этого. Учитывая, что экспорт модулей должен был собираться и организовываться в библиотеке времени выполнения, у нас была ситуация, когда вы могли иметь последствия для библиотеки, которую вы не могли легко обнаружить, и модулей, которые перезагружались при каждом импорте.
Поэтому для безопасности мы решили полностью исключить библиотеку времени выполнения и просто экспортировать слова из этих безымянных модулей непосредственно в локальный (модуль или пользовательский) контекст, который их импортировал. Это делает эти модули эффективно закрытыми, как если бы они принадлежали целевым контекстам. Мы взяли потенциально неловкую ситуацию и сделали ее особенной.
Это была такая особенность, что мы решили явно поддержать ее private
вариант. Это явный параметр помогает нам справиться с последней проблемой, не вызвавшей имя: нам не нужно перезагружать приватные модули снова и снова. Если вы даете модулю имя, его экспорт все еще может быть закрытым, но ему нужна только одна копия того, что он экспортирует.
Тем не менее, именованные или нет, частные или нет, то есть 3 типа экспорта.
Обычные именованные модули
Давайте возьмем этот модуль:
REBOL [type: module name: foo] export bar: 1
Импортирование этого добавляет модуль в список загруженных модулей с версией по умолчанию 0.0.0 и экспортирует одно слово bar
в библиотеку времени выполнения. "Экспорт" в этом случае означает добавление слова bar
в библиотеку времени выполнения, если его там нет, и установку этого слова lib/bar
к значению, что слово foo/bar
имеет после foo
завершил выполнение (если оно еще не установлено).
Стоит отметить, что этот автоматический экспорт происходит только один раз, когда тело foo
закончено выполнение Если вы внесете изменения в foo/bar
после этого это не влияет lib/bar
, Если вы хотите изменить lib/bar
Вы также должны сделать это вручную.
Стоит также отметить, что если lib/bar
уже существует раньше foo
импортировано, у вас не будет добавлено еще одно слово. И если lib/bar
уже установлено значение (не сброшено), импортируя foo
не будет перезаписывать существующее значение. Первым прибыл - первым обслужен Эквивалент в русском языке: поздний гость гложет и кость. Если вы хотите переопределить существующее значение lib/bar
вам придется сделать это вручную. Вот как мы используем lib
управлять переопределениями.
Основное преимущество, которое дает нам библиотека времени выполнения, состоит в том, что мы можем управлять всеми нашими экспортированными словами в одном месте, разрешая конфликты и переопределения. Тем не менее, еще одним преимуществом является то, что большинству модулей и сценариев фактически не нужно говорить, что они импортируют. Пока библиотека времени выполнения заполняется должным образом всеми необходимыми словами, ваш скрипт или модуль, который вы загрузите позже, будет в порядке. Это позволяет легко помещать кучу операторов импорта и любых переопределений в ваш стартовый код, который устанавливает все, что будет нужно остальному коду. Это сделано для того, чтобы упростить организацию и написание кода вашего приложения.
Именованные частные модули
В некоторых случаях вы не хотите экспортировать ваши вещи в основную библиотеку времени выполнения. Вещи в lib
импортируется во все, поэтому вы должны экспортировать только lib
что вы хотите сделать общедоступными. Иногда вы хотите создавать модули, которые экспортируют вещи только для контекстов, которые этого хотят. Иногда у вас есть несколько связанных модулей, общего объекта и служебного модуля или около того. Если это так, вы можете сделать частный модуль.
Давайте возьмем этот модуль:
REBOL [type: module name: foo options: [private]] export bar: 1
Импорт этого модуля не влияет lib
, Вместо этого его экспорт собирается в частную библиотеку времени выполнения, которая является локальной для модуля или пользовательского контекста, который импортирует этот модуль, вместе с таковыми из любых других частных модулей, которые импортирует цель, а затем импортируется в цель оттуда. Частная библиотека времени выполнения используется для того же разрешения конфликтов, что и lib
используется для. Основная библиотека времени выполнения lib
имеет приоритет над частной библиотекой, так что не рассчитывайте на частную библиотеку, переопределяющую глобальные вещи.
Такие вещи полезны для создания служебных модулей, расширенных API-интерфейсов или других подобных приемов. Это также полезно для создания строго модульного кода, который требует явного импорта, если это то, чем вы занимаетесь.
Стоит отметить, что если ваш модуль на самом деле ничего не экспортирует, нет разницы между именованным приватным модулем или именованным публичным модулем, поэтому он в основном рассматривается как публичный. Все, что имеет значение, это то, что у него есть имя. Что приводит нас к...
Безымянные Модули
Как объяснено выше, если у вашего модуля нет имени, его нужно рассматривать как частное. Хотя это больше, чем личное, так как вы не можете определить, загружен ли он, вы не можете обновить его или даже не перезагружать. Но что, если ты этого хочешь?
В некоторых случаях вы действительно хотите, чтобы ваш код работал для эффекта. В этих случаях повторный запуск кода каждый раз - это то, что вы хотите сделать. Может быть, это сценарий, с которым вы работаете do
но структурирование как модуль, чтобы избежать утечки слов. Возможно, вы создаете миксин, некоторые служебные функции, которые имеют локальное состояние, которое нужно инициализировать. Это может быть что угодно.
Я часто делаю свои %rebol.r
подать безымянный модуль, потому что я хочу иметь больше контроля над тем, что он экспортирует и как. Плюс, так как это сделано для эффекта и не нуждается в перезагрузке или обновлении, нет смысла давать ему имя.
Нет необходимости в примере кода, ваши предыдущие будут действовать таким образом.
Я надеюсь, что этого достаточно для обзора дизайна модульной системы R3.