Emacs 自力求生指南 ── 初识 Elisp
1 Emacs Lisp
Emacs lisp 是一个函数式语言。就我野生的 CS 知识理解,函数式语言的核心是「表达式的构造、求值和保护」。
1.1 List
List: LISt processor 。List 是 lisp 程序的基本指令单元和数据结构。一对括号表示一个 list,内容物可以是任意东西
|
|
1.2 求值
在 Emacs 里,上面四个表达式,你可以把光标移到右括号的右边按 C-x C-e 对其求值,结果会显示在 minibuffer 里。结果分别为:
- 显示「软件包列表」buffer
- 报错: Symbol abc 没有绑定函数
20
nil
,在 Lisp 世界里表示「假」。1(+ 1 2)
求值:「找到第一个 symbol 所定义的函数,并把 list 里剩下的元素求值后作为参数传入」。第一行有定义函数,且不需要参数; 因为我们没有把 abc 这个 symbol 定义一个函数,所以第二行报错了;求值是递归的,所以第三行里的两个子 list 先被求值再传入 + 函数; 对空 list 的求值永远为空 list,所以第四行不会报错;
第五个结果有点意思,我们本来期望它可能出现 3 的,怎么这个 list 被原样抛出来了呢?
1.3 保护
(quote)
可以用来保护一个 List 免于被求值,使其可以保留完整表达。
考虑以下程序:
|
|
我们希望这个表达式能返回 +
这个 symbol,但它却报错了: (wrong-type-argument listp 5)
。数字 5 的出现说明表达式被递归求值了。
那我们怎么抵抗求值呢?像上面例 5 那样保护起来就行了:
|
|
这命令有什么用呢?简单地说,这个命令 模糊了指令和数据的界线 :
- 指令被
(quote)
起来就变成了一个普通的 list (eval)
一个 list 就变成了指令
写一个函数,把所有传入的表达式都变成加法表达式
|
|
1.4 保护内临时展开
一个十分常见的场景:我想构造一个 list ,其中一个位置在构造时需要插入外部变量的 值 。
比如:
|
|
我希望上文能给我 (+ 123 444)
这个表达式,但我最终拿到的是 (+ my-value 444)
。这结果显然是不可用的,因为 let
的外部没有
my-value
的定义。
这时,我们要在 quote 一个表达式时, unquote
这个变量,即重新对其求值:
|
|
这有什么用呢?举一个我实际使用中遇到的例子:
|
|
有没有体会到「指令即数据,数据即指令」? (゚∀゚)
2 常用调试命令、快捷键、流程、资料表
C-x C-e
(eval-last-sexp
)- 最常用的调试方法。由于 Lisp 递归求值的特性,你可以通过移动光标,从内向外逐级调试。
(message "abc")
- 在
*Messages*
buffer 里留下信息,也就是printf
或者console.log()
(toggle-debug-on-error)
- 在异常发生时自动弹出调用栈。注意它是 toggle。
C-h v
(describe-variable
)- 寻找某个全局变量的值、文档、定义位置等
C-h f
(describe-functionh
)- 寻找某个函数的文档、定义位置等
C-h k
(describe-key
)- 寻找某个快捷键当前绑定的 function
- 基于函数入口的调试法
- 在函数被 call 时自动弹出调用栈
- ANSI Common Lisp 手册中文版
- 用来查找常用控制流和 helper 函数的名字。Elisp 和 Common lisp 区别不大。
-
nil
和()
意义相同。「真」是t
。对nil
和t
的求值结果永远为自身。 ↩︎