[Emacs Lisp] 變量和符號

Lisp程序是用Lisp對象表示的,
但是代碼卻是用文本形式來書寫的,
Lisp讀取器會(huì)通過對象的read syntax來將文本讀取為對象。
變量就是symbol對象的read syntax,
例如:x是一個(gè)變量,它表示一個(gè)symbol。

即,變量是程序中的一個(gè)名字,它用于表示一個(gè)值。
在Emacs Lisp中,每一個(gè)變量對應(yīng)一個(gè)symbol,
變量名就是symbol的name,變量的值保存在symbol的value cell中。

1. 全局變量

使用defvardefconst來定義全局變量。
setq可用于改變一個(gè)變量的值,如果沒有就創(chuàng)建全局變量。
全局變量在整個(gè)Emacs Lisp程序生命周期內(nèi)都有效,除非你改寫它的值。

例如:

(setq x '(a b))
x    ; (a b)

(setq x 4)
x    ; 4

2. 局部變量

全局變量直到被設(shè)置新的值,否則一直保持原來的值不變,
然而,很多時(shí)候,僅在局部給變量使用某個(gè)其他的值是有用的。
這個(gè)值只在這一段程序中有效,當(dāng)控制流離開了這塊代碼,變量又恢復(fù)為進(jìn)入之前的值。
我們說這種行為是,局部變量遮擋(shadow)了它以前的值。

例如,當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),它的參數(shù)變量就是局部變量,
局部變量的值只在函數(shù)體中有效,
同一個(gè)函數(shù)的不同調(diào)用,參數(shù)變量可能會(huì)被賦予不同的值。

let也可以用于創(chuàng)建局部變量,
且只在let體中有效。

3. 變量綁定的作用域規(guī)則

每一個(gè)局部變量的綁定都具有兩方面的屬性,
作用域(scope)和生存期(extent)。

作用域表示,在源代碼文本中,綁定在什么地方(where)有效。
生存期表示,在程序執(zhí)行的過程中,綁定在什么時(shí)候(when)有效。

Emacs Lisp支持兩種形式的綁定,
動(dòng)態(tài)綁定(dynamic binding)和靜態(tài)綁定(lexical binding)。

動(dòng)態(tài)綁定具有動(dòng)態(tài)作用域和動(dòng)態(tài)生存期,
動(dòng)態(tài)作用域(dynamic scope),任何一段代碼都可能訪問變量的綁定,
動(dòng)態(tài)生存期(dynamic extent),只有只有在綁定結(jié)構(gòu)(例如let)執(zhí)行的過程中,綁定才有效。

靜態(tài)綁定具有詞法作用域和無限的生存期,
詞法作用域(lexical scope),綁定在綁定結(jié)構(gòu)的源代碼文本范圍中有效,
無限生存期(indefinite extent),某些情況下,綁定可能永遠(yuǎn)有效。

(1)動(dòng)態(tài)綁定的具體實(shí)現(xiàn)

動(dòng)態(tài)綁定在Emacs Lisp中通過以下方式實(shí)現(xiàn),
每一個(gè)symbol都有一個(gè)value cell,表示變量的當(dāng)前值(current dynamic value),
當(dāng)一個(gè)symbol被給定一個(gè)局部綁定時(shí)(dynamic local binding),
Emacs會(huì)把原來的value cell記錄在一個(gè)棧上,然后把新值放入value cell中。
當(dāng)綁定結(jié)構(gòu)執(zhí)行完后,Emacs進(jìn)行彈棧操作,取出舊的值放回value cell中。

例子:

(defvar x 0)
(defun getx ()
    x)

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

(getx)    ; 0
(2)靜態(tài)綁定的具體實(shí)現(xiàn)

每一個(gè)綁定結(jié)構(gòu)會(huì)創(chuàng)建一個(gè)詞法環(huán)境(lexical environment),
在這個(gè)環(huán)境中保存了,變量名和它所對應(yīng)值之間的對應(yīng)關(guān)系,
當(dāng)Lisp求值器對某個(gè)變量求值的時(shí)候,它首先從詞法環(huán)境中尋找值,如果找到了,就用這個(gè)值。
否則就認(rèn)為這個(gè)symbol是一個(gè)動(dòng)態(tài)變量,讀取symbol的value cell作為變量的值。

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

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

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

(funcall test)    ; "bar"

注:
(1)動(dòng)態(tài)綁定變量的值總是從symbol的value cell中獲取,
而靜態(tài)綁定變量的值從詞法環(huán)境中獲取,
所以,無法使用symbol-value獲取靜態(tài)綁定變量的值。

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

(let ((x 1))
  (symbol-value 'x))    ; Symbol’s value as variable is void: x

(2)全局變量是動(dòng)態(tài)綁定的,
即使啟用了詞法綁定規(guī)則,let并沒有引入新的靜態(tài)變量x,
而是,建立了局部動(dòng)態(tài)變量x,然后用局部動(dòng)態(tài)變量遮擋了全局動(dòng)態(tài)變量的值。

; -*- 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

在進(jìn)行試驗(yàn)時(shí),需要在全新的buffer中,分別測試,
否則(defvar x 0)一旦執(zhí)行,即使再重新M-x eval-buffer,x的值已經(jīng)被定義了。

4. Symbol

symbol是一個(gè)對象,它具有唯一的名字,
symbol內(nèi),包含4個(gè)組成部分,稱為cell,
(1)name:symbol的名字
(2)value cell:作為一個(gè)動(dòng)態(tài)變量,symbol的值
(3)function cell:作為一個(gè)函數(shù),它的函數(shù)值
(4)property list:屬性列表

defvardefconst會(huì)創(chuàng)建一個(gè)全局symbol,并設(shè)置value cell。
defundemacro會(huì)創(chuàng)建全局symbol,并設(shè)置function cell。

當(dāng)Lisp讀取器遇到一個(gè)symbol的時(shí)候,它會(huì)從源代碼中讀取到symbol的名字,
然后在一個(gè)帶索引的數(shù)據(jù)結(jié)構(gòu)中查找symbol,這個(gè)數(shù)據(jù)結(jié)構(gòu)稱為obarray,
其索引是symbol名字的哈希值。

在Emacs Lisp中,obarray實(shí)際上是一個(gè)向量(vector),
根據(jù)哈希值,會(huì)查找到obarray的某一個(gè)元素,obarray的元素是一個(gè)桶(bucket),
里面包含了用鏈表存儲的具有相同哈希值的symbol。

Emacs默認(rèn)有一個(gè)obarray,用戶也可以創(chuàng)建自己的obarray,
通過make-vector可以創(chuàng)建一個(gè)新的obarray。

(make-vector 7 0)    ; LENGTH=7,INIT=0

每個(gè)obarray中symbol的名字不能相同,
相同名字的symbol可以放入不同的obarray中。
obarray中的symbol稱為interned symbol,
還有symbol不在任何obarray中,稱為uninterned symbol。

通過make-symbol可以創(chuàng)建uninterned symbol,

(setq sym (make-symbol "foo")
(eq sym 'foo)    ; nil

通過intern可以將名字放入默認(rèn)或指定的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可以從默認(rèn)或指定的obarray中刪除symbol。


參考

GNU Emacs Lisp Reference Manual

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容