[Emacs Lisp] 变量和符号
Lisp程序是用Lisp对象表示的,
但是代码却是用文本形式来书写的,
Lisp读取器会通过对象的read syntax来将文本读取为对象。
变量就是symbol对象的read syntax,
例如:x
是一个变量,它表示一个symbol。
即,变量是程序中的一个名字,它用于表示一个值。
在Emacs Lisp中,每一个变量对应一个symbol,
变量名就是symbol的name,变量的值保存在symbol的value cell中。
1. 全局变量
使用defvar
和defconst
来定义全局变量。
setq
可用于改变一个变量的值,如果没有就创建全局变量。
全局变量在整个Emacs Lisp程序生命周期内都有效,除非你改写它的值。
例如:
(setq x '(a b))
x ; (a b)
(setq x 4)
x ; 4
2. 局部变量
全局变量直到被设置新的值,否则一直保持原来的值不变,
然而,很多时候,仅在局部给变量使用某个其他的值是有用的。
这个值只在这一段程序中有效,当控制流离开了这块代码,变量又恢复为进入之前的值。
我们说这种行为是,局部变量遮挡(shadow)了它以前的值。
例如,当一个函数被调用时,它的参数变量就是局部变量,
局部变量的值只在函数体中有效,
同一个函数的不同调用,参数变量可能会被赋予不同的值。
let
也可以用于创建局部变量,
且只在let
体中有效。
3. 变量绑定的作用域规则
每一个局部变量的绑定都具有两方面的属性,
作用域(scope)和生存期(extent)。
作用域表示,在源代码文本中,绑定在什么地方(where)有效。
生存期表示,在程序执行的过程中,绑定在什么时候(when)有效。
Emacs Lisp支持两种形式的绑定,
动态绑定(dynamic binding)和静态绑定(lexical binding)。
动态绑定具有动态作用域和动态生存期,
动态作用域(dynamic scope),任何一段代码都可能访问变量的绑定,
动态生存期(dynamic extent),只有只有在绑定结构(例如let
)执行的过程中,绑定才有效。
静态绑定具有词法作用域和无限的生存期,
词法作用域(lexical scope),绑定在绑定结构的源代码文本范围中有效,
无限生存期(indefinite extent),某些情况下,绑定可能永远有效。
(1)动态绑定的具体实现
动态绑定在Emacs Lisp中通过以下方式实现,
每一个symbol都有一个value cell,表示变量的当前值(current dynamic value),
当一个symbol被给定一个局部绑定时(dynamic local binding),
Emacs会把原来的value cell记录在一个栈上,然后把新值放入value cell中。
当绑定结构执行完后,Emacs进行弹栈操作,取出旧的值放回value cell中。
例子:
(defvar x 0)
(defun getx ()
x)
(let ((x 1))
(getx)) ; 1
(getx) ; 0
(2)静态绑定的具体实现
每一个绑定结构会创建一个词法环境(lexical environment),
在这个环境中保存了,变量名和它所对应值之间的对应关系,
当Lisp求值器对某个变量求值的时候,它首先从词法环境中寻找值,如果找到了,就用这个值。
否则就认为这个symbol是一个动态变量,读取symbol的value cell作为变量的值。
; -*- lexical-binding: t -*-
(setq test (let ((foo "bar"))
(lambda ()
foo)))
(let ((foo "something-else"))
(funcall test)) ; "bar"
(funcall test) ; "bar"
注:
(1)动态绑定变量的值总是从symbol的value cell中获取,
而静态绑定变量的值从词法环境中获取,
所以,无法使用symbol-value
获取静态绑定变量的值。
; -*- lexical-binding: t -*-
(let ((x 1))
(symbol-value 'x)) ; Symbol’s value as variable is void: x
(2)全局变量是动态绑定的,
即使启用了词法绑定规则,let
并没有引入新的静态变量x
,
而是,建立了局部动态变量x
,然后用局部动态变量遮挡了全局动态变量的值。
; -*- lexical-binding: t -*-
(setq test (let ((x 1))
(lambda ()
x)))
(funcall test) ; 1
; -*- lexical-binding: t -*-
(defvar x 0)
(setq test (let ((x 1))
(lambda ()
x)))
(funcall test) ; 0
在进行试验时,需要在全新的buffer中,分别测试,
否则(defvar x 0)
一旦执行,即使再重新M-x eval-buffer
,x
的值已经被定义了。
4. Symbol
symbol是一个对象,它具有唯一的名字,
symbol内,包含4个组成部分,称为cell,
(1)name:symbol的名字
(2)value cell:作为一个动态变量,symbol的值
(3)function cell:作为一个函数,它的函数值
(4)property list:属性列表
defvar
和defconst
会创建一个全局symbol,并设置value cell。
defun
和demacro
会创建全局symbol,并设置function cell。
当Lisp读取器遇到一个symbol的时候,它会从源代码中读取到symbol的名字,
然后在一个带索引的数据结构中查找symbol,这个数据结构称为obarray,
其索引是symbol名字的哈希值。
在Emacs Lisp中,obarray实际上是一个向量(vector),
根据哈希值,会查找到obarray的某一个元素,obarray的元素是一个桶(bucket),
里面包含了用链表存储的具有相同哈希值的symbol。
Emacs默认有一个obarray,用户也可以创建自己的obarray,
通过make-vector
可以创建一个新的obarray。
(make-vector 7 0) ; LENGTH=7,INIT=0
每个obarray中symbol的名字不能相同,
相同名字的symbol可以放入不同的obarray中。
obarray中的symbol称为interned symbol,
还有symbol不在任何obarray中,称为uninterned symbol。
通过make-symbol
可以创建uninterned symbol,
(setq sym (make-symbol "foo")
(eq sym 'foo) ; nil
通过intern
可以将名字放入默认或指定的obarray中。
(defvar other-obarray
(make-vector 7 0))
(setq sym (intern "foo")
(eq sym 'foo) ; t
(setq sym1 (intern "foo" other-obarray)
(eq sym1 'foo) ; nil
通过unintern
可以从默认或指定的obarray中删除symbol。