Lisp-1 vs Lisp-2[1]
Scheme的求值模型非常简单:只是用一个名字空间,所有表达式中相应位置的值应该是明确的。
Common Lisp和Scheme最大的不同是,函数名字空间和数据的名字空间是分离的。操作函数名字空间的语句包括,defun,flet,labels,defmethod和defgeneric等方法。要用函数名作为另一个函数的参数来传递函数时,需要使用function特殊操作符或者使用#' 。
我们称Scheme的变量模型为Lisp-1,Common Lisp的变量模型是Lisp-2.
绑定
在编程语言中绑定是变量名(变量标识符)和对象(保存于内存中的存储单元,数据或代码)的映射关系。在这个绑定过程中是作用域有密不可分的关系,作用域决定了哪个变量绑定了哪个存储单元。
为变量建立绑定之后,就可以通过变量名来引用其所绑定的值。绑定的具体含义,可以参考下图
词法作用域(lexical scoping)
词法作用域又叫静态作用域(static scope)。顾名思义,词法变量即是使用词法作用域的变量。在词法作用域里,一个变量的变量名只能在一个函数或一段代码区域( block )内存在,此时变量名才会绑定到变量的值。
词法变量拥有不确定的生存期,即从时间上来讲,一个词法变量可以在任意的时间里持续存在,取决于该变量需要被使用(reference)多久。 词法作用域里,对于函数体中的一个符号,不会逐层检查函数的调用链,而是检查函数定义时的外部环境,即捕捉的是函数定义时该符号的绑定。
动态作用域(dynamic scoping)
使用动态作用域的变量叫做动态(dynamic)变量,有时也叫做特殊(special)变量。动态作用域里,每个变量名(变量标识符)都拥有一个全局的绑定栈。引入一个与动态变量同名的局部变量会为此变量名创建一个新的变量绑定并将其压入此变量名的全局绑定栈中,一个全局的变量名(变量标识符)总是引用当前其栈顶的绑定,当使用该变量绑定的代码执行完毕(即程序控制流离开了此变量的作用域),该变量绑定就会从此变量名的全局绑定栈中被弹出,该变量绑定就失效。
动态作用域表示的范围是不确定的,可从任何位置访问一个动态变量,取决于它们在什么地方被绑定。动态变量拥有动态的生存期。因容易引起误会而需要注意的是,不确定的作用域和动态生存期的组合经常被错误地称为动态作用域(dynamic scope)。
动态作用域里,函数执行遇到一个符号,会由内向外逐层检查函数的调用链,并打印第一次遇到的那个绑定的值。最外层的绑定即是全局状态下的那个值。
Common Lisp的例子
请看下面的代码
(let ((y 7))(defun foo (x)(print x)(print y)))(let ((y 5))(foo 1))
我们通过SLIM执行后,可以得到输出1和7,这说明Common Lisp使用的是词法作用域。在foo中寻找y的绑定时,它检查函数foo的词法上下文。
再请看下面代码
(let ((y 7))(defun foo (x)(print x)(print y)(setq y (+ y 2))))(let ((y 5))(foo 1)(foo 1))(let ((y 5))(foo 2))
我们通过SLIM执行后,我们会看到,1,7,1,9,2和11。在例子中的第一个let表里,定义了一个变量,符号名为y并绑定了值7,那么这个y的作用域就是这个let表区域。 foo函数定义在这个区域内,其内部会使用到一个符号名为y的变量。 那么在词法作用域的情况下,当foo被调用时,其会查找其定义的环境有没有符号名y的变量可以绑定,如果有则把foo中符号y的值绑定,在这里就是7。 并且这里foo中的y和外部let中的y共享一个值,都是对这个值的引用,并不是拷贝了一个新值。
参考
- ^https://en.wikipedia.org/wiki/Common_Lisp#The_function_namespace