[譯] Emacs Lisp 速成

原文見 『Emergency Elisp』。html

你用着 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 就能寫的那些代碼。app

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

快速開始

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

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

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

要將一個名爲 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,例如 #xabcdxDEADBEE

  • 浮點數:位數是 64;

  • 科學計數,例如 5e-106.02e23

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

布爾值

符號 ttrue,符號 nilfalse(與 null 同義)。

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

數組

Emacs Lisp 有定長數組,名曰『向量』(Vector)。可以使用方括號來構建預先初始化的字面向量,例如:

[-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)),單個等號,求值結果爲 tnil,也能用於浮點數比較。

數值不相等判斷:(/= 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 有兩個版本:condcase

Emacs Lisp 的 condcase 不具有 switch 的查表優化功能,它們本質上是嵌套的 if-then-else 從句。不過,若是你有多重嵌套,用 condcase 要比 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=10total=0,而後執行循環。循環結束後,可對 total 進行求值,結果爲 55(從 1 到 10 求和結果)。

break/continue

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

在 Emacs Lisp 中要 break 一個循環,能夠將 (cache 'break ...) 置於循環外部,而後在循環內部須要中斷的地方放置 (throw 'break value),例如:

catch/throw

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

若是你不關心 while 循環的『返回值』,能夠 (throw 'break nil)

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

continue

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

break + continue

上面的循環的計算結果爲 4000,即 total 的值。要獲得這個結果,還有更好的計算方式,不過我須要足夠簡單的東西來說述如何在 Lisp 中實現 breakcontinue

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

dowhile 之間能夠有任意數量的 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 手冊得到更多信息。

函數

defundefine 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 中,這一需求可基於 breakcontinue 來實現。上文中的 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 的等價描述。

try/catch

若是你想讓 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

try/finally

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 代碼本質上是等價的:

class

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 函數將一個變量聲明爲緩衝區級別的局部變量,一般這個函數會在 devardefconst 以後出現(見下文)。

變量

在 Emacs Lisp 中,能夠用 defvardefconst 聲明變量,也能夠爲變量提供文檔化註釋:

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

語法爲 (defvar 變量名 值 [文檔化註釋])

不過,會讓你大跌眼鏡的是,defconst 定義的是變量,而 defvar 定義的是常量,至少在從新求值時是這樣。要改變 defvar 變量的值,須要使用 makeunbound 來解除變量的綁定。不過,老是可使用 setq 來修改 defvardefconst 變量的值。這兩種變量形式,僅有的區別是,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 所寫的系列文檔可做爲進階教程。

相關文章
相關標籤/搜索