14.4 包 (Packages)

一个包是一个将名字映对到符号的 Lisp 对象。当前的包总是存在全局变量 *package* 里。当 Common Lisp 启动时,当前的包会是 *common-lisp-user* ,通常称为用户包 (user package)。函数 package-name 返回包的名字,而 find-package 返回一个给定名称的包:

  1. > (package-name *package*)
  2. "COMMON-LISP-USER"
  3. > (find-package "COMMON-LISP-USER")
  4. #<Package "COMMON-LISP-USER" 4CD15E>

通常一个符号在读入时就被 interned 至当前的包里面了。函数 symbol-package 接受一个符号并返回该符号被 interned 的包。

  1. (symbol-package 'sym)
  2. #<Package "COMMON-LISP-USER" 4CD15E>

有趣的是,这个表达式返回它该返回的值,因为表达式在可以被求值前必须先被读入,而读取这个表达式导致 sym 被 interned。为了之后的用途,让我们给 sym 一个值:

  1. > (setf sym 99)
  2. 99

现在我们可以创建及切换至一个新的包:

  1. > (setf *package* (make-package 'mine
  2. :use '(common-lisp)))
  3. #<Package "MINE" 63390E>

现在应该会听到诡异的背景音乐,因为我们来到一个不一样的世界了: 在这里 sym 不再是本来的 sym 了。

  1. MINE> sym
  2. Error: SYM has no value

为什么会这样?因为上面我们设为 99 的 symmine 里的 sym 是两个不同的符号。 [2] 要在用户包之外参照到原来的 sym ,我们必须把包的名字加上两个冒号作为前缀:

  1. MINE> common-lisp-user::sym
  2. 99

所以有着相同打印名称的不同符号能够在不同的包内共存。可以有一个 symcommon-lisp-user 包,而另一个 symmine 包,而他们会是不一样的符号。这就是包存在的意义。如果你在分开的包内写你的程序,你大可放心选择函数与变量的名字,而不用担心某人使用了同样的名字。即便是他们使用了同样的名字,也不会是相同的符号。

包也提供了信息隐藏的手段。程序应通过函数与变量的名字来参照它们。如果你不让一个名字在你的包之外可见的话,那么另一个包中的代码就无法使用或者修改这个名字所参照的对象。

通常使用两个冒号作为包的前缀也是很差的风格。这么做你就违反了包本应提供的模块性。如果你不得不使用一个双冒号来参照到一个符号,这是因为某人根本不想让你用。

通常我们应该只参照被输出 ( exported )的符号。如果我们回到用户包里,并输出一个被 interned 的符号,

  1. MINE> (in-package common-lisp-user)
  2. #<Package "COMMON-LISP-USER" 4CD15E>
  3. > (export 'bar)
  4. T
  5. > (setf bar 5)
  6. 5

我们使这个符号对于其它的包是可视的。现在当我们回到 mine ,我们可以仅使用单冒号来参照到 bar ,因为他是一个公开可用的名字:

  1. > (in-package mine)
  2. #<Package "MINE" 63390E>
  3. MINE> common-lisp-user:bar
  4. 5

通过把 bar 输入 ( import )至 mine 包,我们就能进一步让 mineuser 包可以共享 bar 这个符号:

  1. MINE> (import 'common-lisp-user:bar)
  2. T
  3. MINE> bar
  4. 5

在输入 bar 之后,我们根本不需要用任何包的限定符 (package qualifier),就能参照它了。这两个包现在共享了同样的符号;不可能会有一个独立的 mine:bar 了。

要是已经有一个了怎么办?在这种情况下, import 调用会产生一个错误,如下面我们试着输入 sym 时便知:

  1. MINE> (import 'common-lisp-user::sym)
  2. Error: SYM is already present in MINE.

在此之前,当我们试着在 mine 包里对 sym 进行了一次不成功的求值,我们使 sym 被 interned 至 mine 包里。而因为它没有值,所以产生了一个错误,但输入符号名的后果就是使这个符号被 intern 进这个包。所以现在当我们试着输入 symmine 包里,已经有一个相同名称的符号了。

另一个方法来获得别的包内符号的存取权是使用( use )它:

  1. MINE> (use-package 'common-lisp-user)
  2. T

现在所有由用户包 (译注: common-lisp-user 包)所输出的符号,可以不需要使用任何限定符在 mine 包里使用。(如果 sym 已经被用户包输出了,这个调用也会产生一个错误。)

含有自带操作符及变量名字的包叫做 common-lisp 。由于我们将这个包的名字在创建 mine 包时作为 make-package:use 参数,所有的 Common Lisp 自带的名字在 mine 里都是可视的:

  1. MINE> #'cons
  2. #<Compiled-Function CONS 462A3E>

在编译后的代码中, 通常不会像这样在顶层进行包的操作。更常见的是包的调用会包含在源文件里。通常,只要把 in-packagedefpackage 放在源文件的开头就可以了,正如 137 页所示。

这种由包所提供的模块性实际上有点奇怪。我们不是对象的模块 (modules),而是名字的模块。

每一个使用了 common-lisp 的包,都可以存取 cons ,因为 common-lisp 包里有一个叫这个名字的函数。但这会导致一个名字为 cons 的变量也会在每个使用了 common-lisp 包里是可视的。如果包使你困惑,这就是主要的原因;因为包不是基于对象而是基于名字。