为什么使用defpackage导致NAME-CONFLICT?

时间:2014-02-24 22:26:14

标签: common-lisp sbcl slime

所以我正在使用Project Euler练习lisp,我正在将一些小实用程序函数收集到一个单独的文件中以减少重复,我希望它可能会变得非常庞大,所以我继续前进并制定了一个包定义。这是该文件的精简版本,仍然说明了我的问题:

(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))

当我尝试编译并加载THAT文件时,我收到如下错误:

“USE-PACKAGE#导致以下符号之间的#中的名称冲突:EULER-UTILS:DIVISIBLE-BY,COMMON-LISP-USER :: DIVISIBLE-BY [NAME-CONFLICT类型的条件]”

为什么这个符号出现在COMMON-LISP-USER包中,如何阻止它?

2 个答案:

答案 0 :(得分:6)

compile-file读取文件中的所有表单,并仅执行扩展为相应eval-when表达式的表单。在上面的示例中,只有在读取了引用文件(在cl-user中实现其所有符号)之后才会加载util.lisp文件。 use-package同样是一个简单的函数调用,直到加载时才进行评估,并且当您要求它使用当前包中可访问的相同名称创建两个不同的符号时。

一种选择是将loaduse-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")自动加载它及其依赖项。

答案 1 :(得分:0)

注意:我写这个答案是为了回应我对这个问题的初步理解,我认为这个图像在某些先前的代码中仍然是“脏”的。这是这些症状的常见病例。然而,这种情况源于use-package的影响不是在编译时发生,而是在加载时发生的。因此,当我load下面的文件时,没有问题,但在将其编译为OP时,会引用cl-user::divisble-by。我在这里留下这个答案,因为有这个问题的人仍然可能会找到它,但这与OP所遇到的问题并不完全相同。

您的代码:

我已在util.lispfile2.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))

重现问题

这个问题很容易重现:如果你以某种方式将名为"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。使用新鲜的Lisp,你的代码加载得很好:

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

233168