[譯] Emacs Lisp 速成

From:http://emacsist.com/10845

點(diǎn) 這里 查看更多 Emacs 相關(guān)推薦文章 或 最新 Emacs 圈的動(dòng)態(tài). 歡迎關(guān)注微信公眾賬號(hào): Emacsist

原文見(jiàn) 『Emergency Elisp』惨奕。

你用著 Emacs 卻不懂 Lisp 吧田巴?歡迎閱讀這篇 Emacs Lisp 入門(mén)教程葬馋!它應(yīng)該能夠助你搞定 Emacs Lisp补胚,從而更加自如的駕馭 Emacs。

有很多種學(xué)習(xí) Lisp 的方式弹沽,其中有一些方式要比其他方式更為 Lisp檀夹。我喜歡的方式是,基于 C++ 或 Java 的編程經(jīng)驗(yàn)來(lái)學(xué)習(xí) Lisp策橘。

本文重點(diǎn)放在 Emacs Lisp 語(yǔ)言本身炸渡,因?yàn)樗攀亲铍y的部分,至于成噸的 Emacs 的 API 的用法丽已,你可以通過(guò)閱讀 Emacs Lisp 文檔來(lái)學(xué)習(xí)蚌堵。

有些事(例如編寫(xiě)生成代碼的代碼)是 Lisp 擅長(zhǎng)的,而有些事(例如算數(shù)表達(dá)式)是它不擅長(zhǎng)的沛婴。我不打算談?wù)?Lisp 是好還是壞吼畏,只關(guān)心如何用它編程。Emacs Lisp 跟其他語(yǔ)言差不多瘸味,最終你會(huì)習(xí)慣它的宫仗。

許多介紹 Lisp 的文章或書(shū)籍嘗試給你展現(xiàn) Lisp 之『道』,飽含著奉承旁仿、贊頌以及瑜伽之類(lèi)的東西藕夫。事實(shí)上孽糖,一開(kāi)始我真正想要的是一本簡(jiǎn)單的 cookbook,它講述的是如何用 Lisp 來(lái)做一些我日常生活中的事毅贮。本文便立意于此办悟,它講述的是大致是如何用 Emacs Lisp 來(lái)寫(xiě) C,Java 或 JavaScript 就能寫(xiě)的那些代碼滩褥。

我們開(kāi)始吧病蛉,看看我能夠?qū)⑦@篇文章寫(xiě)的多么短小。我要從挺無(wú)聊的詞法標(biāo)記瑰煎、運(yùn)算符開(kāi)始铺然,然后講述如何實(shí)現(xiàn)一些眾所周知的語(yǔ)句、聲明以及一些程序結(jié)構(gòu)酒甸。

快速開(kāi)始

Lisp 代碼是像 (+ 2 3) 的嵌套的括號(hào)表達(dá)式魄健。這些表達(dá)式有時(shí)被稱(chēng)為 form(塊)。

也有些不帶括號(hào)的代碼插勤,譬如字符串沽瘦、數(shù)字、符號(hào)(必須以單引號(hào)為前綴农尖,例如 'foo)析恋、向量等,它們被稱(chēng)為原子(基本上可理解為葉結(jié)點(diǎn))盛卡。

注釋只能是單行的助隧,分號(hào)是注釋符。

要將一個(gè)名為 foo 的變量的值設(shè)置為 "bar"窟扑,只需:

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

要以 "flim" 與 "flam" 作為參數(shù)值調(diào)用一個(gè)名為 foo-bar 的函數(shù)喇颁,只需:

(foo-bar "flim" "flam")

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

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

也就是說(shuō)嚎货,Lisp 的算數(shù)運(yùn)算用的是前綴表達(dá)式,與 Lisp 函數(shù)調(diào)用方式一致蔫浆。

Lisp 沒(méi)有靜態(tài)類(lèi)型系統(tǒng)殖属;你可以在程序運(yùn)行時(shí)判斷數(shù)據(jù)的類(lèi)型。在 Emacs Lisp 中瓦盛,謂詞函數(shù)通常以 p 作為后綴洗显,其含義下文有講。

重點(diǎn):可以在 Emacs 的 *scratch* 緩沖區(qū)中對(duì) Emacs Lisp 表達(dá)式進(jìn)行求值試驗(yàn)原环,有以下幾種基本的求值方式:

將光標(biāo)移到表達(dá)式最后一個(gè)封閉的括號(hào)的后面挠唆,然后執(zhí)行 C-j(即 Ctrl + j 鍵);

將光標(biāo)移到表達(dá)式內(nèi)部嘱吗,然后執(zhí)行 M-C-x(即 Alt + Ctrl + x 鍵)玄组;

將光標(biāo)放到表達(dá)式最后一個(gè)封閉的括號(hào)的后面,然后執(zhí)行 C-x C-e。

第一種求值方式會(huì)將求值結(jié)果顯示于 *scratch* 緩沖區(qū)俄讹,其他兩種方式會(huì)將求值結(jié)果顯示于 Emacs 的小緩沖區(qū)(Minibuffer)哆致。這些求值方式也適用于 Lisp 的原子——數(shù)字、字符串患膛、字符以及符號(hào)摊阀。

詞法要素

Lisp 的詞法標(biāo)記(原子級(jí)別的程序元素)屈指可數(shù)。

注釋

注釋是單行的踪蹬,由分號(hào)領(lǐng)起:

(blah blah blah) ; I am a comment

字符串

帶雙引號(hào)的就是字符串:

"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 的求值結(jié)果是 ASCII 碼 97豌鹤,而 ?(問(wèn)號(hào)后面是一個(gè)空格)的求知結(jié)果是 32。

? 后面尾隨的字符枝缔,有些需要逃逸布疙,例如 ?\\(,?\\) 以及 ?\\\愿卸。

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

字符本質(zhì)上只是整型數(shù)值趴荸,因此你可以對(duì)它們做算術(shù)運(yùn)算(例如儒溉,從 ?a 迭代到 ?z)。

數(shù)字

整型數(shù)的位數(shù)是 29 位(并非大家習(xí)慣的 32 位)发钝;

二進(jìn)制數(shù)顿涣,前綴是 #b,例如 #b10010110酝豪;

八進(jìn)制數(shù):#o[0-7]+涛碑,例如 #o377;

十六進(jìn)制數(shù)孵淘,前綴是 #x蒲障,例如 #xabcd,xDEADBEE瘫证;

浮點(diǎn)數(shù):位數(shù)是 64揉阎;

科學(xué)計(jì)數(shù),例如 5e-10背捌,6.02e23毙籽。

在不支持大整數(shù)的 Emacs Lisp 中,變量 most-positive-fixnum 與 most-negative-fixnum 分別是最大的與最小的整型數(shù)毡庆。Emacs 22+ 提供了一個(gè)叫做 calc 的大整數(shù)/數(shù)學(xué)庫(kù)坑赡,以備不時(shí)之需巡扇。也就是說(shuō),Emas Lisp 的算數(shù)運(yùn)算會(huì)發(fā)生上溢和下溢垮衷,如同你在 C 或 Java 中遇到的情況相似厅翔。

布爾值

符號(hào) t 是 true,符號(hào) nil 是 false(與 null 同義)搀突。

在 Emacs Lisp 中刀闷,nil 是唯一的『假』值,其他非 nil 值皆為『真』值仰迁,也就是說(shuō)像空字串甸昏、0、'false 符號(hào)以及空向量之類(lèi)徐许,都是真值施蜜。不過(guò),空的列表 '() 與 nil 等價(jià)雌隅。

數(shù)組

Emacs Lisp 有定長(zhǎng)數(shù)組翻默,名曰『向量』(Vector)∏∑穑可使用方括號(hào)來(lái)構(gòu)建預(yù)先初始化的字面向量修械,例如:

[-2 0 2 4 6 8 10]

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

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

注意,要使用空白字符來(lái)隔離數(shù)組中的元素检盼,不要使用逗號(hào)肯污。

向量中存儲(chǔ)的數(shù)據(jù)可以是混合類(lèi)型,也能夠?qū)ο蛄窟M(jìn)行嵌套吨枉。通常是使用 make-vector 來(lái)構(gòu)建向量蹦渣,因?yàn)樽置嫦蛄渴菃卫瑢?duì)此不要驚訝貌亭。

列表

Lisp 重度依賴(lài)鏈表柬唯,因此專(zhuān)門(mén)為它提供了詞法標(biāo)記。圓括號(hào)里的任何東西都是列表属提,除非你引用了它权逗,否則 Lisp 解釋器就會(huì)像函數(shù)調(diào)用那樣對(duì)其進(jìn)行求值美尸。在 Lisp 中有以下幾種列表引用形式:

(quote (1 2 3)) ;? 產(chǎn)生列表 (1 2 3)冤议,并且不會(huì)對(duì)列表元素進(jìn)行求值

'(1 2 3)? ; 單引號(hào)是 (quote (...)) 形式的簡(jiǎn)寫(xiě),注意它在左括號(hào)之外

(list 1 (+ 1 1) 3) ; 也可以產(chǎn)生列表 (1 2 3)师坎,因?yàn)?Lisp 解釋器會(huì)首先對(duì)列表元素進(jìn)行求值

`(1 ,(+ 1 1) 3)? ; 也可以產(chǎn)生列表 (1 2 3)恕酸,這是經(jīng)過(guò)『反引號(hào)』模板系統(tǒng)產(chǎn)生的

關(guān)于列表還有很多東西可說(shuō),但是其他人已經(jīng)都說(shuō)過(guò)了胯陋。

序?qū)?/p>

你可以直接設(shè)定 Lisp 列表的首部與尾部蕊温,將其作為 2 個(gè)元素的無(wú)類(lèi)型結(jié)構(gòu)來(lái)使用袱箱。語(yǔ)法是 (head-val . tail-value),不過(guò)必須是引用的形式(見(jiàn)上文)义矛。

對(duì)于較小的數(shù)據(jù)集发笔,檢索表的數(shù)據(jù)結(jié)構(gòu)通常設(shè)計(jì)為關(guān)聯(lián)列表(即所謂的 alist),這只不過(guò)是帶點(diǎn)的序?qū)λ鶚?gòu)成的列表而已凉翻,例如:

'( (apple . "red")

(banana . "yellow")

(orange . "orange") )

Emacs Lisp 有內(nèi)建的哈希表了讨,位向量等數(shù)據(jù)結(jié)構(gòu),但是它們并沒(méi)有語(yǔ)法制轰,你只能通過(guò)函數(shù)來(lái)創(chuàng)建它們前计。

運(yùn)算符

有些運(yùn)算,在其他語(yǔ)言中體現(xiàn)為運(yùn)算符的形式垃杖,而在 Emacs Lisp 中體現(xiàn)為函數(shù)的調(diào)用男杈。

等號(hào)

數(shù)值相等判斷:(= 2 (+ 1 1)),單個(gè)等號(hào)调俘,求值結(jié)果為 t 或 nil伶棒,也能用于浮點(diǎn)數(shù)比較。

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

值相等判斷:(eq 'foo 2)侧巨,類(lèi)似于 Java 的 ==舅锄,適用于整型、符號(hào)司忱、限定字串(Interned String)以及對(duì)象引用的相等比較皇忿。對(duì)于浮點(diǎn)數(shù),可使用 eql(或者 =)坦仍。

結(jié)構(gòu)的深度相等比較:使用 equal鳍烁,例如:

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

equal 函數(shù)類(lèi)似于 Java 的 Object.equals(),適用于列表繁扎、向量幔荒、字符串等類(lèi)型。

字符串

字符串沒(méi)有任何運(yùn)算符梳玫,只是有很多字符串操作函數(shù)爹梁,下面是幾個(gè)常用的函數(shù):

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

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

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

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

使用 M-x apropos RET \bstring\b RET 可查看所有與字符串操作相關(guān)的函數(shù)說(shuō)明提澎。

算術(shù)

還是畫(huà)個(gè)表容易看……

語(yǔ)句

這一節(jié)會(huì)給出一些類(lèi)似 Java 語(yǔ)句的代碼片段姚垃。它不復(fù)雜,僅僅是讓你能夠上手的方子盼忌。

if/else

情況 1:無(wú) 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 中存在多條表達(dá)式积糯,可使用 progn——類(lèi)似于 C 或 Java 的花括號(hào)掂墓,對(duì)這些表達(dá)式進(jìn)行封裝:

(if (zerop 0)

(progn

(do-something)

(do-something-else)

(etc-etc-etc)))

在 else-expr 中沒(méi)必要使用 progn,因?yàn)?then-expr 之后的所有東西都被視為 else-expr 的一部分看成,例如:

(if (today-is-friday)

(message "yay, friday")

(message "not friday!")

(non-friday-stuff)

(more-non-friday-stuff))

情況 3: 通過(guò) if 語(yǔ)句的嵌套可實(shí)現(xiàn) else-if 從句君编,也可以用 cond(下文有講):

(if 'sunday

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

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

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

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

情況 4:無(wú) else-if 的多分支表達(dá)式——使用 when:

如果沒(méi)有 else 從句,可以使用 when川慌,這是一個(gè)宏啦粹,它提供了隱式的 progn:

(when (> 5 1)

(blah)

(blah-blah)

(blah blah blah))

也可以用 unless,它的測(cè)試表達(dá)式與 when 反義:

(unless (weekend-p)

(message "another day at work")

(get-back-to-work))

switch

經(jīng)典的 switch 語(yǔ)句窘游,Emacs Lisp 有兩個(gè)版本:cond 與 case唠椭。

Emacs Lisp 的 cond 與 case 不具備 switch 的查表優(yōu)化功能,它們本質(zhì)上是嵌套的 if-then-else 從句忍饰。不過(guò)贪嫂,如果你有多重嵌套,用 cond 或 case 要比 if 表達(dá)式更美觀一些艾蓝。cond 的語(yǔ)法如下:

(cond

(test-1

do-stuff-1)

(test-2

do-stuff-2)

...

(t

do-default-stuff))

do-stuff 部分可以是任意數(shù)量的語(yǔ)句力崇,無(wú)需用 progn 封裝。

與經(jīng)典的 switch 不同赢织,cond 可以處理任何測(cè)試表達(dá)式(它只是依序檢驗(yàn)這些表達(dá)式)亮靴,并非僅限于數(shù)字。這樣所帶來(lái)的負(fù)面影響是于置,cond 對(duì)數(shù)字不進(jìn)行任何特定的轉(zhuǎn)換茧吊,因此你不得不將它們與某種東西進(jìn)行比較。下面是字符串比較的示例:

(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 從句是可選的八毯。若某個(gè)從句匹配成功搓侄,那么這個(gè)從句的求值結(jié)果便是整個(gè) cond 表達(dá)式的求值結(jié)果。

Emacs 'cl(Common Lisp)包(譯注:Emacs Lisp 手冊(cè)推薦使用 'cl-lib 话速,因?yàn)?'cl 過(guò)時(shí)了)讶踪,提供了 case,它能夠進(jìn)行數(shù)值或符號(hào)比較泊交,因此它看上去比較像標(biāo)準(zhǔn)的 switch:

(case 12

(5 "five")

(1 "one")

(12 "twelve")

(otherwise

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

使用 case乳讥,默認(rèn)從句可以用 t,也可以用 otherwise廓俭,但它必須最后出現(xiàn)云石。

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

while

Emacs Lisp 的 while 函數(shù)相對(duì)正常一些留晚,其語(yǔ)法為 (while test body-forms)。

例如告嘲,可在 *scratch* 緩沖區(qū)中執(zhí)行以下代碼:

(setq x 10

total 0)

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

(incf total x)? ; total += x

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

在上述代碼中错维,我們首先設(shè)置了兩個(gè)全局變量 x=10 與 total=0,然后執(zhí)行循環(huán)橄唬。循環(huán)結(jié)束后赋焕,可對(duì) total 進(jìn)行求值,結(jié)果為 55(從 1 到 10 求和結(jié)果)仰楚。

break/continue

Lisp 的 cache/throw 能夠?qū)崿F(xiàn)控制流的向上級(jí)轉(zhuǎn)移隆判,它與 Java 或 C++ 的異常處理相似,盡管功能上要弱一些僧界。

在 Emacs Lisp 中要 break 一個(gè)循環(huán)侨嘀,可以將 (cache 'break ...) 置于循環(huán)外部,然后在循環(huán)內(nèi)部需要中斷的地方放置 (throw 'break value)捂襟,例如:

符號(hào) 'break 不是 Lisp 語(yǔ)法咬腕,而是自己取的名字——要取容易理解的名字,譬如對(duì)于多重循環(huán)葬荷,可在 cache 表達(dá)式中用 'break-outer 與 'break-inner 之類(lèi)的名字涨共。

如果你不關(guān)心 while 循環(huán)的『返回值』,可以 (throw 'break nil)宠漩。

要實(shí)現(xiàn)循環(huán)中的 continue举反,可將 cache 置入循環(huán)內(nèi)部之首。例如扒吁,對(duì)從 1 到 99 的整數(shù)求和火鼻,并且在該過(guò)程中避開(kāi)能被 5 整除的數(shù)(這是個(gè)蹩腳的例子,只是為了演示 continue 的用法):

可將這些示例組合起來(lái)雕崩,在同一個(gè)循環(huán)內(nèi)實(shí)現(xiàn) break 與 continue:

上面的循環(huán)的計(jì)算結(jié)果為 4000凝危,即 total 的值。要得到這個(gè)結(jié)果晨逝,還有更好的計(jì)算方式蛾默,不過(guò)我需要足夠簡(jiǎn)單的東西來(lái)講述如何在 Lisp 中實(shí)現(xiàn) break 與 continue。

catch/throw 機(jī)制能夠像異常那樣跨函數(shù)使用捉貌。不過(guò)支鸡,它的設(shè)計(jì)并非真的是面向異常或錯(cuò)誤處理——Emacs Lisp 另外有一套機(jī)制來(lái)做這些事趁窃,也就是后文的 try/catch 這一節(jié)所討論東西牧挣。你應(yīng)該習(xí)慣在 Emacs Lisp 代碼中使用 catch/throw 進(jìn)行控制流轉(zhuǎn)移。

do/while

Emacs Lisp 中最容易使用的循環(huán)機(jī)制是 Common Lisp 包提供的 loop 宏醒陆。要使用這個(gè)宏瀑构,需要加載 cl-lib 包:

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

loop 宏是帶有大量特征的微語(yǔ)言,值得好好觀摩一番刨摩。我主要用它來(lái)演示如何構(gòu)造一些基本的循環(huán)寺晌。

基于 loop 所實(shí)現(xiàn)的 do/while 機(jī)制如下:

(loop do

(setq x (1+ x))

while

(< x 10))

在 do 與 while 之間可以有任意數(shù)量的 Lisp 表達(dá)式世吨。

for

C 風(fēng)格的 for 循環(huán)由四種成分構(gòu)成:變量初始化,循環(huán)體呻征,條件測(cè)試以及自增耘婚。用 loop 宏也能模擬出這種循環(huán)結(jié)構(gòu)。例如陆赋,像下面的 JavaScript 的循環(huán)結(jié)構(gòu):

var result = [];

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

result.push(i+j);

}

對(duì)于這樣的循環(huán)結(jié)構(gòu)沐祷,基于 Emacs Lisp 的 loop 可將其模擬為:

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

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

for j from 0 by 2? ? ? ? ; j 從 0 開(kāi)始自增 2

while (< j 10)? ? ? ? ? ? ; j >= 10 時(shí)循環(huán)終止

do

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

finally

return (nreverse result)) ; 將 result 中存儲(chǔ)的數(shù)據(jù)次序逆轉(zhuǎn),然后作為求值結(jié)果

由于 loop 表達(dá)式有很多選項(xiàng)攒岛,這樣寫(xiě)雖然繁瑣赖临,但是容易理解。

注意灾锯,上述代碼中兢榨,loop 聲明了一個(gè)數(shù)組 result,然后將它作為『返回』值挠进。事實(shí)上色乾,loop 也能處理循環(huán)之外的變量,這種情況下就不需要 finally return 從句了领突。

loop 宏出人意料的靈活暖璧。有關(guān)它的全面介紹超出了本文范疇,但是如果你想駕馭 Emacs Lisp君旦,那么你有必要花一些時(shí)間揣摩一下它澎办。

for .. in

如果你迭代訪(fǎng)問(wèn)一個(gè)集合,Java 提供了『智能』的 for 循環(huán)金砍,JavaScript 提供了 for .. in 與 for each .. in局蚀。這些,在 Lisp 里也能做到恕稠,但是你可能需要對(duì) loop 宏有很好的理解琅绅,它可以為迭代過(guò)程提供一站式服務(wù)。

最基本的方式是 loop for var in sequence鹅巍,然后針對(duì)特定結(jié)果做一些處理千扶。例如,你可以將 sequence 中的東西收集起來(lái)(或者將一個(gè)函數(shù)作用與它們):

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

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

loop 宏能夠迭代列表元素骆捧、列表單元澎羞、向量、哈希鍵序列敛苇、哈希值序列妆绞、緩沖區(qū)、窗口、窗框括饶、符號(hào)以及你想遍歷的任何東西株茶。請(qǐng)參閱 Emacs 手冊(cè)獲得更多信息。

函數(shù)

用 defun(define function)定義函數(shù)巷帝。

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

(defun square (x)

"Return X squared."

(* x x))

對(duì)于無(wú)參函數(shù)忌卤,只需讓參數(shù)列表為空即可:

(defun hello ()

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

(message "hello!"))

函數(shù)體可由任意數(shù)量的表達(dá)式構(gòu)成扫夜,函數(shù)的返回值是最后那個(gè)表達(dá)式的求值結(jié)果楞泼。由于函數(shù)的返回類(lèi)型沒(méi)有聲明,因此有必要在文檔化注釋中注明函數(shù)的返回類(lèi)型笤闯。對(duì)函數(shù)進(jìn)行求值之后堕阔,其文檔化注釋可通過(guò) M-x describe-function 查看。

Emacs Lisp 不支持函數(shù)/方法的重載颗味,但是它支持 Python 和 Ruby 所提供的那種可選參數(shù)與 rest 參數(shù)超陆。你可以使用 Common Lisp 化的參數(shù)列表,在使用 defun* 宏代替 defun 時(shí)浦马,可支持關(guān)鍵字參數(shù)(keyword arguments时呀,見(jiàn)后文的 defstruct 一節(jié))。defun*宏也允許使用 (return "foo") 這種控制流轉(zhuǎn)移方式來(lái)代替 catch/throw 機(jī)制晶默。

如果你像讓自己定義的函數(shù)能夠作為 M-x 命令來(lái)執(zhí)行谨娜,只需將 (interactive) 作為函數(shù)體內(nèi)的第一個(gè)表達(dá)式,亦即位于文檔化注釋字串之后磺陡。

局部變量

在函數(shù)中要聲明局部變量趴梢,可使用 let 表達(dá)式”宜基本語(yǔ)法是 (let var-decl var-decl):

(let ((name1 value1)

(name2 value2)

name3

name4

(name5 value5)

name6

...))

每個(gè) var-decl 要么僅僅是變量名坞靶,要么就是 (變量名 初始值) 形式。初始化的變量與未初始化的變量出現(xiàn)的次序是任意的蝴悉。未初始化的變量彰阴,其值為 nil。

在一個(gè)函數(shù)中可以有多條 let 表達(dá)式拍冠,但是為了性能起見(jiàn)尿这,通常是將變量聲明都放到開(kāi)始的 let 表達(dá)式中,這樣會(huì)快一點(diǎn)倦微。不過(guò)妻味,你應(yīng)該寫(xiě)清晰的代碼。

引用參數(shù)

C++ 有引用參數(shù)欣福,函數(shù)可以修改調(diào)用者堆棧中的變量责球。Java 沒(méi)有這個(gè)功能,因此有時(shí)你不得不迂回的向函數(shù)傳遞單元素?cái)?shù)組,或一個(gè)對(duì)象雏逾,或別的什么東西來(lái)模擬這個(gè)功能嘉裤。

Emacs Lisp 也沒(méi)有真正的向函數(shù)傳遞引用的機(jī)制,但是它有動(dòng)態(tài)域(Dynamic Scope)栖博,這意味著你可以用任何方式修改位于調(diào)用者堆棧中的變量屑宠。看下面這兩個(gè)函數(shù):

(defun foo ()

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

(bar)? ? ? ; 調(diào)用 bar 函數(shù)

x))? ? ? ? ; 返回 x

(defun bar ()

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

如果你調(diào)用了 (foo)典奉,返回值為 7。

動(dòng)態(tài)域通常被認(rèn)為是近乎邪惡的壞設(shè)計(jì)丧叽,但是它有時(shí)也能派上用場(chǎng)卫玖。即使它真的很糟糕,通過(guò)它也能了解一些 Emacs 的內(nèi)幕踊淳。

譯注:Emacs 24 對(duì)詞法域(Lexical Scope)提供了支持假瞬,但是 Emacs Lisp 默認(rèn)依然是動(dòng)態(tài)域。要開(kāi)啟詞法域功能迂尝,可在 .el 文件的第一行添加以下信息:

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

return

Lisp 函數(shù)默認(rèn)是返回最后一個(gè)被求值的表達(dá)式的結(jié)果脱茉。通過(guò)一些構(gòu)造技巧,也可以讓每個(gè)可能的返回結(jié)果安排在函數(shù)的尾部位置垄开。例如:

上述 Lisp 函數(shù) day-name 的返回值是最后一個(gè)表達(dá)式的求值結(jié)果琴许,因此無(wú)論我們?cè)趺辞短?if,都能自動(dòng)產(chǎn)生一個(gè)結(jié)果返回说榆,因此這里不需要顯式的 return 語(yǔ)句虚吟。

不過(guò),有時(shí)用 if 嵌套的方式來(lái)重構(gòu)函數(shù)的返回形式會(huì)不太方便签财,它較適合一些小的函數(shù)串慰。對(duì)于一些規(guī)模較大并且嵌套較深的函數(shù),你可能希望函數(shù)能夠在較早的時(shí)機(jī)返回唱蒸。在 Emacs Lisp 中邦鲫,這一需求可基于 break 與 continue 來(lái)實(shí)現(xiàn)。上文中的 day-name 可重構(gòu)為:

(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 會(huì)降低程序性能庆捺,但是有時(shí)你會(huì)需要用它來(lái)消除太深的嵌套結(jié)構(gòu)。

try/catch

前文已經(jīng)講了 catch/throw屁魏,它類(lèi)似于異常滔以,可用于控制流轉(zhuǎn)移。

Emacs 真正的錯(cuò)誤處理機(jī)制叫做『條件』系統(tǒng)氓拼,本文不打算對(duì)此予以全面介紹你画,僅涉及如何捕捉異常以及如何忽略它們抵碟。

下面是一個(gè)一般化的 condition-case 結(jié)構(gòu),而且我也給出了 Java 的等價(jià)描述坏匪。

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

(ignore-errors

(do-something)

(do-something-else))

有時(shí)你的啟動(dòng)文件(譯注:可能是 .emacs 或init.el文件)可能不是總是正確工作∈首遥可以使用 ignore-errors 來(lái)封裝 Emacs Lisp 代碼敦迄,這樣即使被封裝的代碼出錯(cuò),也不會(huì)導(dǎo)致 Emacs 啟動(dòng)失敗凭迹。

condition-case nil 的意思是『錯(cuò)誤信息不賦給已命名的變量』罚屋。Emacs Lisp 允許你捕獲不同的錯(cuò)誤類(lèi)別并對(duì)錯(cuò)誤信息進(jìn)行排查。這方面的知識(shí)請(qǐng)從 Emacs Lisp 手冊(cè)獲取蕊苗。

在 condition-case 塊內(nèi)如果存在多條表達(dá)式需要求值沿后,必須用 progn 將它們封裝起來(lái)沿彭。

condition-case 不會(huì)捕捉 throw 扔出來(lái)的值——這兩個(gè)系統(tǒng)是彼此獨(dú)立的朽砰。

try/finally

Emacs Lisp 提供了類(lèi)似 finally 的功能 unwind-protect:

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

try/catch/finally

如果讓 condition-case(等價(jià)于 try/catch)成為 unwind-protect(等價(jià)于 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 }

類(lèi)

Emacs Lisp 不是標(biāo)準(zhǔn)意義上的面向?qū)ο缶幊陶Z(yǔ)言,它沒(méi)有類(lèi)廉邑、繼承以及多態(tài)等語(yǔ)法哥蔚。Emacs 的 Common Lisp 包(現(xiàn)在的 cl-lib)提供了一個(gè)有用的特性 defstruct,通過(guò)它可以實(shí)現(xiàn)簡(jiǎn)單的 OOP 支持蛛蒙。下面我會(huì)給出一個(gè)簡(jiǎn)單的示例椿肩。

下面的 Emacs Lisp 代碼與 Java 代碼本質(zhì)上是等價(jià)的:

defstruct 宏提供了一個(gè)靈活的默認(rèn)構(gòu)造器箕母,但是你也可以根據(jù)自己的需要來(lái)定義相適的構(gòu)造器。

defstruct 宏在創(chuàng)建對(duì)象實(shí)例時(shí),也創(chuàng)建了一組判定函數(shù)辈双,它們的用法如下:

(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 在對(duì)象構(gòu)造器方面可能挺糟糕,不過(guò) Emacs 在域(類(lèi)成員)的設(shè)置方面挺糟糕躏啰。要設(shè)置類(lèi)(結(jié)構(gòu)體)的域婶博,必須使用 setf 函數(shù),然后將類(lèi)名作為域名的前綴:

這樣看上去收奔,Lisp 并不是太糟糕掌呜,但是在實(shí)踐中(因?yàn)?Emacs Lisp 不支持命名空間,并且也沒(méi)有 with-slots 宏)坪哄,你會(huì)被卷入很長(zhǎng)的類(lèi)名與域名中的质蕉,例如:

(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)

要獲取域的值呢撞,需要將類(lèi)名與域名連接起來(lái),然后作為函數(shù)來(lái)用:

(person-name steve) ; yields "Steve"

defstruct 還能做很多事——它的功能非常得體饰剥,該考慮的事都考慮了殊霞,盡管它沒(méi)能形成一個(gè)完善的面向?qū)ο笙到y(tǒng)。

緩沖區(qū)即類(lèi)

在 Emacs Lisp 編程中汰蓉,將緩沖區(qū)視為類(lèi)的實(shí)例往往很有用绷蹲。因?yàn)?Emacs 支持緩沖區(qū)級(jí)別的局部變量的概念——無(wú)論變量以那種方式設(shè)置(譯注,例如通過(guò) setq 設(shè)置的變量)顾孽,它們都會(huì)自動(dòng)變成緩沖區(qū)內(nèi)部的局部變量祝钢。因此,這些變量的行為就像是被封裝在實(shí)例中的變量若厚。

可以用 make-variable-buffer-local 函數(shù)將一個(gè)變量聲明為緩沖區(qū)級(jí)別的局部變量拦英,通常這個(gè)函數(shù)會(huì)在 devar 或 defconst 之后出現(xiàn)(見(jiàn)下文)。

變量

在 Emacs Lisp 中测秸,可以用 defvar 或 defconst 聲明變量疤估,也可以為變量提供文檔化注釋?zhuān)?/p>

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

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

不過(guò)霎冯,會(huì)讓你大跌眼鏡的是铃拇,defconst 定義的是變量,而 defvar 定義的是常量沈撞,至少在重新求值時(shí)是這樣慷荔。要改變 defvar 變量的值,需要使用 makeunbound 來(lái)解除變量的綁定缠俺。不過(guò)显晶,總是可以使用 setq 來(lái)修改 defvar 或 defconst 變量的值。這兩種變量形式壹士,僅有的區(qū)別是磷雇,defconst 可以表達(dá)一種意圖:你定義的是一個(gè)常量。

可以使用 setq 來(lái)創(chuàng)建全新的變量墓卦,但是如果用 defvar倦春,Emacs Lisp 的字節(jié)碼編譯器能捕捉到一些錯(cuò)誤信息。

總結(jié)

Emacs Lisp 是一種真正的編程語(yǔ)言落剪。它有編譯器睁本、調(diào)試器、性能分析器忠怖、效果顯示器呢堰、運(yùn)行時(shí)文檔、庫(kù)凡泣、輸入/輸出枉疼、網(wǎng)絡(luò)皮假、進(jìn)程控制等。它有很多東西值得學(xué)習(xí)骂维,但是我希望這篇小文章能夠讓你向它邁出第一步惹资。

無(wú)論 Emacs Lisp 有多么古怪和煩人,只要你上手了航闺,它就能讓你體驗(yàn)到編程的快樂(lè)褪测。作為一種編程語(yǔ)言,它并不偉大潦刃,而且每個(gè)人都期望它是 Common Lisp 或 Scheme 或其他某種更好的 Lisp 方言侮措。有些人甚至認(rèn)為它根本不是 Lisp。

但是乖杠,要定制你的 Emacs分扎,或者修復(fù)你從他人那里得到的 Emacs Lisp 代碼,那么 Emacs Lisp 就會(huì)非常非常有用胧洒。四兩 Emacs Lisp 可撥千鈞之物畏吓。

正在學(xué)習(xí) Emacs Lisp 的你,如果覺(jué)得這份文檔是有用的略荡,請(qǐng)告訴我庵佣。如果你打算寫(xiě)一些 Emacs 擴(kuò)展,可以告訴我你希望我的下一篇文檔要寫(xiě)什么汛兜。有興趣的化,我會(huì)再繼續(xù)這個(gè) Emergency Elisp 系列通今。

Good Luck粥谬!

譯注:作者似乎沒(méi)有再寫(xiě)下去。xahlee 在 http://ergoemacs.org/emacs/elisp.html 所寫(xiě)的系列文檔可作為進(jìn)階教程辫塌。

原文出處: segmentfault

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

原文時(shí)間: 2016-04-10 19:22

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

整理時(shí)間: 2016-04-22 03:25

本文由 Hick 整理漏策,轉(zhuǎn)載請(qǐng)保留以上信息;

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 .

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市臼氨,隨后出現(xiàn)的幾起案子掺喻,更是在濱河造成了極大的恐慌,老刑警劉巖储矩,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件感耙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡持隧,警方通過(guò)查閱死者的電腦和手機(jī)即硼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)屡拨,“玉大人只酥,你說(shuō)我怎么就攤上這事褥实。” “怎么了裂允?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵损离,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我绝编,道長(zhǎng)草冈,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任瓮增,我火速辦了婚禮怎棱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绷跑。我一直安慰自己拳恋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布砸捏。 她就那樣靜靜地躺著谬运,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垦藏。 梳的紋絲不亂的頭發(fā)上梆暖,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音掂骏,去河邊找鬼轰驳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛弟灼,可吹牛的內(nèi)容都是我干的级解。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼田绑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼勤哗!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起掩驱,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤芒划,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后欧穴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體民逼,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年苔可,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缴挖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡焚辅,死狀恐怖映屋,靈堂內(nèi)的尸體忽然破棺而出苟鸯,到底是詐尸還是另有隱情,我是刑警寧澤棚点,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布早处,位于F島的核電站,受9級(jí)特大地震影響瘫析,放射性物質(zhì)發(fā)生泄漏砌梆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一贬循、第九天 我趴在偏房一處隱蔽的房頂上張望咸包。 院中可真熱鬧,春花似錦杖虾、人聲如沸烂瘫。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坟比。三九已至,卻和暖如春嚷往,著一層夾襖步出監(jiān)牢的瞬間葛账,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工皮仁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留籍琳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓魂贬,卻偏偏與公主長(zhǎng)得像巩割,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子付燥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容