试论common lisp中符号(symbol)与变量的关系,以及词法变量与动态变量的区别

本文一些论点基于个人的推断与总结,请保持独立思考的能力,本文中所做的实验你也可以动手做一下

符号(symbol)与变量

变量是符号,但符号不一定是变量。

实验

CL-USER> (symbol-value 'app)
	error: The variable APP is unbound.
CL-USER> (setq app 3333)
;(一些其他信息,省略)
3333
CL-USER> (symbolp app) ;注意这里最终求值的应该是(symbolp 3333)
NIL
CL-USER> (symbolp 'app)
T
CL-USER> (symbol-value 'app)
3333
CL-USER> (symbol-function 'app)
	error: The function COMMON-LISP-USER::APP is undefined
CL-USER> (defun app (x) (* 2 x))
APP
CL-USER> (symbol-function 'app)
#<FUNCTION APP>
CL-USER> (+ 1 app)
3334
CL-USER> (app 1234)
2468

由此,我认为,符号和值绑定后,符号有被看作变量的资格;符号和函数绑定后,符号有被看作函数的资格。
根据符号所处的上下文,符号可以被看作变量或函数。

再补充一些可能看起来很奇怪的:

CL-USER> (setq 'appp (function (lambda (x) (* x 4))))
	error: Variable name is not a symbol: 'APPP.
CL-USER> (setq appp (function (lambda (x) (* x 4))))
;(省略一些其他信息)
#<FUNCTION (LAMBDA (X)) {23E7131D}>
CL-USER> (symbol-value 'appp)
#<FUNCTION (LAMBDA (X)) {23E7131D}>
CL-USER> (symbol-function 'appp)
	error: The function COMMON-LISP-USER::APPP is undefined
CL-USER> (mapcar #'appp '(1 2 3))
	error: The function COMMON-LISP-USER::APPP is undefined
CL-USER> (mapcar appp '(1 2 3))
(4 8 12)

词法变量,动态变量的区别

test 1

CL-USER> (defun show-my () (print a))
;(省略输出和警告信息)(函数应该是定义成功了)
CL-USER> (let ((a 3)) (show-my))
	==>error:  The variable A is unbound.

test 2

CL-USER> (defparameter *a* 1)
CL-USER> (defun show-my () (print *a*))
CL-USER> (let ((*a* 3)) (show-my))
	;我也不知道这里为啥会有个换行符,大概是print的作用吧。
3
3

test 3

CL-USER> (defparameter ap 1)
AP
CL-USER> (defun show-my () (print ap))
WARNING: redefining COMMON-LISP-USER::SHOW-MY in DEFUN
SHOW-MY
CL-USER> (show-my)

1 
1
CL-USER> (let ((ap 3)) (show-my))

3 
3
CL-USER> 

分析test 2

At this point, the LET binding of *MY-SPECIAL-VARIABLE* is not lexically apparent to the code in SHOW-MY-SPECIAL.
上面的这句话是我在找关于词法变量和动态变量的资料时在一个网站上的看到的。在这个例子中就是:
当执行S表达式"(print a)"时,*a*的动态绑定,is not lexically apparent to the code in SHOW-MY。
apparent:adj. 显而易见的,易懂的;貌似的,表面上的
我想表达的意思是:
当从(let ((*a* 3)) (show-my))这里,然后执行(defun show-my () (print *a*)),执行到(print *a*)这个表达式时,let绑定的那个((*a* 3))并不是像词法变量那样,能够被(print *a*)看见;而是通过另外一种方式让(print *a*)看见。(这里被看见的方式,就是我目前所理解的词法变量和动态变量的区别)

下面阐述我的理解:
也就是说,当执行到(print *a*)时,lisp发现了一个列表,其有两个元素:print , *a*,因为print在列表的第一个位置,且这个列表前面没有quote(即" ' ",因为列表前面有这个单引号表示不对列表求值),所以lisp看这个符号print是否有绑定的函数,就像是执行(symbol-function 'print),结果发现了符号print绑定了一个函数对象,这个函数对象主要是有打印的作用,然后,lisp打算将符号print之后的列表元素当作参数传递给这个函数对象,然后lisp发现了个参数*a*,lisp通过查阅得知(我不清楚lisp是如何查阅的。不知道是查阅了符号*a*的相关属性还是什么别的)*a*已经被声明为特殊的(special)(是这句'(defparameter *a* 1)'声明*a*是特殊的),然后lisp就去查*a*的特殊绑定(也就是动态绑定,注意在本文,你将“动态”和“特殊”理解为相同的东西就好)。
lisp中是有个动态绑定的栈的。
lisp从那个栈中查*a*的动态绑定,最先找到的动态绑定将作为结果返回。
那么,在执行(print a)的时候,那个动态绑定的栈是什么样呢?
从test 2的最开始看:
(defparameter *a* 1)创建了一个动态绑定,向动态绑定栈中压入了一个动态绑定,此时栈是这样的:
*a* 1
然后定义show-my:(defun show-my () (print *a*)),动态绑定栈没啥变化。
然后(let ((*a* 3)) (show-my)),当用let创建绑定的时候,lisp发现要创建(*a* 3)这个绑定,lisp向列表里面看去,看到了列表的第一个元素:符号*a*,lisp查了查这个符号,发现*a*是特殊的(special)(lisp并不是因为*a*这个符号带了两个'*'才判定*a*是特殊的,你可以看test 3来理解这个问题),于是lisp就决定要创建一个动态绑定,并向动态绑定栈中压入新创建的动态绑定,栈变为:
*a* 3
*a* 1
之后,当执行到(print *a*)时,lisp寻找*a*的动态绑定,(注意:lisp是从栈顶开始,向下搜索),所以查到的绑定就是(*a* 3).

要进一步比较词法变量和动态变量的区别,应该比较test1与test2。test 1 中没有特殊变量,所以show-my函数中的表达式在执行时,看不到a的词法绑定,故而引起异常。关于词法变量和动态变量的比较,就先到此为止吧,因为我也想不出这两个更深层次的区别了。

补充,并改写书上的一段话:
let是怎样知道在它绑定a时打算创建的是动态绑定而不是词法绑定呢?这是因为该名字已经被声明为特殊的。每一个由defvar和defparameter所定义的变量其名字都被自动声明为全局特殊的。这意味着无论何时你在绑定形式中使用这样一个名字,无论是在let中,还是通过函数形参,亦或是在任何创建新变量绑定的构造中,被创建的绑定将成为一个动态绑定。
再补充下:(这段话摘抄自《实用Common Lisp编程》page63)......在任何给定时刻,最近建立的绑定会覆盖其他所有的绑定。从概念上讲,一个给定动态变量的每个新绑定都将被推到一个用于该变量的绑定栈中,而对该变量的引用总是使用最近的绑定。当绑定形式返回时,他们所创建的绑定会被从栈上弹出,从而暴露出前一个绑定。

一个可能让你困惑的例子

CL-USER> (defun show-my ()(declare (special a)) (print a))
CL-USER> (let ((a 3)) (show-my))
	==>ERROR : The variable A is unbound.

CL-USER> (declare (special a) (let ((a 3)) (show-my)))
	==>ERROR : NO FUNCTION NAMED DECLARE

CL-USER> (let ((a 3)) (declare (special a)) (show-my))

3 
3
CL-USER> (show-my)
	==>error: The variable A is unbound.

一个与本文主题不大相关的补充例子

(setf (symbol-value 'n) 1) =>  1
 (set 'n 2) =>  2
 (symbol-value 'n) =>  2
 (let ((n 3))
   (declare (special n))
   (setq n (+ n 1))
   (setf (symbol-value 'n) (* n 10))
   (set 'n (+ (symbol-value 'n) n))
   n) =>  80

;;这里是重点
 n =>  2
 (let ((n 3))
   (setq n (+ n 1))
   (setf (symbol-value 'n) (* n 10)) 
   (set 'n (+ (symbol-value 'n) n))
   n) =>  4
 n =>  44

 (defvar *n* 2)
 (let ((*n* 3))
   (setq *n* (+ *n* 1))
   (setf (symbol-value '*n*) (* *n* 10))
   (set '*n* (+ (symbol-value '*n*) *n*))
   *n*) =>  80
  *n* =>  2

理解重点处的关键在于symbol-value和set这两个函数。
symbol-value函数的文档描述(摘要版本):

symbol-value returns the current value of the dynamic (special) variable named by symbol.

symbol-value cannot access the value of a lexical variable.
也就是说(symbol-value 'n)获取到的符号的值,是动态变量的,而不是词法变量的。

还有set函数的文档描述(摘要版本)
set allows alteration of the value of a dynamic (special) variable.

set cannot alter the value of a local (lexically bound) variable.
也就是说,我推断,(set 'n (+ (symbol-value 'n) n))中,set的是动态变量n的值。

下面的我也不明白,向大家求助这是为啥

? (setq var-1 'var-2)
-> VAR-2

? var-1
-> VAR-2

? var-2
->| Error: Unbound variable

? (set var-1 99)
-> 99

? var-1
-> VAR-2

? VAR-2
-> 99
上一篇:基于CPUID的内存频率,cl值,FSB频率小知识


下一篇:Cisco Catalyst 9800-CL Wireless Controller for Cloud, Release Bengaluru-17.06.01 ED