上文中提到的變量函數(shù)之類,之所以會在某些時候與我們的理解發(fā)生偏差,并且總是存在些
神秘的地方無法解釋。這完全是因為我們理解得太片面導(dǎo)致。Lisp中的Symbol可以說就是某
個變量,或者某個函數(shù),但這太片面。Lisp中的Symbol擁有更豐富的含義。
Symbol的名字
就像很多語言的變量、函數(shù)名一樣,Lisp中的Symbol比其他語言在命名方面更自由: 只
要位于'|'字符之間的字符串,就表示一個合法的Symbol名。 我們可以使用函數(shù)
symbol-name來獲取一個Symbol的名字,例如:
(symbol-name '|this is a symbol name|)
輸出:"this is a symbol name"
'(quote)操作符告訴Lisp不要對其修飾的東西進行求值(evaluate)。但假如沒有這個操作符
會怎樣呢?后面我們將看到會怎樣。
Symbol與Variable和Function的聯(lián)系
自然而然地,翻閱Lisp文檔,我們會發(fā)現(xiàn)果然還有其他函數(shù)來訪問Symbol的其他域,例如:
symbol-function
symbol-value
symbol-package
symbol-plist
但是這些又與上文提到的變量和函數(shù)有什么聯(lián)系呢?真相只有一個, 變量、函數(shù)粗略來
說就是Symbol的一個域,一個成員。變量對應(yīng)Value域,函數(shù)對應(yīng)Function域。一個Symbol
這些域有數(shù)據(jù)了,我們說它們發(fā)生了綁定(bind)。 而恰好,我們有幾個函數(shù)可以用于判
定這些域是否被綁定了值:
boundp ;判定Value域是否被綁定
fboundp;判定Function域是否被綁定
通過一些代碼來回味以上結(jié)論:
(defvar *var* 1)
(boundp '*var*) ; 返回真
(fboundp '*var*) ; 返回假
(defun *var* (x) x) ; 定義一個名為*var*的函數(shù),返回值即為參數(shù)
(fboundp '*var*) ; 返回真
上面的代碼簡直揭秘了若干驚天地泣鬼神的真相。首先,我們使用我們熟知的defvar定義了
一個名為 *var* 的變量,初值為1,然后使用boundp去判定 *var* 的Value域是否
發(fā)生了綁定。這其實是說: 原來定義變量就是定義了一個Symbol,給變量賦值,原來就
是給Symbol的Value域賦值!
其實,Lisp中所有這些符號,都是Symbol。 什么變量,什么函數(shù),都是浮云。上面的
例子中,緊接著用fboundp判斷Symbol *var* 的Function域是否綁定,這個時候為假。
然后我們定義了一個名為 *var* 的函數(shù),之后再判斷,則已然為真。這也是為什么,
在Lisp中某個函數(shù)可以和某個變量同名的原因所在。 從這段代碼中我們也可以看出
defvar/defun這些操作符、宏所做事情的本質(zhì)。
More More More
事情就這樣結(jié)束了?Of course not。還有很多上文提到的疑惑沒有解決。首先,Symbol是
如此復(fù)雜,那么Lisp如何決定它在不同環(huán)境下的含義呢?Symbol雖然是個對象,但它并不像
C++中的對象一樣,它出現(xiàn)時并不指代自己!不同應(yīng)用環(huán)境下,它指代的東西也不一樣。這
些指代主要包括變量和函數(shù),意思是說: Symbol出現(xiàn)時,要么指的是它的Value,要么是
它的Function。 這種背地里干的事情,也算是造成迷惑的一個原因。
當一個Symbol出現(xiàn)在一個List的第一個元素時,它被處理為函數(shù)。這么說有點迷惑人,因為
它帶進了Lisp中代碼和數(shù)據(jù)之間的模糊邊界特性。簡單來說,就是當Symbol出現(xiàn)在一個括號
表達式(s-expression)中第一個位置時,算是個函數(shù),例如:
(add2 3) ; add2位于第一個位置,被當作函數(shù)處理
(*var* 3) ; 這里*var*被當作函數(shù)調(diào)用,返回3
除此之外,我能想到的其他大部分情況,一個Symbol都被指代為它的Value域,也就是被當
作變量,例如:
(*var* *var*) ; 這是正確的語句,返回1
這看起來是多么古怪的代碼。但是運用我們上面說的結(jié)論,便可輕易解釋:表達式中第一個
*var* 被當作函數(shù)處理,它需要一個參數(shù);表達式第二部分的 *var* 被當作變量
處理,它的值為1,然后將其作為參數(shù)傳入。
再來說說'(quote)操作符,這個操作符用于防止其操作數(shù)被求值。而當一個Symbol出現(xiàn)時,
它總是會被求值,所以,我們可以分析以下代碼:
(symbol-value *var*) ; wrong
這個代碼并不正確,因為 *var* 總是會被求值,就像 (*var* *var*) 一樣,第二
個 *var* 被求值,得到數(shù)字1。這里也會發(fā)生這種事情,那么最終就等同于:
(symbol-value 1) ; wrong
我們試圖去取數(shù)字1的Value域,而數(shù)字1并不是一個Symbol。所以,我們需要quote運算符:
(symbol-value '*var*) ; right
這句代碼是說,取Symbol *var* 本身的Value域!而不是其他什么地方。至此,我們
便可以分析以下復(fù)雜情況:
(defvar *name* "kevin lynx")
(defvar *ref* '*name*) ; *ref*的Value保存的是另一個Symbol
(symbol-value *ref*) ; 取*ref*的Value,得到*name*,再取*name*的Value
現(xiàn)在,我們甚至能解釋上文留下的一個問題:
;; wrong
(defvar fn #' (lambda (x) (+ x 2)))
(fn 3)
給fn的Value賦值一個函數(shù), (fn 3) 當一個Symbol作為函數(shù)使用時,也就是取其
Function域來做調(diào)用。但其Function域什么也沒有,我們試圖將一個Symbol的Value域當作
Function來使用。如何解決這個問題?想想,symbol-function可以取到一個Symbol的
Function域:
(setf (symbol-function 'fn) #' (lambda (x) (+ x 2)))
(fn 3)
通過顯示地給fn的Function域賦值,而不是通過defvar隱式地對其Value域賦值,就可以使
(fn 3) 調(diào)用正確。還有另一個問題也能輕易解釋:
(apply-fn add2 2) ; wrong
本意是想傳入add2這個Symbol的function域,但是直接這樣寫的話,傳入的其實是add2的
Value域 ,這當然是不正確的。對比正確的寫法,我們甚至能猜測#'運算符就是一個
取Symbol的Function域的運算符。進一步,我們還可以給出另一種寫法:
(apply-fn (symbol-function 'add2) 2)
深入理解事情的背后,你會發(fā)現(xiàn)你能寫出多么靈活的代碼。