有时候,推迟表达式的求值是很有用的。例如:如果后续程序中可能根本用不到某个结果,你就可以避免执行一次耗时的计算。 thunk 库提供了下面这些函数和宏,来支持这种 延迟求值(deferred evaluation):
返回一个用于对 forms 进行求值的 惰性计算对象(thunk)。thunk 是一个闭包(see 闭包),它会继承 thunk-delay 调用处的词法环境。使用该宏需要开启 lexical-binding(词法绑定)。
强制执行 thunk,对创建该 thunk 的 thunk-delay 中指定的表达式进行求值。函数会返回最后一个表达式的求值结果。该 thunk 还会 “记住(remembers)”自己已被执行:后续对同一个 thunk 调用 thunk-force 时,只会直接返回相同结果,而不会再次求值这些表达式。
该宏与 let 类似,但会创建 “惰性(lazy)” 变量绑定。每个绑定的形式为 (symbol value-form)。与 let 不同的是:value-form 的求值会被推迟,直到在对 forms 求值时,第一次使用对应符号 symbol 的绑定才会触发求值。每个 value-form 最多只会被求值一次。使用该宏需要开启 lexical-binding。
示例:
(defun f (number)
(thunk-let ((derived-number
(progn (message "Calculating 1 plus 2 times %d" number)
(1+ (* 2 number)))))
(if (> number 10)
derived-number
number)))
(f 5) ⇒ 5
(f 12) ⊣ Calculating 1 plus 2 times 12 ⇒ 25
由于惰性绑定变量的特殊性质,对它们进行赋值(例如使用 setq)会报错。
该宏与 thunk-let 类似,但允许 bindings 中的表达式引用当前 thunk-let* 中前面已经定义的绑定。使用该宏需要开启 lexical-binding。
(thunk-let* ((x (prog2 (message "Calculating x...")
(+ 1 1)
(message "Finished calculating x")))
(y (prog2 (message "Calculating y...")
(+ x 1)
(message "Finished calculating y")))
(z (prog2 (message "Calculating z...")
(+ y 1)
(message "Finished calculating z")))
(a (prog2 (message "Calculating a...")
(+ z 1)
(message "Finished calculating a"))))
(* z x))
⊣ Calculating z...
⊣ Calculating y...
⊣ Calculating x...
⊣ Finished calculating x
⊣ Finished calculating y
⊣ Finished calculating z
⇒ 8
thunk-let 和 thunk-let* 会隐式使用 thunk:它们在展开时会创建辅助符号,并将这些符号绑定到包裹了绑定表达式的 thunk 上。函数体 forms 中对原变量的所有引用,都会被替换成以对应辅助变量为参数调用 thunk-force 的表达式。
因此,任何使用 thunk-let 或 thunk-let* 的代码都可以改写成直接使用 thunk,但在很多场景下,使用这些宏写出的代码比显式使用 thunk 更简洁优雅。