在 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 之外,几乎都是些玩具,从未真正地走出课堂。