11.5 Конфликты имён

Основное неизменяемое правило системы пакетов состоит в том, что внутри одного пакета каждое имя может ссылаться не более, чем на один символ. Конфликтом имён называется ситуация, когда существует более одного подходящего символа и не ясно какой из них должен быть выбран. Если система не будет следовать одному методу выбора, то правило чтения-чтения будет нарушено. Например, некоторая программа или данные должны быть прочитаны при некоторой связи имени с символом. Если связь изменяется в другой символ, и затем прочитывается дополнительная программа или символ, то две программы не будут получать доступ к одному и тому же символу, даже если используют одинаковое имя. Даже если система всегда выбирает один метод выбора символа, конфликт имён приводит к связи имени с символом отличной от той, что ожидает пользователь, приводя к некорректному выполнению программы. Таким образом, в любом случае возникновения конфликта имён сигнализируется ошибка. Пользователь может указать системе пакетов, как разрешить конфликт, и продолжить выполнение.

Может быть ситуация, когда один символ может быть доступен для пакета более чем одним способом. Например, символ может быть внешним символов более чем одного используемого пакета, или символ может быть напрямую представлен в пакете и также унаследован из другого пакета. В таких случаях конфликта имён не возникает. Один и тот же символ не может конфликтовать сам с собой. Конфликты имён возникают только между разными символами с одинаковыми именами.

Создатель пакета может заранее указать системе, как разрешать конфликт имён, используя скрытие. Каждый пакет имеет список скрывающих символов. Скрывающий символ имеет преимущество перед любым другим символом с тем же именем. Разрешение конфликта с участием скрывающего символа всегда происходит в пользу последнего без сигнализирования об ошибке (за исключение одного использования import описанного ниже). Функции shadow и shadowing-import могут использоваться для декларации скрывающих символов.

Конфликты имён обнаруживаются, когда они становятся возможными, то есть, когда изменяется структура пакета. Нет смысла проверять конфликты в каждом процессе поиска имени для символа.

Функции use-package, import и export проверяют возникновение конфликтов имён. use-package делает внешние символы пакета доступными для использования в другом пакете. Каждый из этих символов проверяется на конфликт имён с уже доступным в пакете символом. import добавляет один символ как внутренний символ пакета, проверяя на конфликты имён с родственными или доступными символами. import сигнализирует об ошибке конфликта имён, даже если конфликт произошёл со скрывающим символом. Это объясняется том, что пользователь дал два явных и взаимоисключающих указания. export делает один символ доступным для всех пакетов, которые используют пакет, из которого символ был экспортирован. Все эти пакеты проверяются на конфликты имён: (export s p) делает (find-symbol (symbol-name s q) для каждого пакета q в (package-used-by-list p). Следует отметить, что в обычно при выполнении export в течение первоначального определения пакета, результат package-used-by-list будет nil и проверка конфликта имён не будет занимать много времени.

Функция intern, которая является одной из часто используемых Lisp’овыми считывателем для поиска имён символов, не нуждается в проверке конфликта имён, потому что она никогда не создаёт новые символы, если символ с указанным именем уже доступен.

shadow и shadowing-import никогда не сигнализируют ошибку конфликта имён, потому что пользователь, вызывая их, указывает, как возможный конфликт будет разрешён. shadow проверяет конфликт имён при условии, что другой существующий символ с указанным именем доступен и, если так, является ли родственным или унаследованным. В последнем случае, новый символ создаётся, чтобы скрыть старый. shadowing-import проверяет конфликт имён при условии, что другой существующий символ с указанным именем доступен и, если так, он скрывается новым символом, что означает, что он должен быть удалён из пакета, если он был представлен в пакете напрямую.

unuse-package, unexport и unintern (когда символ, будучи удаляемым, не является скрывающим символом) не требуют проверки конфликтов имён, потому что они просто удаляют символы из пакета. Они не делают доступными какие-либо новые символы.

Указание скрывающего символа в функцию unintern может возобновлять конфликт имён, который был ранее разрешён с помощью затенения. Если пакет A использует пакеты B и C, A содержит скрывающий символ x, и B, и C, каждый содержит внешний символ с именем x, тогда при удалении скрывающего символа x из A будет обнаружен конфликт между b:x и c:x, если эти два символа различны. В этом случае unintern будет сигнализировать ошибку.

Прерывание ошибки конфликта имён оставляет оригинальный символ доступным. Функции для пакетов всегда сигнализируют ошибки конфликтов имён перед любыми изменениями в структуре пакетов. Однако когда изменяются много символов за раз, например, когда export получила список символов, реализация может обрабатывать каждое изменение по отдельности, таким образом прерывание ошибки, возникшей для второго символа, не приведёт к отмене результатов, выполненных для первого символа. Однако, прерывание из ошибки конфликта имён при использовании export для одного символа не оставит этот символ доступным для одних пакетов и недоступным для других. В отношении к каждому обрабатываемому символу export ведёт себя как атомарная операция.

Продолжение из ошибки конфликта имён должно предлагать пользователю возможность разрешить конфликт имён в пользу какого-либо из кандидатов. Структура пакета должна измениться в соответствие с разрешением конфликта имён, с помощью shadowing-import, unintern или unexport.

Конфликт имён в use-package между родственными символом в использующем пакете и внешними символом в используемом пакете может быть разрешён в пользу первого символа с помощью создания скрывающего символа, или в пользу второго символа с помощью удаления первого символа из использующего пакета. Последний способ опасен тем, что если символ для удаления является внешним символом использующего пакета, он перестанет быть внешним символом.

Конфликт имён в use-package между двумя внешними символами, унаследованными в использующем пакете из других пакетов, может быть разрешён в пользу одного из символов, с помощью импортирования его в использующий пакет и превращения его в затеняющий символ.

Конфликт имён в export между символом для экспорта и символом уже присутствующем в пакете, который будет наследовать свеже экспортирующийся символ, может быть разрешён в пользу экспортируемого символа с помощью удаления другого символа, или в пользу уже присутствующего символа, превращением его в скрывающий.

Конфликт имён в export или unintern из-за пакета, наследующего два различных символа с одинаковым именем из двух разных пакетов, может быть разрешён в пользу одного из символов с помощью импортирования его в использующий пакет и превращении его в скрывающий, также как и при использовании use-package.

Конфликт имён в import между символом для импортирования и символом унаследованным из некоторого другого пакета может быть разрешён в пользу импортируемого символа превращением его в скрывающий символ или в пользу уже доступного символа с помощью отмены import. Конфликт имён в import с символом уже присутствующем в пакете может быть разрешён с помощью удаления этого символа, или отмены import.

Хороший стиль пользовательского интерфейса диктует то, что use-package и export, которые могут вызывать за раз много конфликтов имён, сначала проверяют все конфликты имён перед тем, как предоставить любой из них пользователю. Пользователь может выбрать разрешать ли конфликт для всех сразу или по отдельности. Последний способ трудоёмок, но позволяет разрешить каждый конфликт отдельным способом.

Реализации могут предлагать другие пути решения конфликтов имён. Например, если конфликтующие символы, не используются для объектов, а только для имен функций, они могут быть «слиты» с помощью размещения определения функции в обоих символах. Ссылка на любой символ в целях вызова функции будет эквивалентна. Похожая операция «слияния» может быть сделана для значений переменных или для вещей, сохранённых в списке свойств. В Lisp Machine Lisp’е, например, можно было также выдвинуть (forward) значение, функцию и ячейки свойств так, что изменения в одном символе приводили к изменению в другом. Некоторые другие реализации позволяют сделать это для ячейки значения, но на для ячейки списка свойств. Тогда пользователь может знать является ли этот метод разрешения конфликтов имён адекватным, потому что метод будет работать только, если использование двух не-eq символов с одинаковым именем не будет препятствовать корректной работе программы. Значение стоимости слияния символов, как разрешении конфликта имён, в том, что оно может избегать необходимости выбрасывать весь Lisp’овый мир, исправлять формы определения пакета, который вызвал ошибку и начинать с нуля.