Coffee Break Clojure, Vol.2

Coffee Break Clojure, Vol.2

💡 原文中文,约7500字,阅读约需18分钟。
📝

内容提要

本文介绍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* 都是特殊形式。

🏷️

标签

➡️

继续阅读