[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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容