上文中提到的变量函数之类,之所以会在某些时候与我们的理解发生偏差,并且总是存在些
神秘的地方无法解释。这完全是因为我们理解得太片面导致。Lisp中的Symbol可以说就是某
个变量,或者某个函数,但这太片面。Lisp中的Symbol拥有更丰富的含义。
Symbol的名字
就像很多语言的变量、函数名一样,Lisp中的Symbol比其他语言在命名方面更自由: 只
要位于'|'字符之间的字符串,就表示一个合法的Symbol名。 我们可以使用函数
symbol-name来获取一个Symbol的名字,例如:
(symbol-name '|this is a symbol name|)
输出:"this is a symbol name"
'(quote)操作符告诉Lisp不要对其修饰的东西进行求值(evaluate)。但假如没有这个操作符
会怎样呢?后面我们将看到会怎样。
Symbol与Variable和Function的联系
自然而然地,翻阅Lisp文档,我们会发现果然还有其他函数来访问Symbol的其他域,例如:
symbol-function
symbol-value
symbol-package
symbol-plist
但是这些又与上文提到的变量和函数有什么联系呢?真相只有一个, 变量、函数粗略来
说就是Symbol的一个域,一个成员。变量对应Value域,函数对应Function域。一个Symbol
这些域有数据了,我们说它们发生了绑定(bind)。 而恰好,我们有几个函数可以用于判
定这些域是否被绑定了值:
boundp ;判定Value域是否被绑定
fboundp;判定Function域是否被绑定
通过一些代码来回味以上结论:
(defvar *var* 1)
(boundp '*var*) ; 返回真
(fboundp '*var*) ; 返回假
(defun *var* (x) x) ; 定义一个名为*var*的函数,返回值即为参数
(fboundp '*var*) ; 返回真
上面的代码简直揭秘了若干惊天地泣鬼神的真相。首先,我们使用我们熟知的defvar定义了
一个名为 *var* 的变量,初值为1,然后使用boundp去判定 *var* 的Value域是否
发生了绑定。这其实是说: 原来定义变量就是定义了一个Symbol,给变量赋值,原来就
是给Symbol的Value域赋值!
其实,Lisp中所有这些符号,都是Symbol。 什么变量,什么函数,都是浮云。上面的
例子中,紧接着用fboundp判断Symbol *var* 的Function域是否绑定,这个时候为假。
然后我们定义了一个名为 *var* 的函数,之后再判断,则已然为真。这也是为什么,
在Lisp中某个函数可以和某个变量同名的原因所在。 从这段代码中我们也可以看出
defvar/defun这些操作符、宏所做事情的本质。
More More More
事情就这样结束了?Of course not。还有很多上文提到的疑惑没有解决。首先,Symbol是
如此复杂,那么Lisp如何决定它在不同环境下的含义呢?Symbol虽然是个对象,但它并不像
C++中的对象一样,它出现时并不指代自己!不同应用环境下,它指代的东西也不一样。这
些指代主要包括变量和函数,意思是说: Symbol出现时,要么指的是它的Value,要么是
它的Function。 这种背地里干的事情,也算是造成迷惑的一个原因。
当一个Symbol出现在一个List的第一个元素时,它被处理为函数。这么说有点迷惑人,因为
它带进了Lisp中代码和数据之间的模糊边界特性。简单来说,就是当Symbol出现在一个括号
表达式(s-expression)中第一个位置时,算是个函数,例如:
(add2 3) ; add2位于第一个位置,被当作函数处理
(*var* 3) ; 这里*var*被当作函数调用,返回3
除此之外,我能想到的其他大部分情况,一个Symbol都被指代为它的Value域,也就是被当
作变量,例如:
(*var* *var*) ; 这是正确的语句,返回1
这看起来是多么古怪的代码。但是运用我们上面说的结论,便可轻易解释:表达式中第一个
*var* 被当作函数处理,它需要一个参数;表达式第二部分的 *var* 被当作变量
处理,它的值为1,然后将其作为参数传入。
再来说说'(quote)操作符,这个操作符用于防止其操作数被求值。而当一个Symbol出现时,
它总是会被求值,所以,我们可以分析以下代码:
(symbol-value *var*) ; wrong
这个代码并不正确,因为 *var* 总是会被求值,就像 (*var* *var*) 一样,第二
个 *var* 被求值,得到数字1。这里也会发生这种事情,那么最终就等同于:
(symbol-value 1) ; wrong
我们试图去取数字1的Value域,而数字1并不是一个Symbol。所以,我们需要quote运算符:
(symbol-value '*var*) ; right
这句代码是说,取Symbol *var* 本身的Value域!而不是其他什么地方。至此,我们
便可以分析以下复杂情况:
(defvar *name* "kevin lynx")
(defvar *ref* '*name*) ; *ref*的Value保存的是另一个Symbol
(symbol-value *ref*) ; 取*ref*的Value,得到*name*,再取*name*的Value
现在,我们甚至能解释上文留下的一个问题:
;; wrong
(defvar fn #' (lambda (x) (+ x 2)))
(fn 3)
给fn的Value赋值一个函数, (fn 3) 当一个Symbol作为函数使用时,也就是取其
Function域来做调用。但其Function域什么也没有,我们试图将一个Symbol的Value域当作
Function来使用。如何解决这个问题?想想,symbol-function可以取到一个Symbol的
Function域:
(setf (symbol-function 'fn) #' (lambda (x) (+ x 2)))
(fn 3)
通过显示地给fn的Function域赋值,而不是通过defvar隐式地对其Value域赋值,就可以使
(fn 3) 调用正确。还有另一个问题也能轻易解释:
(apply-fn add2 2) ; wrong
本意是想传入add2这个Symbol的function域,但是直接这样写的话,传入的其实是add2的
Value域 ,这当然是不正确的。对比正确的写法,我们甚至能猜测#'运算符就是一个
取Symbol的Function域的运算符。进一步,我们还可以给出另一种写法:
(apply-fn (symbol-function 'add2) 2)
深入理解事情的背后,你会发现你能写出多么灵活的代码。