条件控制结构用于在多个备选逻辑中做出选择。Emacs Lisp 提供五种条件形式:if(其用法与其他编程语言基本一致)、when 和 unless(均为 if 的变体)、cond(通用化的分支语句)以及 pcase(cond 的进一步通用化形式,see 模式匹配条件)。
if 会根据 condition(条件)的求值结果,在 then-form(then 分支形式)和 else-forms(else 分支形式)之间选择执行逻辑:若 condition 求值结果为非 nil,则执行 then-form 并返回其结果;否则按文本顺序执行所有 else-forms,并返回最后一个形式的求值结果。(if 的 else 部分是隐式 progn 的典型示例。See 顺序执行。)
若 condition 的求值结果为 nil,且未指定任何 else-forms,则 if 返回 nil。
if 属于特殊形式,原因在于未被选中的分支永远不会被求值 —— 会被直接忽略。因此在以下示例中,true 不会被打印出来,因为 print 函数根本不会被调用:
(if nil
(print 'true)
'very-false)
⇒ very-false
这是 if 的一个变体形式,它不含 else-forms(else 分支形式),且可以包含多个 then-forms(then 分支形式)。具体来说,
(when condition a b c)
完全等价于
(if condition (progn a b c) nil)
这是 if 的一个变体形式,它不含 then-form(then 分支形式):
(unless condition a b c)
完全等价于
(if condition nil a b c)
cond 可在任意数量的备选逻辑中做选择。cond 中的每个 clause(分支子句)都必须是一个列表:该列表的 CAR(首部元素)为 condition(条件);若列表还有剩余元素,则这些元素为 body-forms(主体形式)。因此,一个分支子句的结构如下:
(condition body-forms...)
cond 会按照文本顺序依次尝试各个分支子句:先对每个子句的 condition(条件)进行求值。若 condition 的求值结果为非 nil,则该子句判定为「成功」;此时 cond 会执行该子句的 body-forms(主体形式),并返回最后一个 body-forms 的求值结果。后续所有剩余子句都会被忽略。
若 condition 的求值结果为 nil,则该子句判定为「失败」,cond 会继续处理下一个子句,对其 condition 进行求值判断。
分支子句也可以采用如下形式:
(condition)
此时,若检测到 condition(条件)的求值结果为非 nil,则该 cond 形式会返回 condition 的值。
若所有 condition 的求值结果均为 nil(即所有分支子句都判定为失败),则 cond 返回 nil。
以下示例包含四个分支子句,分别用于检测 x 的值是否为数字、字符串、缓冲区和符号:
(cond ((numberp x) x)
((stringp x) x)
((bufferp x)
(setq temporary-hack x) ; multiple body-forms
(buffer-name x)) ; in one clause
((symbolp x) (symbol-value x)))
我们经常需要在前面所有分支都不匹配时,执行最后一个分支。为此,我们可以把 t 用作最后一个分支的 condition(条件),形如:(t body-forms)。形式 t 的求值结果是 t,它永远不会是 nil,因此只要 cond 执行到这个分支,它就一定会成功。例如:
(setq a 5)
(cond ((eq a 'hack) 'foo)
(t "default"))
⇒ "default"
这个 cond 表达式在 a 的值为 hack 时返回 foo,否则返回字符串 "default"。
所有条件结构都可以用 cond 或 if 来表示。因此,二者之间的选择只是风格问题。例如:
(if a b c) ≡ (cond (a b) (t c))
在使用条件判断的同时绑定变量往往会很方便。常见的场景是:你先计算出一个值,然后在该值非 nil 时对它做一些处理。直接的写法可以像下面这样,例如:
(let ((result1 (do-computation)))
(when result1
(let ((result2 (do-more result1)))
(when result2
(do-something result2)))))
由于这是一种非常常见的模式,Emacs 提供了许多宏来让这一写法更简便、更易读。上面的代码也可以改用下面这种方式来写:
(when-let* ((result1 (do-computation))
(result2 (do-more result1)))
(do-something result2))
围绕这种模式有许多变体,下面会对它们做简要说明。
依次对 varlist 中的每个绑定进行求值,若某个绑定的值为 nil 则停止。如果所有绑定的值都非 nil,则返回 then-form 的值;否则返回 else-forms 中最后一个形式的值。
varlist 中的每个元素都具有格式 (symbol value-form):对 value-form 求值,并将 symbol 局部绑定到该结果。绑定是顺序进行的,与 let* 类似(see 局部变量)。作为一种特殊情况:如果只关心 value-form 的判断结果,可以省略 symbol:此时会对 value-form 求值并检查是否为 nil,但不会绑定它的值。
依次对 varlist 中的每个绑定求值,若某个绑定的值为 nil 则停止。如果所有绑定都非 nil,则返回 then-forms 中最后一个形式的值。
varlist 的格式与 if-let* 相同:varlist 中的每个元素格式为 (symbol value-form),其中对 value-form 求值,并将 symbol 局部绑定到该结果。绑定按顺序进行,与 let* 一致(see 局部变量)。作为一种特殊情况:如果只关心 value-form 的判断结果,可以省略 symbol:此时会对 value-form 求值并检查是否为 nil,但不会绑定其值。
依次对 varlist 中的每个绑定求值,若某个绑定的值为 nil 则停止。如果所有绑定都非 nil,则返回 then-forms 中最后一个形式的值;如果没有 then-forms,则返回最后一个绑定的值。
varlist 的格式与 if-let* 相同:varlist 中的每个元素格式为 (symbol value-form),其中对 value-form 求值,并将 symbol 局部绑定到该结果。绑定按顺序进行,与 let* 一致(see 局部变量)。作为一种特殊情况:如果只关心 value-form 的判断结果,可以省略 symbol:此时会对 value-form 求值并检查是否为 nil,但不会绑定其值。
部分 Lisp 程序员遵循这样的惯例:and 和 and-let* 用于关注返回值的形式求值,而 when 和 when-let* 用于关注副作用、忽略返回值的形式求值。
Emacs 还提供了类似的宏,用于循环执行直到某个绑定求值为 nil:
依次对 spec 中的每个绑定求值,若某个绑定的值为 nil 则停止。如果所有绑定都非 nil,则执行 then-forms,然后重复循环。注意:循环重复时,spec 中的 value-form 会被重新求值,绑定也会重新建立。
varlist 的格式与 if-let* 相同:varlist 中的每个元素格式为 (symbol value-form),其中对 value-form 求值,并将 symbol 局部绑定到该结果。绑定按顺序进行,与 let* 一致(see 局部变量)。作为一种特殊情况:如果只关心 value-form 的判断结果,可以省略 symbol:此时会对 value-form 求值并检查是否为 nil,但不会绑定其值。
while-let 的返回值永远是 nil。