原文见 『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
,例如 #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)。可以使用方括号来构建预先初始化的字面向量,例如:
[-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 语句的代码片断。它不复杂,仅仅是让你可以上手的方子。
状况 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
语句,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
更通用。
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 求和结果)。
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
进行控制流转移。
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 表达式。
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,那么你有必要花一些时间揣摩一下它。
若是你迭代访问一个集合,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 -*-
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
会下降程序性能,可是有时你会须要用它来消除太深的嵌套结构。
前文已经讲了 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
扔出来的值——这两个系统是彼此独立的。
Emacs Lisp 提供了相似 finally 的功能 unwind-protect
:
与 condition-case
类似,unwind-protect
接受单个体块(body-form,译注:try 部分),后面跟随着一条或多条善后的表达式,所以你须要用 progn
将体块内的表达式封装起来。
若是让 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 所写的系列文档可做为进阶教程。