14.4 包 (Packages)
一个包是一个将名字映对到符号的 Lisp 对象。当前的包总是存在全局变量 *package*
里。当 Common Lisp 启动时,当前的包会是 *common-lisp-user*
,通常称为用户包 (user package)。函数 package-name
返回包的名字,而 find-package
返回一个给定名称的包:
> (package-name *package*)
"COMMON-LISP-USER"
> (find-package "COMMON-LISP-USER")
#<Package "COMMON-LISP-USER" 4CD15E>
通常一个符号在读入时就被 interned 至当前的包里面了。函数 symbol-package
接受一个符号并返回该符号被 interned 的包。
(symbol-package 'sym)
#<Package "COMMON-LISP-USER" 4CD15E>
有趣的是,这个表达式返回它该返回的值,因为表达式在可以被求值前必须先被读入,而读取这个表达式导致 sym
被 interned。为了之后的用途,让我们给 sym
一个值:
> (setf sym 99)
99
现在我们可以创建及切换至一个新的包:
> (setf *package* (make-package 'mine
:use '(common-lisp)))
#<Package "MINE" 63390E>
现在应该会听到诡异的背景音乐,因为我们来到一个不一样的世界了: 在这里 sym
不再是本来的 sym
了。
MINE> sym
Error: SYM has no value
为什么会这样?因为上面我们设为 99 的 sym
与 mine
里的 sym
是两个不同的符号。 [2] 要在用户包之外参照到原来的 sym
,我们必须把包的名字加上两个冒号作为前缀:
MINE> common-lisp-user::sym
99
所以有着相同打印名称的不同符号能够在不同的包内共存。可以有一个 sym
在 common-lisp-user
包,而另一个 sym
在 mine
包,而他们会是不一样的符号。这就是包存在的意义。如果你在分开的包内写你的程序,你大可放心选择函数与变量的名字,而不用担心某人使用了同样的名字。即便是他们使用了同样的名字,也不会是相同的符号。
包也提供了信息隐藏的手段。程序应通过函数与变量的名字来参照它们。如果你不让一个名字在你的包之外可见的话,那么另一个包中的代码就无法使用或者修改这个名字所参照的对象。
通常使用两个冒号作为包的前缀也是很差的风格。这么做你就违反了包本应提供的模块性。如果你不得不使用一个双冒号来参照到一个符号,这是因为某人根本不想让你用。
通常我们应该只参照被输出 ( exported )的符号。如果我们回到用户包里,并输出一个被 interned 的符号,
MINE> (in-package common-lisp-user)
#<Package "COMMON-LISP-USER" 4CD15E>
> (export 'bar)
T
> (setf bar 5)
5
我们使这个符号对于其它的包是可视的。现在当我们回到 mine
,我们可以仅使用单冒号来参照到 bar
,因为他是一个公开可用的名字:
> (in-package mine)
#<Package "MINE" 63390E>
MINE> common-lisp-user:bar
5
通过把 bar
输入 ( import
)至 mine
包,我们就能进一步让 mine
和 user
包可以共享 bar
这个符号:
MINE> (import 'common-lisp-user:bar)
T
MINE> bar
5
在输入 bar
之后,我们根本不需要用任何包的限定符 (package qualifier),就能参照它了。这两个包现在共享了同样的符号;不可能会有一个独立的 mine:bar
了。
要是已经有一个了怎么办?在这种情况下, import
调用会产生一个错误,如下面我们试着输入 sym
时便知:
MINE> (import 'common-lisp-user::sym)
Error: SYM is already present in MINE.
在此之前,当我们试着在 mine
包里对 sym
进行了一次不成功的求值,我们使 sym
被 interned 至 mine
包里。而因为它没有值,所以产生了一个错误,但输入符号名的后果就是使这个符号被 intern 进这个包。所以现在当我们试着输入 sym
至 mine
包里,已经有一个相同名称的符号了。
另一个方法来获得别的包内符号的存取权是使用( use
)它:
MINE> (use-package 'common-lisp-user)
T
现在所有由用户包 (译注: common-lisp-user 包)所输出的符号,可以不需要使用任何限定符在 mine
包里使用。(如果 sym
已经被用户包输出了,这个调用也会产生一个错误。)
含有自带操作符及变量名字的包叫做 common-lisp
。由于我们将这个包的名字在创建 mine
包时作为 make-package
的 :use
参数,所有的 Common Lisp 自带的名字在 mine
里都是可视的:
MINE> #'cons
#<Compiled-Function CONS 462A3E>
在编译后的代码中, 通常不会像这样在顶层进行包的操作。更常见的是包的调用会包含在源文件里。通常,只要把 in-package
和 defpackage
放在源文件的开头就可以了,正如 137 页所示。
这种由包所提供的模块性实际上有点奇怪。我们不是对象的模块 (modules),而是名字的模块。
每一个使用了 common-lisp
的包,都可以存取 cons
,因为 common-lisp
包里有一个叫这个名字的函数。但这会导致一个名字为 cons
的变量也会在每个使用了 common-lisp
包里是可视的。如果包使你困惑,这就是主要的原因;因为包不是基于对象而是基于名字。