[Emacs] Emacs之魂(四):标识符,符号和变量

1. 符号

上文我们提到了Emacs Lisp是一种Lisp-2
即同一个符号(symbol)在不同的上下文中,可以分别表示两种不同的值(value):
变量(variable)或者函数(function),
这里符号(symbol)实际上是一个Lisp对象,而它的文本表示(textual representation)称之为标识符(identifier)。

标识符,符号和变量,这三个概念如果不谨慎对待,就会造成混乱。
其它编程语言可能没有“符号”的概念,这也是学习Lisp时容易困惑的原因之一。
此外,这里“符号”特指Lisp语言的“Symbol”,不能用汉语字面意思来理解它。

标识符,是Lisp的上下文无关文法(context-free grammar)中的一个非终结符(nonterminal),
它是一种词法结构,编译器前端(compiler front-end)在进行词法分析时会将标识符从字符流中识别出来。

符号(symbol)是一个Lisp对象,它是一个数据结构,由以下4个部分组成,
(1)name:symbol的名字
(2)value cell:作为一个动态变量,symbol的值
(3)function cell:作为一个函数,它的函数值
(4)property list:属性列表

标识符直接在Lisp代码中出现,会被读取为一个符号(symbol),
然后在不同的上下文中,Lisp求值器会看情况取出value cell或者function cell的内容,
作为该符号(symbol)的值(value)。

如果某一个函数接受符号(symbol)而不是它的值(value)作为参数,我们就得引用(quote)它,
即,我们使用引用,可以创建一个符号(symbol)字面量(literal)。
例如:symbol-name函数可以用来获取符号(symbol)x的name,

(symbol-name 'x)
"x"

结合上一篇,我们总结如下,
(1)直接写(foo bar bar)表示函数调用或者宏调用
(2)加引用'(foo bar bar)表示列表
(3)直接写x表示变量或者函数
(4)加引用'x表示符号(symbol)

如果只是这样的话,还很容易理解的,
可是value cell中只能保存动态变量,这一点理解起来就比较困难了。
“动态”是什么意思呢?还要从变量的定义和类别说起。

2. 全局变量和局部变量

Lisp提供了两种定义变量的方式,defvarlet
其中defvar用来定义全局变量,let用来定义局部变量。

例子:

(defvar a "1")

(let ((b "2"))
  (message "%s" b))    ; "2"

(message "%s" a)    ; "1"
(message "%s" b)    ; Error: Symbol’s value as variable is void: b

以上程序中,我们用defvar定义了全局变量a,和局部变量b
其中message用于在Emacs的“echo area”中输出内容,
message的第一个参数是表示格式的字符串,第二个参数是待输出的内容。
Lisp用分号表示注释。

为了执行这段程序,我们需要将它写到Emacs的buffer中,然后按M-x再输入eval-buffer回车,来求值整个缓冲区。
其中M-x表示按住alt键,然后再按x,该快捷键命令会将光标定位到echo area,等待用户输入一个函数名,
我们输入函数eval-buffer,它用来求值当前buffer,
它还有一个别名为ev-b,可以记为M-x ev-b

注意,按M-x之后,我们不用输入“M-x”,直接输入函数名“ev-b”就可以了。
程序最终的执行结果如注释所示,变量a在整个程序中可用,而变量b只在let范围内可用。

3. 作用域和生存期

以上程序中,我们通过defvarlet,让a的值为字符串"1"b的值为字符串"2"
我们说,defvarlet建立了两个绑定(binding),将a绑定为"1"b绑定为"2"

The association between a variable and its value is called a binding.
——《Essentials of Programming Languages - P90》

变量除了可以分为全局变量和局部变量之外,还有另外两方面的属性,作用域(scope)和生存期(extent)。
作用域表示,在源代码文本中,绑定在什么地方(where)有效。
生存期表示,在程序执行的过程中,绑定在什么时候(when)有效。

Emacs Lisp支持两种形式的绑定,
动态绑定(dynamic binding)和静态绑定(lexical binding)。

动态绑定具有动态作用域和动态生存期,
动态作用域(dynamic scope),任何一段代码都可能访问变量的绑定,
动态生存期(dynamic extent),只有在绑定结构(例如let)执行的过程中,绑定才有效。

静态绑定具有静态作用域(也称词法作用域)和无限生存期,
词法作用域(lexical scope),绑定在绑定结构的源代码文本范围中有效,
无限生存期(indefinite extent),某些情况下,绑定可能永远有效。

幸运的是,Emacs Lisp同时支持这两种绑定方式,否则很难直观的理解它们,
默认情况下Emacs Lisp支持动态绑定,我们还可以为Emacs启用静态绑定规则。

3.1 动态绑定

例子:

(defvar x 0)

(defun getx ()
    x)

(let ((x 1))
    (getx))    ; 1

(getx)    ; 0

其中defun用于在Emacs Lisp中定义函数,以上代码定义了一个getx无参数函数,
(getx)是对该函数的调用。

在对getx进行的第一次调用时,函数中引用了自由变量x,Lisp要寻找程序执行期间对x最近的绑定,
于是找到了let表达式中,getx调用之前对x的绑定,为1

第二次调用getx时,let表达式的执行已经结束了,它对任何变量的绑定都将销毁,
这时候再调用getx,程序执行期间最近的对x的绑定,是(defvar x 0)x的绑定,为0

在Emacs Lisp中,每一个符号(symbol)都有一个value cell,表示变量的当前值(current dynamic value),当一个符号(symbol)被给定一个局部绑定时(dynamic local binding),Emacs会把原来的value cell记录在一个栈上,然后把新值放入value cell中。当绑定结构(例如let)执行完后,Emacs进行弹栈操作,取出旧的值放回value cell中。

注意,其他语言中的全局变量并不是动态绑定,考虑以下JavaScript代码,

let x = 0;
function getx(){
    return x;
}

((x)=>{
    getx();    // 0
})(1);

getx();    // 0

JavaScript的全局变量仍然是静态绑定,第一个getx被调用时,并不会携带x的任何信息过去。
getx总是从源代码文本范围内寻找x,JavaScript对变量采用的是静态绑定。

3.2 静态绑定

例子:

; -*- lexical-binding: t -*-

(setq test (let ((foo "bar"))
         (lambda () 
           foo)))

(let ((foo "something-else"))
  (funcall test))    ; "bar"

(funcall test)    ; "bar"

其中,; -*- lexical-binding: t -*-是Emacs的文件变量(file variable),
用于对当前文件或buffer启用静态绑定规则,它必须位于文件或者buffer的第一行

在调用test函数时,函数中引用的自由变量foo,总是从源代码文本范围内离该函数最近的位置寻找,
于是找到了(lambda () foo)外层let中绑定的"bar"
所以两次对test的调用,结果都是"bar"

在Emacs Lisp中,每一个绑定结构都会创建一个新的词法环境(lexical environment),在这个环境中,保存了变量名和它所对应值之间的对应关系(即,绑定关系),当Lisp求值器对某个符号(symbol)求值的时候,它首先从词法环境中寻找值,如果找到了,就用这个值。否则就认为这个符号(symbol)是一个动态变量,读取符号(symbol)的value cell作为变量的值。

4. 全局变量的动态性质

(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

以上两段程序都启用了静态绑定规则,第一段程序中的x是静态绑定的,
第二段程序中的x是全局变量,使用defvar定义了,所以它是动态绑定的。

在进行试验时,需要在全新的buffer中,分别测试,
否则(defvar x 0)一旦执行,即使再重新M-x eval-bufferx的值已经被定义了。

参考

GNU Emacs manual
GNU Emacs Lisp Reference Manual
Essentials of Programming Languages

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

推荐阅读更多精彩内容