[譯] Emacs Lisp 速成

From:http://emacsist.com/10845

點 這里 查看更多 Emacs 相關推薦文章 或 最新 Emacs 圈的動態. 歡迎關注微信公眾賬號: Emacsist

原文見 『Emergency Elisp』。

你用著 Emacs 卻不懂 Lisp 吧?歡迎閱讀這篇 Emacs Lisp 入門教程!它應該能夠助你搞定 Emacs Lisp,從而更加自如的駕馭 Emacs。

有很多種學習 Lisp 的方式,其中有一些方式要比其他方式更為 Lisp。我喜歡的方式是,基于 C++ 或 Java 的編程經驗來學習 Lisp。

本文重點放在 Emacs Lisp 語言本身,因為它才是最難的部分,至于成噸的 Emacs 的 API 的用法,你可以通過閱讀 Emacs Lisp 文檔來學習。

有些事(例如編寫生成代碼的代碼)是 Lisp 擅長的,而有些事(例如算數表達式)是它不擅長的。我不打算談論 Lisp 是好還是壞,只關心如何用它編程。Emacs Lisp 跟其他語言差不多,最終你會習慣它的。

許多介紹 Lisp 的文章或書籍嘗試給你展現 Lisp 之『道』,飽含著奉承、贊頌以及瑜伽之類的東西。事實上,一開始我真正想要的是一本簡單的 cookbook,它講述的是如何用 Lisp 來做一些我日常生活中的事。本文便立意于此,它講述的是大致是如何用 Emacs Lisp 來寫 C,Java 或 JavaScript 就能寫的那些代碼。

我們開始吧,看看我能夠將這篇文章寫的多么短小。我要從挺無聊的詞法標記、運算符開始,然后講述如何實現一些眾所周知的語句、聲明以及一些程序結構。

快速開始

Lisp 代碼是像 (+ 2 3) 的嵌套的括號表達式。這些表達式有時被稱為 form(塊)。

也有些不帶括號的代碼,譬如字符串、數字、符號(必須以單引號為前綴,例如 'foo)、向量等,它們被稱為原子(基本上可理解為葉結點)。

注釋只能是單行的,分號是注釋符。

要將一個名為 foo 的變量的值設置為 "bar",只需:

(setq foo "bar") ; setq means "set quoted"

要以 "flim" 與 "flam" 作為參數值調用一個名為 foo-bar 的函數,只需:

(foo-bar "flim" "flam")

要進行算 (0x15 * (8.2 + (7 << 3))) % 2,只需:

(% (* #x15 (+ 8.2 (lsh 7 3))) 2)

也就是說,Lisp 的算數運算用的是前綴表達式,與 Lisp 函數調用方式一致。

Lisp 沒有靜態類型系統;你可以在程序運行時判斷數據的類型。在 Emacs Lisp 中,謂詞函數通常以 p 作為后綴,其含義下文有講。

重點:可以在 Emacs 的 *scratch* 緩沖區中對 Emacs Lisp 表達式進行求值試驗,有以下幾種基本的求值方式:

將光標移到表達式最后一個封閉的括號的后面,然后執行 C-j(即 Ctrl + j 鍵);

將光標移到表達式內部,然后執行 M-C-x(即 Alt + Ctrl + x 鍵);

將光標放到表達式最后一個封閉的括號的后面,然后執行 C-x C-e。

第一種求值方式會將求值結果顯示于 *scratch* 緩沖區,其他兩種方式會將求值結果顯示于 Emacs 的小緩沖區(Minibuffer)。這些求值方式也適用于 Lisp 的原子——數字、字符串、字符以及符號。

詞法要素

Lisp 的詞法標記(原子級別的程序元素)屈指可數。

注釋

注釋是單行的,由分號領起:

(blah blah blah) ; I am a comment

字符串

帶雙引號的就是字符串:

"He's said: \"Emacs Rules\" one time too many."

要讓字符串含有換行符,只需:

"Oh Argentina!

Your little tin of pink meat

Soars o'er the Pampas"

字符

?x 可以獲得字符 x 的 ASCII 碼,這里的 x 可以是任意 ASCII 編碼的字符。例如 ?a 的求值結果是 ASCII 碼 97,而 ?(問號后面是一個空格)的求知結果是 32。

? 后面尾隨的字符,有些需要逃逸,例如 ?\\(,?\\) 以及 ?\\\。

Emacs 22+ 支持 Unicode,這超出了本文范圍。

字符本質上只是整型數值,因此你可以對它們做算術運算(例如,從 ?a 迭代到 ?z)。

數字

整型數的位數是 29 位(并非大家習慣的 32 位);

二進制數,前綴是 #b,例如 #b10010110;

八進制數:#o[0-7]+,例如 #o377;

十六進制數,前綴是 #x,例如 #xabcd,xDEADBEE;

浮點數:位數是 64;

科學計數,例如 5e-10,6.02e23。

在不支持大整數的 Emacs Lisp 中,變量 most-positive-fixnum 與 most-negative-fixnum 分別是最大的與最小的整型數。Emacs 22+ 提供了一個叫做 calc 的大整數/數學庫,以備不時之需。也就是說,Emas Lisp 的算數運算會發生上溢和下溢,如同你在 C 或 Java 中遇到的情況相似。

布爾值

符號 t 是 true,符號 nil 是 false(與 null 同義)。

在 Emacs Lisp 中,nil 是唯一的『假』值,其他非 nil 值皆為『真』值,也就是說像空字串、0、'false 符號以及空向量之類,都是真值。不過,空的列表 '() 與 nil 等價。

數組

Emacs Lisp 有定長數組,名曰『向量』(Vector)??墒褂梅嚼ㄌ杹順嫿A先初始化的字面向量,例如:

[-2 0 2 4 6 8 10]

["No" "Sir" "I" "am" "a" "real" "horse"]

["hi" 22 120 89.6 2748 [3 "a"]]

注意,要使用空白字符來隔離數組中的元素,不要使用逗號。

向量中存儲的數據可以是混合類型,也能夠對向量進行嵌套。通常是使用 make-vector 來構建向量,因為字面向量是單例,對此不要驚訝。

列表

Lisp 重度依賴鏈表,因此專門為它提供了詞法標記。圓括號里的任何東西都是列表,除非你引用了它,否則 Lisp 解釋器就會像函數調用那樣對其進行求值。在 Lisp 中有以下幾種列表引用形式:

(quote (1 2 3)) ;? 產生列表 (1 2 3),并且不會對列表元素進行求值

'(1 2 3)? ; 單引號是 (quote (...)) 形式的簡寫,注意它在左括號之外

(list 1 (+ 1 1) 3) ; 也可以產生列表 (1 2 3),因為 Lisp 解釋器會首先對列表元素進行求值

`(1 ,(+ 1 1) 3)? ; 也可以產生列表 (1 2 3),這是經過『反引號』模板系統產生的

關于列表還有很多東西可說,但是其他人已經都說過了。

序對

你可以直接設定 Lisp 列表的首部與尾部,將其作為 2 個元素的無類型結構來使用。語法是 (head-val . tail-value),不過必須是引用的形式(見上文)。

對于較小的數據集,檢索表的數據結構通常設計為關聯列表(即所謂的 alist),這只不過是帶點的序對所構成的列表而已,例如:

'( (apple . "red")

(banana . "yellow")

(orange . "orange") )

Emacs Lisp 有內建的哈希表,位向量等數據結構,但是它們并沒有語法,你只能通過函數來創建它們。

運算符

有些運算,在其他語言中體現為運算符的形式,而在 Emacs Lisp 中體現為函數的調用。

等號

數值相等判斷:(= 2 (+ 1 1)),單個等號,求值結果為 t 或 nil,也能用于浮點數比較。

數值不相等判斷:(/= 2 3),看上去像相除后賦值,但并不是。

值相等判斷:(eq 'foo 2),類似于 Java 的 ==,適用于整型、符號、限定字串(Interned String)以及對象引用的相等比較。對于浮點數,可使用 eql(或者 =)。

結構的深度相等比較:使用 equal,例如:

(equal '(1 2 (3 4)) (list 1 2 (list 3 (* 2 2)))) ; 求值結果為 t

equal 函數類似于 Java 的 Object.equals(),適用于列表、向量、字符串等類型。

字符串

字符串沒有任何運算符,只是有很多字符串操作函數,下面是幾個常用的函數:

(concat "foo" "bar" "baz")? ; 求值結果為 "foobarbaz"

(string= "foo" "baz")? ; 求值結果為 nil (false),也可以用 equal

(substring "foobar" 0 3) ; 求值結果為 "foo"

(upcase "foobar")? ; 求值結果為 "FOOBAR"

使用 M-x apropos RET \bstring\b RET 可查看所有與字符串操作相關的函數說明。

算術

還是畫個表容易看……

語句

這一節會給出一些類似 Java 語句的代碼片段。它不復雜,僅僅是讓你能夠上手的方子。

if/else

情況 1:無 else 從句((if test-expr expr))

示例:

(if (>= 3 2)

(message "hello there"))

情況 2:else 從句((if test-expr then-expr else-expr))

(if (today-is-friday)? ? ? ? ; test-expr

(message "yay, friday")? ; then-expr

(message "boo, other day")) ; else-expr

如果你需要在 then-expr 中存在多條表達式,可使用 progn——類似于 C 或 Java 的花括號,對這些表達式進行封裝:

(if (zerop 0)

(progn

(do-something)

(do-something-else)

(etc-etc-etc)))

在 else-expr 中沒必要使用 progn,因為 then-expr 之后的所有東西都被視為 else-expr 的一部分,例如:

(if (today-is-friday)

(message "yay, friday")

(message "not friday!")

(non-friday-stuff)

(more-non-friday-stuff))

情況 3: 通過 if 語句的嵌套可實現 else-if 從句,也可以用 cond(下文有講):

(if 'sunday

(message "sunday!")? ? ? ; then-expr

(if 'saturday? ? ? ? ? ? ? ; else-if

(message "saturday!")? ; next then-expr

(message ("weekday!")))) ; final else

情況 4:無 else-if 的多分支表達式——使用 when:

如果沒有 else 從句,可以使用 when,這是一個宏,它提供了隱式的 progn:

(when (> 5 1)

(blah)

(blah-blah)

(blah blah blah))

也可以用 unless,它的測試表達式與 when 反義:

(unless (weekend-p)

(message "another day at work")

(get-back-to-work))

switch

經典的 switch 語句,Emacs Lisp 有兩個版本:cond 與 case。

Emacs Lisp 的 cond 與 case 不具備 switch 的查表優化功能,它們本質上是嵌套的 if-then-else 從句。不過,如果你有多重嵌套,用 cond 或 case 要比 if 表達式更美觀一些。cond 的語法如下:

(cond

(test-1

do-stuff-1)

(test-2

do-stuff-2)

...

(t

do-default-stuff))

do-stuff 部分可以是任意數量的語句,無需用 progn 封裝。

與經典的 switch 不同,cond 可以處理任何測試表達式(它只是依序檢驗這些表達式),并非僅限于數字。這樣所帶來的負面影響是,cond 對數字不進行任何特定的轉換,因此你不得不將它們與某種東西進行比較。下面是字符串比較的示例:

(cond

((equal value "foo")? ; case #1 – notice it's a function call to `equal' so it's in parens

(message "got foo")? ; action 1

(+ 2 2))? ? ? ? ? ? ; return value for case 1

((equal value "bar")? ; case #2 – also a function call (to `+')

nil)? ? ? ? ? ? ? ? ; return value for case 2

(t? ? ? ? ? ? ? ? ? ? ; default case – not a function call, just literal true

'hello))? ? ? ? ? ? ; return symbol 'hello

末尾的 t 從句是可選的。若某個從句匹配成功,那么這個從句的求值結果便是整個 cond 表達式的求值結果。

Emacs 'cl(Common Lisp)包(譯注:Emacs Lisp 手冊推薦使用 'cl-lib ,因為 'cl 過時了),提供了 case,它能夠進行數值或符號比較,因此它看上去比較像標準的 switch:

(case 12

(5 "five")

(1 "one")

(12 "twelve")

(otherwise

"I only know five, one and twelve."))? ; result:? "twelve"

使用 case,默認從句可以用 t,也可以用 otherwise,但它必須最后出現。

使用 case 更干凈一些,但是 cond 更通用。

while

Emacs Lisp 的 while 函數相對正常一些,其語法為 (while test body-forms)。

例如,可在 *scratch* 緩沖區中執行以下代碼:

(setq x 10

total 0)

(while (plusp x)? ; 只要 x 是正數

(incf total x)? ; total += x

(decf x))? ? ? ; x -= 1

在上述代碼中,我們首先設置了兩個全局變量 x=10 與 total=0,然后執行循環。循環結束后,可對 total 進行求值,結果為 55(從 1 到 10 求和結果)。

break/continue

Lisp 的 cache/throw 能夠實現控制流的向上級轉移,它與 Java 或 C++ 的異常處理相似,盡管功能上要弱一些。

在 Emacs Lisp 中要 break 一個循環,可以將 (cache 'break ...) 置于循環外部,然后在循環內部需要中斷的地方放置 (throw 'break value),例如:

符號 'break 不是 Lisp 語法,而是自己取的名字——要取容易理解的名字,譬如對于多重循環,可在 cache 表達式中用 'break-outer 與 'break-inner 之類的名字。

如果你不關心 while 循環的『返回值』,可以 (throw 'break nil)。

要實現循環中的 continue,可將 cache 置入循環內部之首。例如,對從 1 到 99 的整數求和,并且在該過程中避開能被 5 整除的數(這是個蹩腳的例子,只是為了演示 continue 的用法):

可將這些示例組合起來,在同一個循環內實現 break 與 continue:

上面的循環的計算結果為 4000,即 total 的值。要得到這個結果,還有更好的計算方式,不過我需要足夠簡單的東西來講述如何在 Lisp 中實現 break 與 continue。

catch/throw 機制能夠像異常那樣跨函數使用。不過,它的設計并非真的是面向異常或錯誤處理——Emacs Lisp 另外有一套機制來做這些事,也就是后文的 try/catch 這一節所討論東西。你應該習慣在 Emacs Lisp 代碼中使用 catch/throw 進行控制流轉移。

do/while

Emacs Lisp 中最容易使用的循環機制是 Common Lisp 包提供的 loop 宏。要使用這個宏,需要加載 cl-lib 包:

(require 'cl-lib) ; 獲取大量的 Common Lisp 里的好東西

loop 宏是帶有大量特征的微語言,值得好好觀摩一番。我主要用它來演示如何構造一些基本的循環。

基于 loop 所實現的 do/while 機制如下:

(loop do

(setq x (1+ x))

while

(< x 10))

在 do 與 while 之間可以有任意數量的 Lisp 表達式。

for

C 風格的 for 循環由四種成分構成:變量初始化,循環體,條件測試以及自增。用 loop 宏也能模擬出這種循環結構。例如,像下面的 JavaScript 的循環結構:

var result = [];

for (var i = 10, j = 0; j <= 10; i--, j += 2) {

result.push(i+j);

}

對于這樣的循環結構,基于 Emacs Lisp 的 loop 可將其模擬為:

(loop with result = '()? ? ? ? ; 初始化:只被執行一次

for i downfrom 10? ? ? ? ; i 從 10 遞減

for j from 0 by 2? ? ? ? ; j 從 0 開始自增 2

while (< j 10)? ? ? ? ? ? ; j >= 10 時循環終止

do

(push (+ i j) result)? ? ; 將 i + j 的求值結果入棧

finally

return (nreverse result)) ; 將 result 中存儲的數據次序逆轉,然后作為求值結果

由于 loop 表達式有很多選項,這樣寫雖然繁瑣,但是容易理解。

注意,上述代碼中,loop 聲明了一個數組 result,然后將它作為『返回』值。事實上,loop 也能處理循環之外的變量,這種情況下就不需要 finally return 從句了。

loop 宏出人意料的靈活。有關它的全面介紹超出了本文范疇,但是如果你想駕馭 Emacs Lisp,那么你有必要花一些時間揣摩一下它。

for .. in

如果你迭代訪問一個集合,Java 提供了『智能』的 for 循環,JavaScript 提供了 for .. in 與 for each .. in。這些,在 Lisp 里也能做到,但是你可能需要對 loop 宏有很好的理解,它可以為迭代過程提供一站式服務。

最基本的方式是 loop for var in sequence,然后針對特定結果做一些處理。例如,你可以將 sequence 中的東西收集起來(或者將一個函數作用與它們):

(loop for i in '(1 2 3 4 5 6)

collect (* i i))? ? ? ? ? ? ;? 結果為 (1 4 9 16 25 36)

loop 宏能夠迭代列表元素、列表單元、向量、哈希鍵序列、哈希值序列、緩沖區、窗口、窗框、符號以及你想遍歷的任何東西。請參閱 Emacs 手冊獲得更多信息。

函數

用 defun(define function)定義函數。

語法:(defun 函數名 參數列表 [可選的文檔化注釋] 函數體)

(defun square (x)

"Return X squared."

(* x x))

對于無參函數,只需讓參數列表為空即可:

(defun hello ()

"Print the string `hello' to the minibuffer."

(message "hello!"))

函數體可由任意數量的表達式構成,函數的返回值是最后那個表達式的求值結果。由于函數的返回類型沒有聲明,因此有必要在文檔化注釋中注明函數的返回類型。對函數進行求值之后,其文檔化注釋可通過 M-x describe-function 查看。

Emacs Lisp 不支持函數/方法的重載,但是它支持 Python 和 Ruby 所提供的那種可選參數與 rest 參數。你可以使用 Common Lisp 化的參數列表,在使用 defun* 宏代替 defun 時,可支持關鍵字參數(keyword arguments,見后文的 defstruct 一節)。defun*宏也允許使用 (return "foo") 這種控制流轉移方式來代替 catch/throw 機制。

如果你像讓自己定義的函數能夠作為 M-x 命令來執行,只需將 (interactive) 作為函數體內的第一個表達式,亦即位于文檔化注釋字串之后。

局部變量

在函數中要聲明局部變量,可使用 let 表達式。基本語法是 (let var-decl var-decl):

(let ((name1 value1)

(name2 value2)

name3

name4

(name5 value5)

name6

...))

每個 var-decl 要么僅僅是變量名,要么就是 (變量名 初始值) 形式。初始化的變量與未初始化的變量出現的次序是任意的。未初始化的變量,其值為 nil。

在一個函數中可以有多條 let 表達式,但是為了性能起見,通常是將變量聲明都放到開始的 let 表達式中,這樣會快一點。不過,你應該寫清晰的代碼。

引用參數

C++ 有引用參數,函數可以修改調用者堆棧中的變量。Java 沒有這個功能,因此有時你不得不迂回的向函數傳遞單元素數組,或一個對象,或別的什么東西來模擬這個功能。

Emacs Lisp 也沒有真正的向函數傳遞引用的機制,但是它有動態域(Dynamic Scope),這意味著你可以用任何方式修改位于調用者堆棧中的變量。看下面這兩個函數:

(defun foo ()

(let ((x 6))? ; 定義了一個(棧中的)局部變量 x,將其初始化為 6

(bar)? ? ? ; 調用 bar 函數

x))? ? ? ? ; 返回 x

(defun bar ()

(setq x 7))? ; 在調用者的棧中搜索 x 并修改它的值

如果你調用了 (foo),返回值為 7。

動態域通常被認為是近乎邪惡的壞設計,但是它有時也能派上用場。即使它真的很糟糕,通過它也能了解一些 Emacs 的內幕。

譯注:Emacs 24 對詞法域(Lexical Scope)提供了支持,但是 Emacs Lisp 默認依然是動態域。要開啟詞法域功能,可在 .el 文件的第一行添加以下信息:

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

return

Lisp 函數默認是返回最后一個被求值的表達式的結果。通過一些構造技巧,也可以讓每個可能的返回結果安排在函數的尾部位置。例如:

上述 Lisp 函數 day-name 的返回值是最后一個表達式的求值結果,因此無論我們怎么嵌套 if,都能自動產生一個結果返回,因此這里不需要顯式的 return 語句。

不過,有時用 if 嵌套的方式來重構函數的返回形式會不太方便,它較適合一些小的函數。對于一些規模較大并且嵌套較深的函數,你可能希望函數能夠在較早的時機返回。在 Emacs Lisp 中,這一需求可基于 break 與 continue 來實現。上文中的 day-name 可重構為:

(defun day-name ()

(let ((date (calendar-day-of-week

(calendar-current-date))))? ; 0-6

(catch 'return

(case date

(0

(throw 'return "Sunday"))

(6

(throw 'return "Saturday"))

(t

(throw 'return "weekday"))))))

顯然,使用 catch/throw 會降低程序性能,但是有時你會需要用它來消除太深的嵌套結構。

try/catch

前文已經講了 catch/throw,它類似于異常,可用于控制流轉移。

Emacs 真正的錯誤處理機制叫做『條件』系統,本文不打算對此予以全面介紹,僅涉及如何捕捉異常以及如何忽略它們。

下面是一個一般化的 condition-case 結構,而且我也給出了 Java 的等價描述。

如果你想讓 cache 塊為空,可使用 ignore-errorse:

(ignore-errors

(do-something)

(do-something-else))

有時你的啟動文件(譯注:可能是 .emacs 或init.el文件)可能不是總是正確工作??梢允褂?ignore-errors 來封裝 Emacs Lisp 代碼,這樣即使被封裝的代碼出錯,也不會導致 Emacs 啟動失敗。

condition-case nil 的意思是『錯誤信息不賦給已命名的變量』。Emacs Lisp 允許你捕獲不同的錯誤類別并對錯誤信息進行排查。這方面的知識請從 Emacs Lisp 手冊獲取。

在 condition-case 塊內如果存在多條表達式需要求值,必須用 progn 將它們封裝起來。

condition-case 不會捕捉 throw 扔出來的值——這兩個系統是彼此獨立的。

try/finally

Emacs Lisp 提供了類似 finally 的功能 unwind-protect:

與 condition-case 相似,unwind-protect 接受單個體塊(body-form,譯注:try 部分),后面跟隨著一條或多條善后的表達式,因此你需要用 progn 將體塊內的表達式封裝起來。

try/catch/finally

如果讓 condition-case(等價于 try/catch)成為 unwind-protect(等價于 try/finally)的體塊,那么就可以得到try/catch/finally 的效果:

(unwind-protect? ? ? ? ? ? ? ? ; finally

(condition-case nil? ? ? ? ; try

(progn? ? ? ? ? ? ? ? ? ; {

(do-something)? ? ? ? ;? body-1

(do-something-else))? ;? body-2 }

(error? ? ? ? ? ? ? ? ? ? ; catch

(message "oh no!")? ? ? ; { catch 1

(poop-pants)))? ? ? ? ? ;? catch 2 }

(first-finally-expr)? ? ? ? ? ; { finally 1

(second-finally-expr))? ? ? ? ;? finally 2 }

Emacs Lisp 不是標準意義上的面向對象編程語言,它沒有類、繼承以及多態等語法。Emacs 的 Common Lisp 包(現在的 cl-lib)提供了一個有用的特性 defstruct,通過它可以實現簡單的 OOP 支持。下面我會給出一個簡單的示例。

下面的 Emacs Lisp 代碼與 Java 代碼本質上是等價的:

defstruct 宏提供了一個靈活的默認構造器,但是你也可以根據自己的需要來定義相適的構造器。

defstruct 宏在創建對象實例時,也創建了一組判定函數,它們的用法如下:

(person-p (make-person))

t

(employee-p (make-person))

nil

(employee-p (make-employee))

t

(person-p (make-employee))? ; yes, it inherits from person

t

Java 在對象構造器方面可能挺糟糕,不過 Emacs 在域(類成員)的設置方面挺糟糕。要設置類(結構體)的域,必須使用 setf 函數,然后將類名作為域名的前綴:

這樣看上去,Lisp 并不是太糟糕,但是在實踐中(因為 Emacs Lisp 不支持命名空間,并且也沒有 with-slots 宏),你會被卷入很長的類名與域名中的,例如:

(setf (js2-compiler-data-current-script-or-function compiler-data) current-script

(js2-compiler-data-line-number compiler-data) current-line

(js2-compiler-data-allow-member-expr-as-function-name compiler-data) allow

(js2-compiler-data-language-version compiler-data) language-version)

要獲取域的值,需要將類名與域名連接起來,然后作為函數來用:

(person-name steve) ; yields "Steve"

defstruct 還能做很多事——它的功能非常得體,該考慮的事都考慮了,盡管它沒能形成一個完善的面向對象系統。

緩沖區即類

在 Emacs Lisp 編程中,將緩沖區視為類的實例往往很有用。因為 Emacs 支持緩沖區級別的局部變量的概念——無論變量以那種方式設置(譯注,例如通過 setq 設置的變量),它們都會自動變成緩沖區內部的局部變量。因此,這些變量的行為就像是被封裝在實例中的變量。

可以用 make-variable-buffer-local 函數將一個變量聲明為緩沖區級別的局部變量,通常這個函數會在 devar 或 defconst 之后出現(見下文)。

變量

在 Emacs Lisp 中,可以用 defvar 或 defconst 聲明變量,也可以為變量提供文檔化注釋:

(defconst pi 3.14159 "A gross approximation of pi.")

語法為 (defvar 變量名 值 [文檔化注釋])。

不過,會讓你大跌眼鏡的是,defconst 定義的是變量,而 defvar 定義的是常量,至少在重新求值時是這樣。要改變 defvar 變量的值,需要使用 makeunbound 來解除變量的綁定。不過,總是可以使用 setq 來修改 defvar 或 defconst 變量的值。這兩種變量形式,僅有的區別是,defconst 可以表達一種意圖:你定義的是一個常量。

可以使用 setq 來創建全新的變量,但是如果用 defvar,Emacs Lisp 的字節碼編譯器能捕捉到一些錯誤信息。

總結

Emacs Lisp 是一種真正的編程語言。它有編譯器、調試器、性能分析器、效果顯示器、運行時文檔、庫、輸入/輸出、網絡、進程控制等。它有很多東西值得學習,但是我希望這篇小文章能夠讓你向它邁出第一步。

無論 Emacs Lisp 有多么古怪和煩人,只要你上手了,它就能讓你體驗到編程的快樂。作為一種編程語言,它并不偉大,而且每個人都期望它是 Common Lisp 或 Scheme 或其他某種更好的 Lisp 方言。有些人甚至認為它根本不是 Lisp。

但是,要定制你的 Emacs,或者修復你從他人那里得到的 Emacs Lisp 代碼,那么 Emacs Lisp 就會非常非常有用。四兩 Emacs Lisp 可撥千鈞之物。

正在學習 Emacs Lisp 的你,如果覺得這份文檔是有用的,請告訴我。如果你打算寫一些 Emacs 擴展,可以告訴我你希望我的下一篇文檔要寫什么。有興趣的化,我會再繼續這個 Emergency Elisp 系列。

Good Luck!

譯注:作者似乎沒有再寫下去。xahlee 在 http://ergoemacs.org/emacs/elisp.html 所寫的系列文檔可作為進階教程。

原文出處: segmentfault

原文地址: https://segmentfault.com/a/1190000004910645

原文時間: 2016-04-10 19:22

本文地址: http://emacsist.com/10845

整理時間: 2016-04-22 03:25

本文由 Hick 整理,轉載請保留以上信息;

The articles on this site come from Internet, thanks to all the original authors.

If anything about COPYRIGHT, or LEFT, please contact Emacsist at gmail dot com .

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容