11.4 Экспортирование и импортирование символов

Символы из одного пакета могут стать доступными в другом пакете двумя способами.

Первый способ, каждый отдельный символ может быть добавлен в пакет с использованием функции import. Форма (import ’editor:buffer) принимает внешний символ с именем buffer в пакете editor (этот символ распознаётся Lisp’овым считывателем) и добавляет его в текущий пакет в качестве внутреннего символа. Символ становится доступным в текущем пакете. Импортированный символ автоматически не экспортируется из текущего пакета, но если он уже существовал в пакете и был внешним, то это свойство не меняется. После вызова import в импортирующем пакете появляется возможность ссылаться на buffer без указания полного имени. Свойства buffer в пакете editor не меняются, editor продолжает оставаться домашним пакетом для этого символа. Будучи импортированным, символ будет присутствовать в пакете и может быть удалён только с помощью вызова unintern.

Если символ уже присутствует в импортирующем пакете, import ничего не делает. Если другой символ с таким же именем buffer уже доступен в импортирующем пакете (напрямую или унаследован), тогда сигнализируется исправимая ошибка, как написано в разделе 11.5. import не допускает сокрытия одного символа другим.

Символ называется скрытым другим символом в некотором пакете, когда первый символ был бы доступен через наследование, если бы не присутствие второго символа. Для импортирования символа без ошибки скрытия, используйте функцию shadowing-import. Она вставляет символ в указанный пакет, как внутренний символ, вне зависимости от того, происходит ли скрытие другого символа с тем же именем. Если другой символ с тем же именем присутствовал в пакете, тогда этот символ сначала удаляется из пакета unintern. Новый символ добавляется в список скрывающих символов FIXME. shadowing-import должна использоваться аккуратно. Она изменяет состояние системы пакетов так, что правила согласованности могут перестать работать.

Второй способ предоставляется функцией use-package. Эта функция делает так, что пакет наследует все внешние символы некоторого другого пакета. Эти символы становятся доступными, как внутренние символы, используемого пакета. То есть, на них можно ссылаться без указания пакета внутри текущего пакета, но они не становятся доступными в других пакетах, использующих данный. Следует отметить, что use-package, в отличие от import, не делает новые символы родственными в текущем пакете, а делает их только доступными с помощью наследования связи символов с именами. use-package проверяет конфликты имен между импортируемыми и уже доступными символами в импортирующем пакете. Это подробнее описано в разделе 11.5.

Обычно пользователь, по-умолчанию работая в пакете common-lisp-user, будет загружать ряд пакетов в Lisp систему для предоставления расширенного рабочего окружения, и затем вызывать use-package для каждого из этих пакетов для простого доступа к их внешним символами. unuse-package производит обратные действия относительно use-package. Внешние символы используемые пакетом перестают наследоваться. Однако, любые импортированные символы, остаются доступными.

Не существует способа наследовать внутренние символы другого пакета. Для ссылки на внутренний символ, пользователь должен поменять домашний пакет для данного символа на текущий, или использовать полное имя (вместе с пакетом), или импортировать этот символ в текущий пакет.

Различие между внешними и внутренними символами прежде всего означает скрытие имён, так чтобы одна программа не могла использовать пространство имён другой программы.

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

Функция export принимает символ, который доступен в некотором указанном пакете (напрямую или унаследован из другого пакета) и делает его внешним символом этого пакета. Если символ уже доступен как внешний, export ничего не делает. Если символ представлен, как внутренний родственный символ, его статус просто меняется на внешний. Если он доступен, как внутренний символ, полученный с помощью use-package, символ сначала импортируется в пакет, а затем делается внешним. (После этого символ становиться импортированным в пакет и остаётся в нем, вне зависимости от того будет ли использована unuse-package или нет). Если символ вообще недоступен в указанном пакете, то сигнализируется ошибка с возможностью решить проблему, а, именно, какой символ должен быть импортирован.

Функция unexport откатывает ошибочное экспортирование символа. Она работает только для символов напрямую представленных в текущем пакете, меняя их свойство на «внутреннее». Если unexport получает символ, который в текущем пакете уже является внутренним, она ничего не делает. Если получает вообще недоступный символ, то сигнализирует ошибку.