内容提要
本文介绍Clojure的四种求值形式:函数形式以指向函数的符号为操作符来调用函数;Lambda形式直接以函数为操作符并立即执行;宏在求值前先展开,避免参数被提前求值,如and、or;特殊形式由编译器实现,如if、let。文章还讲解了符号、def、defn、解构及let与let*的区别。
延伸解读
符号与函数调用的本质
文章指出,函数形式的操作符必须是指向函数的符号。符号不是自求值的,对符号求值会得到它指向的值。因此,只要形式的第一个元素求值后是符号,就能调用函数。例如,((eval 'my-fn) 1 2) 和 ((if (> a 1) + -) 2 1) 都能正常工作。这体现了 Lisp「代码即数据」的灵活性,但也提醒我们,函数名和变量名一样,都只是符号而已。
宏与特殊形式的分工
宏在求值前先展开,避免参数被提前求值,如 and、or 必须定义为宏,否则会执行多余运算甚至丢失副作用。特殊形式由编译器实现,如 if、let*。有趣的是,let 本身是宏,展开为 let*,并在展开时进行语法检查和解构。这种设计让 Clojure 的 let 比 Common Lisp 和 Scheme 的 let 更灵活,无需区分 let 和 let* 的绑定顺序。
解构与 let 的实用性
Clojure 的解构由 destructure 函数在 let 宏展开时完成,而非编译器硬编码。例如,使用 let 解构映射 {:keys [a b c]} 可以方便地提取值。由于 let 是宏,它能在展开前检查绑定向量是否为偶数个,并提供清晰的错误信息。这种基于宏的实现方式,使得 Clojure 的 let 在保持简洁的同时,也具备良好的错误处理和扩展性。
Q&A
Clojure 中的形式(form)根据操作符不同分为哪几种?
根据操作符的不同,形式被分为函数形式、Lambda 形式、宏形式和特殊形式。
Clojure 中符号(Symbol)有什么特点?如何定义符号?
符号是 Lisp 中的一种特殊数据类型,不是自求值的,对符号求值得到的是符号指向的其他值。使用 def 可以定义符号,例如 (def my-symbol 123)。
为什么 Clojure 中的 and 和 or 必须是宏而不是函数?
因为如果 and 和 or 是函数,所有参数会被预先求值,可能导致不必要的计算或副作用(如从消息队列取消息时丢失数据)。宏在求值前先展开,可以避免参数被提前求值,实现短路逻辑。
Clojure 中 let 和 let* 有什么区别?
let 是一个宏,最终会展开为 let*;let* 是特殊形式,由编译器直接实现。let 在展开时会进行语法检查(如绑定必须是偶数个)和解构操作,而 let* 没有这些。
Clojure 中的解构是什么?它是如何实现的?
解构是一种语法特性,用于从集合中提取值并绑定到符号。Clojure 没有用编译器实现解构,而是用 destructure 函数实现,该函数在 let 宏展开时被调用。
Clojure 中特殊形式是什么?举例说明。
特殊形式是由编译器和解释器实现的形式,有特殊的解释或编译规则,类似于其他语言的保留字。例如 if 和 let* 都是特殊形式。