[Emacs Lisp] 变量和符号

Lisp程序是用Lisp对象表示的,
但是代码却是用文本形式来书写的,
Lisp读取器会通过对象的read syntax来将文本读取为对象。
变量就是symbol对象的read syntax,
例如:x是一个变量,它表示一个symbol。

即,变量是程序中的一个名字,它用于表示一个值。
在Emacs Lisp中,每一个变量对应一个symbol,
变量名就是symbol的name,变量的值保存在symbol的value cell中。

1. 全局变量

使用defvardefconst来定义全局变量。
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-bufferx的值已经被定义了。

4. Symbol

symbol是一个对象,它具有唯一的名字,
symbol内,包含4个组成部分,称为cell,
(1)name:symbol的名字
(2)value cell:作为一个动态变量,symbol的值
(3)function cell:作为一个函数,它的函数值
(4)property list:属性列表

defvardefconst会创建一个全局symbol,并设置value cell。
defundemacro会创建全局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。


参考

GNU Emacs Lisp Reference Manual

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容