原文請在此處下載:http://download.csdn.net/detail/firstblood2008/8550683 html
當與多名程序員編寫大型工程的時候,兩個不一樣的程序員常常會因不一樣的意圖而使用相同的名稱。使用命名空間慣例可能會解決這個問題,例如,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概念進行復習。
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符號的集合,這些符號有這樣一個特性,即該集合裏沒有兩個相同名字的符號。
不幸的是,多多少少那是包簡單和直接的最後一個體現,從如今開始事情就變得刺激了。
(關於詞意來歷請參看:字符串的駐留)
將一個符號放入到包裏的動做叫符號駐留。做爲包的成員的符號能夠說是駐留在那個包中的。那些不是任何包的成員的符號能夠說沒有被駐留。未駐留符號打印時,將在其前面加一個#:前綴,用來與已駐留符號作區分,這個前綴的做用是提醒你:由於這個符號是另外一個符號,事實上不是你之前見過的相同的那個符號。
如今,這裏就是開始讓你有那麼一點兒困惑的地方。
有一個叫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用來建立符號的那個函數。
操做包的函數,像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。
Common Lisp視圖維持的一個不變式叫作打印-讀取一致性特性。該特性表述爲,若是你打印一個符號,而後讀取那個符號的結果打印形式,該結果是相同的符號,有兩點附加說明:
一下子咱們將解釋它的意思。
爲了保證打印-讀取一致性,一些符號須要與他們的包標識符一塊兒打印。例如:
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>這種事情避之則吉。
假設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符號),同時爲了獲取它們,經過強制你多鍵入一個冒號的方式來使用沒有導出的符號,會讓你很氣餒(是的,我沒開玩笑)。
還有最後一個你須要知道的事:在包裏使用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(還有不少不少其它使人驚奇的事情)能夠致使不指望出現的名稱衝突。不指望的名稱衝突的最多見的緣由一般是在包裏不慎地駐留了符號引發的,駐留符號通常是經過在讀取器裏鍵入該符號而此時卻沒有對你所在的包足夠關心引發的。
既然你已經學過全部用來操做包的大量函數與宏,但你也不該該使用它們中的任意一個,相反地,IMPORT、EXPORT、SHADOW等等的全部功能都會出如今一個單個宏裏——DEFPACKAGE宏,它纔是你在真實代碼裏應該用到的。
我不打算給你解釋DEFPACKAGE宏,由於既然你已經理解了包的基本概念,你就應該可以讀懂hyperspec裏的文檔並理解它。(hyperspec裏還有一些其它的好東西,好比DO-SYMBOLS和WITH-PACKAGE-ITERATOR,如今你應該可以本身理解它們。)
關於使用DEFPACKAGE的一個補充說明:注意傳遞給DEFPACKAGE的大多數參數都是字符串標識,不是符號,這意味着它們多是符號,可是若是你選擇使用符號,不管此時DEFPACKAGE形式讀取到什麼,那麼那些符號將會駐留在當前包裏。這一般致使不受歡迎的結果,養成在你的DEFPACKAGE形式裏使用關鍵字或字符串的習慣是個好主意。
理解包的最重要的事情是它們從根本上是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的影響就是獲取新的行爲,反之調用函數對象就產生老的行爲。解釋列表裏第二各和第四個結果留下當作讀取器的練習。