在 CL 中,符號扮演了一個特殊的角色,這個角色在其它語言中不存在。程序員
首先,符號扮演了其它語言中「標識符」的做用,它能夠用來命名變量和函數;另外一方面,符號自己是一種獨立的數據類型,就如同數字、字符串同樣。也就是符號自己也能夠是「值」。例如:T
和 nil
就是兩個特殊的符號,它們的值就是它們本身。全部的關鍵字符號(以冒號開頭的符號)都是自求值的,它們不指向內存中的某個值,它們的值就是它本身。函數
符號背後的真相是複雜的。事實上符號也許只是一個「引用」,它指向內存中的某塊數據。CL 的複雜性在於,內存中的這一塊數據有可能分紅了多個格子,能夠同時存放多個值,而且根據上下文需求來訪問其中的某個格子。調試
其中一個格子存放「值」(value),和其它語言中的變量同樣,就是普通意義上的值。code
另一個格子裏可能存放着一個函數(function)!!對象
其它還有一些格子,不過這裏暫時不討論那些東西。只從前面兩種可能性就能夠理解這種複雜性。ip
換句話說,同一個符號,它有多是一個普通變量,也可能同時又是一個函數。那麼,解釋器(編譯器)如何知道在何時要去訪問內存中的哪一個格子呢?內存
很簡單,當符號出如今參數位置上的時候,系統從 value
格子中取值並返回;當符號出如今函數調用位置(緊跟在括號後面)的時候,系統認爲這是一個函數調用,因而去function
格子中取出函數並進行調用。ci
在代碼中顯式或隱式地進行變量綁定時,值會放在 value
格子中,當 defun
時,函數體(代碼)會放在function
格子中。作用域
仍是實際操做一下吧,這樣容易理解文檔
;; 隱式地建立一個變量,賦值爲32 (setq foo 32) => 32 ;; 在 REPL 中驗證一個,foo 的值確實是32 foo => 32 ;; 再定義一個函數 foo, 它老是返回 nil (defun foo () nil) => FOO ;; 如今,在 foo 這個符號後面已經有兩個格子了, ;; 分別存了整數 32 和一個函數,互不干擾 foo => 32 (foo) => nil ;; 若是把函數放到 value 的位置上會怎麼樣? ;; 咱們知道 lambda 表達式返回一個函數對象 (setf foo #'(lambda () t)) => #<FUNCTION (LAMBDA ()) {1004E601EB}> foo => #<FUNCTION (LAMBDA ()) {1004E601EB}> (foo) => nil ;; 有趣的來了 (funcall foo) => T
同一個變量,保存了兩個函數,調用其中一個返回 nil
,調用另外一個返回T
。爲何會這樣?
當使用顯式的函數調用(緊跟在括號後面)去調用 foo
的時候,系統從 function
中取得函數,並進行調用
當使用funcall
去調用的時候,foo
處在參數位置上,因而,系統去value
格子中取出了另外一個函數,傳遞給 funcall
,因而獲得了另外一種結果。
(symbol-value 'foo) => #<FUNCTION (LAMBDA ()) {1004E601EB}> foo => #<FUNCTION (LAMBDA ()) {1004E601EB}> (symbol-function 'foo) => #<FUNCTION FOO> #'foo => #<FUNCTION FOO> (function foo) => #<FUNCTION FOO>
咱們知道,#'
前綴不過是 function
的語法糖,如今看起來,function
也不過是 symbol-function
的語法糖而以。它們共同的做用,就是從一個符號背後專門放函數的格子裏取出東西來。
把這一點搞清楚是有好處的。把函數看成參數去傳遞時要不要加 #'
前綴?funcall
的第一個參數爲何不能是一個函數名?
我是從 Scheme 入的坑,轉到 CL 時四處碰壁。好比高階函數的使用上,Scheme 直接把一個函數名做用參數傳遞給另一個函數,在另一個函數中能夠直接調用。如今搞明白了,正確的姿式是,先用#'
把一個函數的函數體(代碼)取出來,再傳遞給另一個函數,在被調函數內部,可使用funcall
直接調用這一坨代碼。若是直接給funcall
傳遞一個函數名,並讓它調用會怎麼樣?系統會認爲這個函數名是一個普通變量,而且試圖去value
格子裏讀取它的值,結果找不到就報錯。
還有個好玩的事,除了nil
、T
、關鍵字符號是自求值符號之外,還能夠把普通符號也變成自求值的
(setf abc 'abc) => ABC abc => ABC (symbol-value 'abc) => ABC (symbol-value (symbol-value (symbol-value (symbol-value (symbol-value 'abc))))) => ABC
無論使用嵌套多少層的symbol-value
去讀取一個自求值符號的值,它永遠是它本身。爲何會這樣?由於在系統內部,符號是惟一的,兩個相同的符號,其實都指向內存中的同一個地址。
經過看文檔,發現有幾個訪問器能夠分別訪問一個符號背後的幾個格子:
symbol-name symbol-value symbol-function symbol-plist symbol-package
也就是說,在一個符號背後,有多達5個格子。
package 標識了符號屬於哪一個包(命名空間)
(symbol-name 'bar)
=> "BAR"
;; 嘗試從一個從未出現過的符號中取出屬性值,返回 nil。然而該符號已經悄悄地被建立了
;; 要注意,符號自帶的 plist 的 key 也是用符號表示的,而不是冒號開關的關鍵字符號
(get 'alizarin 'color)
=> NIL
;; 直接賦值
(setf (get 'alizarin 'color) 'red)
;; 而後再讀取
(get 'alizarin 'color)
=> RED
按常理,當一個變量未被聲明(隱式或顯式),內存中是不該該有它的位置的,也就是它背後的格子尚未 malloc。真相是,一個符號從第一次出現,就自動在內存中被建立了(儘管可能會被GC當即回收掉)。因此,CL的變量聲明與其它語言的變量聲明和初始化是不同的,顯得至關類。
詞法做用域 沒有聲明爲 (declare (special var))
的局部變量都是詞法變量。詞法變量的做用域限制在詞法範圍內。在調用時,它的值不會被新的 env 中的同名變量所覆蓋。反過來,若是一個局部變量被聲明爲 special
,那麼它的做用域也是動態的(在運行期,從 env 一級一級往上找,直到找到一個同名符號)
動態做用域 CL 的全局變量幾乎都是用 defparameter
和 defvar
來聲明的。使用這二者聲明的變量都是「動態做用域」的(自由變量)。做態做用域的變量會被新 env 中的同名變量所覆蓋。這就會形成一種錯覺,彷佛 CL 的全局變量都是動態做用域的自由變量。其實,若是用 setf
或者 setq
來隱式地建立全局變量,它的做用域就不是動態的了。
動態變量是把雙刃劍,帶來方便的同時也會帶來難以調試的BUG。須要注意的是,用defparameter
和defvar
聲明的全局變量通通都是自由變量。因此,傳統上,LISP 程序員會使用先後加星號的方式來命名全局變量,多少能起到點強調做用。
CL 的這種複雜性在我看來是沒必要要的,CL 在標準化過程當中帶了太多的歷史包袱,因而就變成了如今這個奇怪的樣子。長得奇怪,可是卻很強大。說句寬慰的話,這幾乎是全部獲得了普遍應用的工業語言的共性。好比 C,好比 perl,好比 Javascript, 再好比 UNIX SHELL(以及UNIX自己), 它們共同的特色是:被人罵了幾十年了......
從某種角度講,Scheme 是比 Common Lisp 更純粹的 LISP,然而 Scheme 界的牛人們彷佛忙於爲了語言標準而爭吵,對於語言的應用並不怎麼上心。Scheme 做爲一個活了幾十年的語言,獲得了太多的讚美。然而對比工業界每幾年就要產生一門新語言,新語言一面世標準庫和第三方庫就迅速擴大。在這方面 Scheme 確實混得挺慘的。除了已經分道揚鑣的 Racket 之外,幾乎都是些玩具,從未真正地走出課堂。