Почему использование defpackage приводит к КОНФЛИКТУ ИМЕНИ?

Итак, я практикую лиспа с Project Euler, и я собираю небольшие служебные функции в отдельный файл, чтобы уменьшить дублирование, и я ожидаю, что он может стать довольно большим, поэтому я пошел дальше и сделал определение пакета. Вот сокращенная версия файла, которая все еще иллюстрирует мою проблему:

(defpackage :euler-util
    (:use :common-lisp)
    (:export :divisible-by))

(in-package :euler-util)

(defun divisible-by (x y)
    (equal 0 (mod x y)))

Я могу скомпилировать и загрузить (C-c C-k) этот файл в SLIME без каких-либо предупреждений или ошибок. Теперь, когда я собираюсь использовать его в другом файле, я делаю следующее:

(load "util.lisp")
(use-package :euler-util)

(defun euler-1 ()
    (loop for i from 3 to 999 when (or (divisible-by i 3) (divisible-by i 5)) sum i))

Когда я пытаюсь скомпилировать и загрузить ЭТОТ файл, я получаю такую ​​ошибку:

"USE-PACKAGE # вызывает конфликты имен в # между следующими символами: EULER-UTILS: DIVISIBLE-BY, COMMON-LISP-USER :: DIVISIBLE-BY [Условие типа NAME-CONFLICT]"

Почему этот символ появляется в пакете COMMON-LISP-USER и как его остановить?


person Lee Crabtree    schedule 24.02.2014    source источник
comment
Можно просто изменить имя вашей divisible-by функции.   -  person Robert Harvey    schedule 25.02.2014
comment
Вы оценивали какой-либо из этих кодов заранее? Похоже, вы не загружаете этот код в новый Лисп.   -  person Joshua Taylor    schedule 25.02.2014
comment
@RobertHarvey Но нет CL: DIVISIBLE-BY, с которым можно было бы столкнуться; это CL-USER: DIVISIBLE-BY, что предполагает, что часть этого кода уже была прочитана, а символы интернированы в CL-USER.   -  person Joshua Taylor    schedule 25.02.2014
comment
О да. Окружающая среда явно не очищается. stackoverflow.com/questions/3725595/reset-state-in -common-lisp   -  person Robert Harvey    schedule 25.02.2014
comment
@RobertHarvey Это частая причина этой проблемы, но проблема не в этом. Xach поймал это правильно; здесь файл с use-package компилируется, но эффекты use-package (функции, а не макроса) проявляются во время загрузки, а не во время компиляции. Таким образом, скомпилированный файл ссылается на cl-user::divisible-by, и возникает конфликт имен после определения другого пакета.   -  person Joshua Taylor    schedule 25.02.2014


Ответы (2)


compile-file читает все формы в файле и выполняет только те формы, которые раскрываются в соответствующие выражения eval-when. В приведенном выше примере файл util.lisp не загружается до тех пор, пока ссылочный файл уже не будет прочитан (интернирование всех его символов в cl-user). use-package - аналогичный вызов простой функции, которая не оценивается до времени загрузки, и именно тогда вы просите его сделать два разных символа с одним и тем же именем доступными в текущем пакете.

Один из вариантов - поместить операторы load и use-package в форму eval-when, например

(eval-when (:compile-toplevel :load-toplevel :execute)
  (load "util.lisp")
  (use-package :euler))

Я думаю, что было бы лучше определить новый пакет для вашего проекта и поместить его в начало файла:

(defpackage #:euler
  (:use #:cl #:euler-util))

(in-package #:euler)

Эти операторы автоматически оцениваются во время компиляции, поэтому eval-when не требуется.

Опытные авторы CL склонны избегать этой проблемы, определяя пакеты в определенном порядке, помещая in-package формы в каждый исходный файл и используя средство определения системы для компиляции и загрузки файлов в правильном порядке.

Я использую ASDF как средство определения системы. Простой системный файл может выглядеть так:

;;;; my-project.asd

(asdf:defsystem my-project
  :serial t
  :components ((:file "util")
               (:file "my-project")))

Если вы поместите этот файл в место, о котором знает ASDF, (asdf:load-system "my-project") скомпилирует и загрузит файлы в указанном порядке.

Если вы используете Quicklisp, один простой способ сделать это - поместить каталог проекта в ~ / quicklisp / local-projects /, а затем использовать (ql:quickload "my-project") для автоматической загрузки его и его зависимостей.

person Xach    schedule 24.02.2014
comment
Совершенно верно. Я не уловил вызов use-package и тот факт, что @Lee компилирует файл, до тех пор, пока от него не появятся новые комментарии. Симптомы очень похожи на обычную проблему с грязным изображением, но причина здесь несколько иная. Молодец, +1. - person Joshua Taylor; 25.02.2014

Примечание: я написал этот ответ в ответ на мое первоначальное понимание проблемы, когда я подумал, что изображение все еще было «грязным» из-за какого-то предыдущего кода. Это частый случай подобных симптомов. Этот случай, однако, возникает из-за того, что эффекты use-package проявляются не во время компиляции, а во время загрузки. Таким образом, когда я load файл ниже, проблем нет, но при компиляции его как OP есть ссылка на cl-user::divisble-by. Я оставляю этот ответ здесь, потому что люди с этой проблемой, скорее всего, ее найдут, но это не совсем та же проблема, что и у OP.

Ваш код:

Я сделал локальные копии вашего кода в util.lisp и file2.lisp:

$ cat util.lisp 
(defpackage :euler-util
    (:use :common-lisp)
    (:export :divisible-by))

(in-package :euler-util)

(defun divisible-by (x y)
    (equal 0 (mod x y)))
$ cat file2.lisp 
(load "util.lisp")
(use-package :euler-util)

(defun euler-1 ()
    (loop for i from 3 to 999 when (or (divisible-by i 3) (divisible-by i 5)) sum i))

Воспроизведение проблемы

Эту проблему легко воспроизвести: если вы каким-то образом внедрили символ a с именем "DIVISIBLE-BY" в пакет CL-USER, а затем попытаетесь использовать пакет, который экспортирует символ с тем же именем, у вас возникнут проблемы:

$ sbcl --noinform
* (defun divisible-by (&rest args) (declare (ignore args)))

DIVISIBLE-BY
* (load "file2")

debugger invoked on a NAME-CONFLICT in thread #<THREAD "initial thread" RUNNING
                                                 {1002979041}>:
  USE-PACKAGE #<PACKAGE "EULER-UTIL"> causes name-conflicts in
  #<PACKAGE "COMMON-LISP-USER"> between the following symbols:
    EULER-UTIL:DIVISIBLE-BY, COMMON-LISP-USER::DIVISIBLE-BY
See also:
  The ANSI Standard, Section 11.1.1.2.5

Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [RESOLVE-CONFLICT] Resolve conflict.
  1: [RETRY           ] Retry EVAL of current toplevel form.
  2: [CONTINUE        ] Ignore error and continue loading file "…/file2.lisp".
  3: [ABORT           ] Abort loading file "…/file2.lisp".
  4:                    Exit debugger, returning to top level.

(NAME-CONFLICT
 #<PACKAGE "COMMON-LISP-USER">
 USE-PACKAGE
 #<PACKAGE "EULER-UTIL">
 EULER-UTIL:DIVISIBLE-BY
 DIVISIBLE-BY)

Разрешение конфликта

Если это произойдет, вы можете решить эту проблему вручную. Здесь я использую SBCL в командной строке, но вы должны (я надеюсь) получить аналогичные параметры отладчика в SLIME. Для краткости я много вставляю в командную строку. Здесь важно следующее: --noinform предотвращает печать баннера; --eval "'divisible-by" проверяет наличие символа cl-user:divisible-by; и --load file2.lisp загружает ваш файл. То есть мы воссоздали проблему в командной строке, чтобы сосредоточиться на ее устранении.

$ sbcl --noinform --eval "'divisible-by" --load file2.lisp 

debugger invoked on a NAME-CONFLICT in thread #<THREAD "initial thread" RUNNING
                                                 {1002979311}>:
  USE-PACKAGE #<PACKAGE "EULER-UTIL"> causes name-conflicts in
  #<PACKAGE "COMMON-LISP-USER"> between the following symbols:
    EULER-UTIL:DIVISIBLE-BY, COMMON-LISP-USER::DIVISIBLE-BY
See also:
  The ANSI Standard, Section 11.1.1.2.5

Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [RESOLVE-CONFLICT] Resolve conflict.
  1: [RETRY           ] Retry EVAL of current toplevel form.
  2: [CONTINUE        ] Ignore error and continue loading file "/home/taylorj/tmp/package-issue/file2.lisp".
  3: [ABORT           ] Abort loading file "/home/taylorj/tmp/package-issue/file2.lisp".
  4:                    Ignore runtime option --load "file2.lisp".
  5:                    Skip rest of --eval and --load options.
  6:                    Skip to toplevel READ/EVAL/PRINT loop.
  7: [QUIT            ] Quit SBCL (calling #'QUIT, killing the process).

(NAME-CONFLICT
 #<PACKAGE "COMMON-LISP-USER">
 USE-PACKAGE
 #<PACKAGE "EULER-UTIL">
 EULER-UTIL:DIVISIBLE-BY
 DIVISIBLE-BY)

Теперь вы можете ввести 0, если хотите разрешить конфликт:

0] 0

Select a symbol to be made accessible in package COMMON-LISP-USER:
  1. EULER-UTIL:DIVISIBLE-BY
  2. COMMON-LISP-USER::DIVISIBLE-BY

Enter an integer (between 1 and 2): 1

Сделав euler-util:divisible-by символ доступным в cl-user, ваш код будет работать так, как вы ожидаете:

* (euler-1)

233168

Со свежим изображением проблем нет.

Хотя вы могли не вызвать проблему таким же образом, как я сделал выше, вероятно, что-то похожее. Похоже, вы загружаете код в не свежей среде (например, где вы уже ввели код, который заставил читателя интернировать "DIVISIBLE-BY" в CL-USER пакет. Со свежим Лиспом ваш код загружается нормально:

$ sbcl --load file2.lisp --noinform 
* (euler-1)

233168
person Joshua Taylor    schedule 24.02.2014
comment
Все продолжают упоминать, что я, кажется, загружаю код в несвежее изображение, но если эта фраза не означает чего-то другого, кроме того, что я думаю (т.е. состояния дел сразу после выполнения M-x slime), Я загружаю новое изображение. - person Lee Crabtree; 25.02.2014
comment
@LeeCrabtree Что ж, вероятно, это означает то, что вы думаете, но могут быть неожиданные способы запятнать это свежее изображение. Когда читатель читает форму, он должен интернировать символы, которые он читает, и делает это на основе явных префиксов пакетов (например, foo::bar) и текущего значения *package*. Обычно проблема такого типа возникает, если вы набираете divisible-by в REPL, когда читатель вставляет divisible-by в пакет cl-user. Затем, когда вы пытаетесь (use-package :euler-util) экспортировать символ с тем же именем, возникает конфликт. Если ты - person Joshua Taylor; 25.02.2014
comment
@LeeCrabtree не делает что-то подобное, тогда что-то похожее, вероятно, происходит, но более хитрым способом. Например, вы можете C-x C-e в буфере Лиспа и оценить некоторый код. У вас может быть что-то в файле инициализации Slime, которое что-то делает с divisible-by; есть много способов, которыми это могло произойти. Если вы запустите Emacs, выполните M-x slime, а затем с типом REPL (load "file2"), у вас все еще есть проблема с тем кодом, который вы нам показали? - person Joshua Taylor; 25.02.2014
comment
@LeeCrabtree На самом деле, если присмотреться к вашему коду, мне интересно, не в этом ли проблема: use-package - это функция, которую компилятор не обрабатывает иначе, чем любая другая функция. Ваш файл, в котором фигурирует use-package, не содержит формы in-package. Если вы компилируете этот файл, а не просто загружаете его, use-package не повлияет на вызов divisible-by, а поскольку use-package не оценивается для компиляции, вы получите cl-user::divisble-by. Проблема здесь в том, что вы компилируете свой file2; мы все его загружали. - person Joshua Taylor; 25.02.2014
comment
И теперь, после того, как я написал весь этот комментарий и собирался отредактировать свой ответ, я понимаю, что @Xach уже поймал, что он ответил должным образом. Его ответ правильный. - person Joshua Taylor; 25.02.2014