Common Lisp包系统彻底指南

原文请在此处下载:http://download.csdn.net/detail/firstblood2008/8550683 html

1. 简介

当与多名程序员编写大型工程的时候,两个不一样的程序员常常会因不一样的意图而使用相同的名称。使用命名空间惯例可能会解决这个问题,例如,Bob在他全部的变量名前都加上"BOB-"前缀,Jane在她全部的变量名前加上"JANE-"前缀。事实上,这就是Scheme对付这个问题的办法(视状况也能够说是其应付失败的地方)。 程序员

Common Lisp提供了一种叫作包的语言机制来分离命名空间,这里有一个包如何工做的例子: 数据结构

CL-USER> (make-package :bob :use '(common-lisp cl-user))
#<PACKAGE "BOB">
CL-USER> (make-package :jane :use '(common-lisp cl-user))
#<PACKAGE "JANE">

CL-USER> (in-package bob)
#<PACKAGE "BOB">
BOB> (defun foo () "This is Bob's foo")
FOO
BOB> (in-package JANE)
#<PACKAGE "JANE">

JANE> (defun foo () "This is jane's foo")
FOO
JANE> (foo)
"This is jane's foo"
JANE> (in-package :bob)
#<PACKAGE "BOB">
BOB> (foo)
"This is Bob's foo"
BOB>
Bob和Jane没人都有一个名为FOO的函数,它们的行不有些不一样,彼此之间不会冲突。

要是Bob想要使用Jane编写的foo该怎么办呢?这里有几种方法来作到。一种就是使用特殊的语法来表示不一样的包将会被使用: 函数

BOB> (in-package bob)
#<PACKAGE "BOB">
BOB> (jane::foo)
"This is jane's foo"
BOB>
另外一种方法就是将你想要使用的符号导入到你的包内。固然,他不想将jane的FOO函数导入,缘由是它会与本身的FOO函数相冲突。可是,若是Jane有一个BAR函数,他想要经过键入(BAZ )的方式来代替(JANE::BAR),他能够这样作:

BOB> (in-package jane)
#<PACKAGE "JANE">
JANE> (defun baz () "This is Jane's foo")
BAZ
JANE> (in-package bob)
#<PACKAGE "BOB">
BOB> (import 'jane::baz)
T
BOB> (baz)
"This is Jane's foo"
哎呀,事情常常不会太顺利:

BOB> (in-package jane)
#<PACKAGE "JANE">
JANE> (defun bar () "This is Jane's bar")
BAR
JANE> (in-package bob)
#<PACKAGE "BOB">
BOB> (bar) ;;;注:此处是找不到符号

; in: BAR
;     (BOB::BAR)
; 
; caught STYLE-WARNING:
;   undefined function: BAR
; 
; compilation unit finished
;   Undefined function:
;     BAR
;   caught 1 STYLE-WARNING condition
; Evaluation aborted on #<UNDEFINED-FUNCTION BAR {24FEC1E1}>.
BOB> (import 'jane::bar) ;;;注:此处是命名冲突
; Evaluation aborted on #<NAME-CONFLICT {251DE141}>.
BOB>
为了理解为何会发生这个问题,对这个问题该作什么,还有包的其它的微妙与惊喜的表现,理解包其实是干什么的和如何工做是很重要的。例如,理解如下事项是很重要的:即当你键入(import 'jane::foo)时,你导入的是符号JANE::FOO,而不是与该符号相关联的函数,理解这两点的不一样是很重要的,因此咱们不得不对一些基本lisp概念进行复习。

2. 符号、值和REPL

Lisp在REPL(读-求值-打印 循环)里操做。大多数有意思的事情都发生在求值阶段,可是一遇到包这些有意思的事儿在这三个阶段就都会发生了,理解什么时间发生了什么是重要的。尤为是,一些与包相关的处理过程能够在“读取”期改变Lisp系统的状态,这可能依次引出一些使人惊奇的行为(有时候也是使人恼火的),就像前一章里的最后一个例子同样。 测试

包,就是Lisp符号的集合,要理解包你首先要理解符号。符号是一个很是普通的Lisp数据结构,就像列表,数值,字符串等等同样。有一些内建的Lisp函数专门用来建立和管理符号。例如,有一个叫gentemp的函数,它能够建立新的符号: this

CL-USER> (gentemp)
T1
CL-USER> (setq x (gentemp))
T2
CL-USER> (set x 123) ; 注意这里用set,不是setf或setq
123
CL-USER> x
T2
CL-USER> t2
123
CL-USER>
(若是你对SET函数不熟悉的话,并且你不想一下子忘了,那如今正是查阅的好时候。) 注:根据CLHS,set已被遗弃,它是动态做用域的操做符,不适用于改变词法变量的值。

由GENTEMP建立的符号和由键入名字获得的符号,他们在全部方面的表现都很像。 lua

你对由GENTEMP建立的符号名称仅有有限控制,你能够向它传递一个可选的前缀字符串,可是系统也会加一个前缀,你又不得不接受它给你的东西。若是你想使用一个特定名称建立一个符号,你不得不使用一个不一样的函数——MAKE-SYMBOL: spa

CL-USER> (make-symbol "my-symbol")
#:|my-symbol|
CL-USER>
嗯,有点奇怪,那个长相怪怪的"#:"是什么东西?想要理解这个,咱们就不得不深刻地挖掘一下符号的五脏六腑:

CL-USER> (setq symbol1 (make-symbol "MY-SYMBOL"))
#:MY-SYMBOL
CL-USER> (setq symbol2 (make-symbol "MY-SYMBOL"))
#:MY-SYMBOL
CL-USER> (setq symbol3 'my-symbol)
MY-SYMBOL
CL-USER> (setq symbol4 'my-symbol)
MY-SYMBOL
CL-USER> (eq symbol1 symbol2)
NIL
CL-USER> (eq symbol3 symbol4)
T
CL-USER>
就像你看到的,MAKE-SYMBOL能够建立多个不一样的符号,它们有相同的名字;然而读取器给你的那些符号是相同的,这些符号是经过在不一样的时机键入的相同名字。

符号识别的这个属性是很是重要的,这个特性保证了你在一个地方键入的FOO与在其余地方键入的FOO是同一个FOO。若是不是这样的话,你将获得一些很是怪异的结果: .net

CL-USER> (set symbol1 123)
123
CL-USER> (set symbol2 456)
456
CL-USER> (setq code-fragment-1 (list 'print symbol1))
(PRINT #:MY-SYMBOL)
CL-USER> (setq code-fragment-2 (list 'print symbol2))
(PRINT #:MY-SYMBOL)
CL-USER> (eval code-fragment-1)

123 
123
CL-USER> (eval code-fragment-2)

456 
456
CL-USER>
与下边这个对照一下:

CL-USER> (set symbol3 123)
123
CL-USER> (set symbol4 456)
456
CL-USER> (setq code-fragment-3 (list 'print symbol3))
(PRINT MY-SYMBOL)
CL-USER> (setq code-fragment-4 (list 'print symbol4))
(PRINT MY-SYMBOL)
CL-USER> (eval code-fragment-3)

456 
456
CL-USER> (eval code-fragment-4)

456 
456
CL-USER>
符号1-4有相同的名字"MY-SYMBOL",可是符号1与符号2是不一样的符号,而符号3和符号4是相同的符号,这到底是怎么回事儿?好吧,一个明显的不一样就是咱们调用了MAKE-SYMboL函数来建立符号1和符号2,而符号3和4是经过Lisp读取器建立的。与调用MAKE-SYMBOL相比,可能Lisp读取器有一种建立符号的方式。咱们能够测试这个猜想:

CL-USER> (trace make-symbol)
  0: (make-symbol "FOURTH")
  0: make-symbol returned #:fourth
  0: (make-symbol "FORMATTER")
  0: make-symbol returned #:formatter
  0: (make-symbol "FORMAT")
  0: make-symbol returned #:format
  0: (make-symbol "FORCE-OUTPUT")
  0: make-symbol returned #:force-output
(MAKE-SYMBOL)
CL-USER> 'foobaz
  0: (make-symbol "FOURTH")
  0: make-symbol returned #:fourth
  0: (make-symbol "FORMATTER")
  0: make-symbol returned #:formatter
  0: (make-symbol "FORMAT")
  0: make-symbol returned #:format
  0: (make-symbol "FORCE-OUTPUT")
  0: make-symbol returned #:force-output
  0: (make-symbol "FOOBAZ")
  0: make-symbol returned #:foobaz
  0: (make-symbol "FOOBAZ")
  0: make-symbol returned #:foobaz
  0: (make-symbol "FOOBAZ")
  0: make-symbol returned #:foobaz
  0: (make-symbol "FOOBAZ")
  0: make-symbol returned #:foobaz
  0: (make-symbol "FOOBAZ")
  0: make-symbol returned #:foobaz
  0: (make-symbol "FOOBAZ")
  0: make-symbol returned #:foobaz
FOOBAZ
CL-USER>

不,经过调用MAKE-SYMBOL,表面上读取器与咱们建立符号的方式相同;可是,等一下,MAKE-SYMBOL返回的符号的前边里有一个搞笑的#:,可是到读取器读取完毕为止,#:已经消失了,它给了咱们什么? code

经过使用带追踪能力的MAKE-SYMBOL进行第二次尝试相同的试验,咱们能够发现答案:

CL-USER> 'foobaz
FOOBAZ
哈哈,咱们第二次键入FOOBAR,读取器就不调用MAKE-SYMBOL了,因此,读取器显然地保存了它建立的全部的符号的集合,在它建立一个新符号以前,它首先检测该集合里是否已经有一个相同名字的符号,若是已经有了,那么它就返回该符号而不是建立一个,该集合里的这样一个符号成员再丢掉那个神秘的#:前缀。

那个符号的集合就叫作包。

包就是Lisp符号的集合,这些符号有这样一个特性,即该集合里没有两个相同名字的符号。

不幸的是,多多少少那是包简单和直接的最后一个体现,从如今开始事情就变得刺激了。

3. 驻留

(关于词意来历请参看:字符串的驻留

将一个符号放入到包里的动做叫符号驻留。做为包的成员的符号能够说是驻留在那个包中的。那些不是任何包的成员的符号能够说没有被驻留。未驻留符号打印时,将在其前面加一个#:前缀,用来与已驻留符号作区分,这个前缀的做用是提醒你:由于这个符号是另外一个符号,事实上不是你之前见过的相同的那个符号。

如今,这里就是开始让你有那么一点儿困惑的地方。

有一个叫INTERN的Lisp函数,你可能但愿用它来向包里加入一个符号,可是它却没起做用。该函数由一个叫作IMPORT的函数来执行:

CL-USER> 'symbol1
SYMBOL1
CL-USER> symbol1
#:MY-SYMBOL
CL-USER> (import symbol1)
T
CL-USER> symbol1
MY-SYMBOL
CL-USER> (eq symbol1 'my-symbol)
T
CL-USER>
就像你看到的,symbol1已经从一个未驻留符号变成驻留符号,它丢掉了#:前缀,如今它与Lisp读取器生成的MY-SYMBOL符号是EQ等价的。

如今,你可能想经过调用UNIMPORT来撤销IMPORT的影响,可是没有这样的一个函数。(我提醒你那样的事情不多是直接的)为了从包里移除符号,你能够调用UNINTERN:

CL-USER> (unintern symbol1)
T
CL-USER> symbol1
#:MY-SYMBOL
CL-USER> (eq symbol1 'my-symbol)
NIL
CL-USER>
事情又回到了它本来的样子,符号1如今是未驻留的,与读取器给你的符号相比它是不一样的符号,让咱们再把symbol1放回到包里:
CL-USER> (import symbol1)
IMPORT #1=#:MY-SYMBOL causes name-conflicts in
#<PACKAGE "COMMON-LISP-USER"> between the following symbols:
  #1#, COMMON-LISP-USER::MY-SYMBOL
   [Condition of type NAME-CONFLICT]
See also:
  Common Lisp Hyperspec, 11.1.1.2.5 [:section]

Restarts:
 0: [RESOLVE-CONFLICT] Resolve conflict.
 1: [RETRY] Retry SLIME REPL evaluation request.
 2: [*ABORT] Return to SLIME's top level.
 3: [TERMINATE-THREAD] Terminate this thread (#<THREAD "repl-thread" RUNNING {25547F21}>)
...........................
MLGB,怎么回事儿?(在读取任何东西以前试图解决这里发生了什么是个至关好的实践方式,这里有你须要的全部信息。暗示:你本身用MAKE-SYMBOL回溯作下试验。)

这里就是所发生的事情:

CL-USER> (unintern 'my-symbol)
T
CL-USER> (eq symbol1 'my-symbol)
NIL
CL-USER>
当咱们键入(eq symbol1 'my-symbol)时,读取器使符号MY_SYMBOL驻留,因此当咱们试图导入symbol1符号的时候,包里已经存在一个名称相同的符号了。记住,在包里任什么时候间仅有一个相同名称的符号(这是重点所在),包会维护这种不变式,因此当你试图导入symbol1的时候(它的名字也是MY-SYMBOL),包里已经有一个有相同名字的符号了(正是由读取器悄悄地驻留在里边的)。这种状况叫符号冲突,呃,该问题很是常见。

顺便注意一下,上边的MAKE-SYMBOL的追溯输出的东西看起来出如今S表达式(eq symbol1 'my-symbol)里,这是由于做者本人使用的MCL从新排列了屏幕上的文字,目的是反映事件的真实顺序。由于MAKE-SYMBOL是由读取器调用的,而处理字符串"my-symbol"倒是在处理闭合括号以后的,输出看起来就是在那个阶段。若是咱们键入:
(list 'x1 'x2 'x3 'x4),输出的结果多是这样的:

这里,文本格式化被保留下来,目的是有助于表示发生了什么,用户键入的文本是粗体的。(注意大多数Lisp系统都不会作这样的格式化)
#|译者注开始:个人sbcl、ccl并无出现原做者的格式化,个人输出与普通列表无异,见下边的代码:

CL-USER> (list 'x1 'x2 'x3 'x4)
(X1 X2 X3 X4)
CL-USER> (list 'x1
               'x2
               'x3
               'x4)
(X1 X2 X3 X4)
CL-USER>
|# 译者注完毕

有一些更加剧要的函数,咱们将继续并提到这些。SYMBOL-NAME返回符号名称的字符串,FIND-SYMBOL带一个字符串参数,告诉你是否存在一个名字为参数值的符号已经驻留,最后,INTERN定义以下:

(defun intern (name)
           (or (find-symbol name)
               (let ((s (make-symbol name)))
                 (import s)
                 s)))
换句话说,INTERN与SYMBOL-NAME有几分相反的意思,SYMBOL-NAME带一个符号做为参数,返回符号名的字符串;INTERN带一个字符串参数,返回名称为该字符串的符号。INTERN就是READ用来建立符号的那个函数。

4. 哪一个包?

操做包的函数,像FIND-SYMBOL、IMPORT和INTERN,默认状况下对这样一个包进行操做:它是全局变量*PACHAGE*的值,也就是众所周知的当前包。和符号同样,包也有名字,任意两个包都不会有相同的名字。包与符号同样,也都是普通的Lisp数据结构。这里有两个函数PACKAGE-NAME和FIND-PACLAGE,它们与SYMBOL-NAME和FIND-SYMBOL的操做是类似的,固然也有不一样,那个FIND-PACKAGE不带包参数。(这好像是对于包名称有一个全局的元包。)

向咱们看到的,咱们能够经过调用MAKE-PACKAGE来建立包,经过调用IN-PACKAGE来设置当前包。(注意那个IN-PACKAGE不是一个函数而是宏,它不能对参数求值。)

你能够经过使用特殊语法 ':'来获取那些不在当前包的符号,该语法的意思是包名后边跟着两个冒号,再跟符号名。若是你不想经过导入来使用符号,这种方法就很方便了。例如,若是Bob想使用Jane的FOO函数,他只须要键入JANE::FOO。

4.1 主包

Common Lisp视图维持的一个不变式叫作打印-读取一致性特性。该特性表述为,若是你打印一个符号,而后读取那个符号的结果打印形式,该结果是相同的符号,有两点附加说明:

  1. 该特性不适用于未驻留符号
  2. 该操做仅适用于避免有必定的危险的行为

一下子咱们将解释它的意思。

为了保证打印-读取一致性,一些符号须要与他们的包标识符一块儿打印。例如:

JANE> (in-package JANE)
#<PACKAGE "JANE">
JANE> 'foo
FOO
JANE> 'jane::foo
FOO
JANE> (in-package "BOB")
#<PACKAGE "BOB">
BOB> 'foo
FOO
BOB> 'jane::foo
JANE::FOO
BOB> 'bob::foo
FOO
BOB>
明显地,一个危险的行为——容许违反打印-读取一致性原则,就是调用IN-PACKAGE。

如今,考虑到下边的状况:

BOB> (in-package :bob)
#<PACKAGE "BOB">
BOB> (unintern 'foo)
T
BOB> (import 'jane::foo)
T
BOB> (make-package :charlie)
#<PACKAGE "CHARLIE">
BOB> (in-package :charlie)
#<COMMON-LISP:PACKAGE "CHARLIE">
CHARLIE> 'bob::foo
JANE::FOO
CHARLIE>
这里咱们有一个符号FOO,它即在JANE内,用在BOB包内,所以它既能够在以JANE::FOO,又能够以BOB::FOO里获取。当从CHARLIE包里打印符号的时候(该符号并未在CHARLIE驻留),系统如何选择使用哪一种打印形式?

这证实每一个符号都保持与一个叫作主包的单个包进行追踪,主包一般是第一个包,那个符号将驻留在那个包里(可是也有例外)。当符号须要使用包标识符打印的时候,使用来自于它的主包的标识符。你能够对它的主包使用SYMBOL-PACKAGE函数来查询符号:

CL-USER> (symbol-package 'bob::foo)
#<PACKAGE "JANE">
CL-USER>
注意,建立符号而没有主包是可能的,例如,未驻留的符号就没有主包。可是不用主包来建立驻留符号也是可能的,例如:
CL-USER> (in-package :jane)
#<PACKAGE "JANE">
JANE> 'weied-symbol
WEIED-SYMBOL
JANE> (in-package BOB)
#<PACKAGE "BOB">
BOB> (import 'jane::weied-symbol)
T
BOB> (in-package :jane)
#<PACKAGE "JANE">
JANE> (unintern 'weied-symbol)
T
JANE> (in-package bob)
#<PACKAGE "BOB">
BOB> 'weied-symbol
#:WEIED-SYMBOL
BOB> (symbol-package 'weied-symbol)
NIL
BOB> (in-package jane)
#<PACKAGE "JANE">
JANE> 'bob::weied-symbol
#:WEIED-SYMBOL
JANE>
这种事情避之则吉。

5. 导出包和使用包

假设Jane和Bob须要在一个软件开发项目上进行合做,每一个人都工做在他们本身的包里以免冲突,Jane写道:

JANE> (in-package jane)
#<PACKAGE "JANE">
JANE> (defclass jane-class () (slot1 slot2 slot3))
#<STANDARD-CLASS JANE-CLASS>
如今,想象一下Bob想要使用jane-class,它写道:
JANE> (in-package bob)
#<PACKAGE "BOB">
BOB> (import 'jane::jane-class)
T
BOB> (make-instance 'jane-class)
#<JANE-CLASS {25418779}>
BOB>
目前为止,一切都好,如今他尝试这么作:
BOB> (setq jc1 (make-instance 'jane-class))
#<JANE-CLASS {25621D99}>
BOB> (slot-value jc1 'slot1)
When attempting to read the slot's value (slot-value), the slot
SLOT1 is missing from the object #<JANE-CLASS {25621D99}>.
   [Condition of type SIMPLE-ERROR]

Restarts:
 0: [RETRY] Retry SLIME REPL evaluation request.
 1: [*ABORT] Return to SLIME's top level.
 2: [TERMINATE-THREAD] Terminate this thread (#<THREAD "new-repl-thread" RUNNING {259323F9}>)
......
又怎么了?好吧,JANE-CLASS定义在JANE包里,因此咱们读取的这个槽的名字驻留在JANE包里。可是Bob视图读取那个类的实例,使用的倒是BOB包里驻留的符号。换句话说,JANE-CLASS有一个名为JANE::SLOT1的槽,Bob试图读取槽名为BOB::SLOT1的槽,固然了没有这样的槽。

Bob真正要作的是导入与JANE-CLASS相关的全部的符号,那就是说,全部的槽名,方法名等等。他如何知道这些符号是什么呢?他能够查看Jane的代码,再尝试想一想办法,可是那也会致使不少问题,至少有一个问题:他可能决定导入一个jane不想让他搞乱的那个符号(记住,从Bob的干预的影响里分离出Jane的符号是使用包的重点所在)。

对Jane来讲一个更好的解决方法就是收集一个列表,这个列表里应该导入Bob想要使用Jane的软件而须要的符号,那么Jane就能够这样作:

JANE> (defvar *published-symbols* '(jane-class slot1 slot2 slot3))
*PUBLISHED-SYMBOLS*
JANE>
而后Bob这么作:
(import jane::*published-symbols*)
Common Lisp提供了一个标准的机制来作这件事。每一个包都维护一个可能被其它包使用的符号列表,这个列表叫那个包的“输出的符号”的列表,你可使用EXPORT函数向那个包里增长符号;想要从一个包里移除一个符号,你可使用UNEXPORT函数(像你指望的,有这个函数);想要向一个包里导入全部已导出的符号,你能够调用USE-PACKAGE;想要解除导入,你能够调用UNUSE-PACKAGE。

关于导出符号,有两件事须要注意:

首先,一个符号能够从任意一个它驻留的包里导出,不必非是它的主包。为了从一些其它驻留该符号的包里导出,该符号也不须要从其主包导出。

其次,在使用包标识符打印的时候,从其主包导出的符号仅须要使用一个冒号而不须要两个。这就提醒你一个事实:这是一个从主包导出的符号(因此你可能使用USE-PACKAGE对符号的包进行操做,而不是IMPORT符号),同时为了获取它们,经过强制你多键入一个冒号的方式来使用没有导出的符号,会让你很气馁(是的,我没开玩笑)。

6. 屏蔽

还有最后一个你须要知道的事:在包里使用USE-PACKAGE与使用IMPORT导入全部已经导出的符号有一些不一样。当你使用IMPORT导入一个符号的时候,你可使用UNINTERN撤销IMPORT的影响。你却不能使用UNINTERN撤销USE-PACKAGE操做的影响。例如:

BOB> (in-package jane)
#<PACKAGE "JANE">
JANE> (export '(slot1 slot2 slot3))
T
JANE> (in-package bob)
#<PACKAGE "BOB">
BOB> (use-package 'jane)
T
BOB> (symbol-package 'slot1)
#<PACKAGE "JANE">
BOB> (unintern 'slot1)
NIL
BOB> (symbol-package 'slot1)
#<PACKAGE "JANE">
BOB>
这是个问题,由于这使如下这种状况成为可能:使用导出同一个名称的符号的两个不一样的包。例如:假设你想在包MYPACKAGE里使用两个包P1和P2,P1和P2都导出了符号名为X的符号。若是你试图使用USE-PACKAGE来使用这两个包的话,你可能会获得名称冲突,由于Lisp没有办法知道MYPACKAGE包里的X是P1:X仍是P2:X。

为了解决这样的名字冲突,每一个包都维护一个叫作“屏蔽符号列表”的东西,这个屏蔽符号列表是这样的一个符号列表,它屏蔽或覆盖任何正常状况下因调用USE-PACKAGE而在那个包里可见的符号。

向包的屏蔽符号列表里添加符号的方式有两种,SHADOW和SHODOWING-IMPORT。SHADOW用来将包内的符号加入到屏蔽符号列表里;SHADOWING-IMPORT用来将其余包的符号加入到屏蔽符号列表里。

例如:

CL-USER> (make-package :p1 :use '(common-lisp cl-user))
#<PACKAGE "P1">
CL-USER> (make-package :p2 :use '(common-lisp cl-user))
#<PACKAGE "P2">
CL-USER> (in-package p1)
#<PACKAGE "P1">
P1> (export '(x y z))
T
P1> (in-package P2)
#<PACKAGE "P2">
P2> (export '(x y z))
T
P2> (make-package :bob :use '(common-lisp cl-user))
#<PACKAGE "BOB">
P2> (in-package :bob)
#<PACKAGE "BOB">
BOB> (use-package 'p1)
T
BOB> (use-package 'p2)
USE-PACKAGE #<PACKAGE "P2"> causes name-conflicts in
#<PACKAGE "BOB"> between the following symbols:
  P2:Y, P1:Y
   [Condition of type SB-EXT:NAME-CONFLICT]
See also:
  Common Lisp Hyperspec, 11.1.1.2.5 [:section]

Restarts:
 0: [RESOLVE-CONFLICT] Resolve conflict.
 1: [RETRY] Retry SLIME REPL evaluation request.
 2: [*ABORT] Return to SLIME's top level.
 3: [TERMINATE-THREAD] Terminate this thread (#<THREAD "repl-thread" RUNNING {2563FF19}>)

Backtrace:
...........................................
BOB> (unuse-package 'p1)
T
BOB> (shadow 'x)
T
BOB> (shadowing-import 'p1:y)
T
BOB> (shadowing-import 'p2:z)
T
BOB> (use-package 'p1)
T
BOB> (use-package 'p2)
T
BOB> (symbol-package 'x)
#<PACKAGE "BOB">
BOB> (symbol-package 'y)
#<PACKAGE "P1">
BOB> (symbol-package 'z)
#<PACKAGE "P2">
BOB>
为了撤销SHADOW或SHADOWING-IMPORT的影响,可使用UNINTERN。

注意,UNINTERN(还有不少不少其它使人惊奇的事情)能够致使不指望出现的名称冲突。不指望的名称冲突的最多见的缘由一般是在包里不慎地驻留了符号引发的,驻留符号通常是经过在读取器里键入该符号而此时却没有对你所在的包足够关心引发的。

7. DEFPACKAGE宏

既然你已经学过全部用来操做包的大量函数与宏,但你也不该该使用它们中的任意一个,相反地,IMPORT、EXPORT、SHADOW等等的全部功能都会出如今一个单个宏里——DEFPACKAGE宏,它才是你在真实代码里应该用到的。

我不打算给你解释DEFPACKAGE宏,由于既然你已经理解了包的基本概念,你就应该可以读懂hyperspec里的文档并理解它。(hyperspec里还有一些其它的好东西,好比DO-SYMBOLS和WITH-PACKAGE-ITERATOR,如今你应该可以本身理解它们。)

关于使用DEFPACKAGE的一个补充说明:注意传递给DEFPACKAGE的大多数参数都是字符串标识,不是符号,这意味着它们多是符号,可是若是你选择使用符号,不管此时DEFPACKAGE形式读取到什么,那么那些符号将会驻留在当前包里。这一般致使不受欢迎的结果,养成在你的DEFPACKAGE形式里使用关键字或字符串的习惯是个好主意。

8. 最后的一点想法

理解包的最重要的事情是它们从根本上是Lisp读取器的一部分,而不是求值器的一部分。一旦你的大脑绕明白这个事情,其它的事情就水到渠成了。包控制读取器将字符串映射到符号上的方式(还控制PRINT将符号映射到字符串的方式),没有其它的。尤为须要注意的是,包与函数、值、属性列表等都没有关系,函数、值、属性列表等与特定的符号可能有关系,也可能不要紧。

尤为须要注意的是,符号和函数对象均可以被当作函数来使用,可是他们的表现会有些不一样,例如:

CL-USER> (defun foo () "Original foo")
FOO
CL-USER> (setf f1 'foo)
FOO
CL-USER> (setf f2 #'foo)
#<FUNCTION FOO>
CL-USER> (defun demo ()
           (list (funcall f1) (funcall f2)
                 (funcall #'foo) (funcall #.#'foo)))
DEMO
CL-USER> (demo)
("Original foo" "Original foo" "Original foo" "Original foo")
CL-USER> (defun foo () "New foo")
STYLE-WARNING: redefining COMMON-LISP-USER::FOO in DEFUN
FOO
CL-USER> (demo)
("New foo" "Original foo" "New foo" "Original foo")
CL-USER>
本例中,咱们有两个变量F1和F2,F1的值是符号FOO,F2的值是一个函数对象,在F2被分配的时候,该函数对象在符号FOO的symbol-function槽里。

注意当FOO重定义的时候,调用符号FOO的影响就是获取新的行为,反之调用函数对象就产生老的行为。解释列表里第二各和第四个结果留下当作读取器的练习。

相关文章
相关标签/搜索