使用宏的一个原因是用户可以编写函数和符号名称,而不必引用所有内容。以Clojure Cookbook 中的这个例子为例:
; Routing
(defroutes main-routes
(GET "/" [] (index))
(GET "/en/" [] (index))
(GET "/fr/" [] (index-fr))
(GET "/:greeting/" [greeting] (view greeting)))
如果是函数,所有index*
符号,加上view
和greeting
必须被引用:GET
; Routing
(defroutes main-routes
(GET "/" [] '(index))
(GET "/en/" [] '(index))
(GET "/fr/" [] '(index-fr))
(GET "/:greeting/" '[greeting] '(view greeting)))
由于函数参数是在调用函数之前评估的,因此一旦读取表单,就会评估(index)
et al 。此外,greeting
arg 将在每个 HTTP 请求上发生变化,因此显然无法提前知道。
该宏还负责处理常规函数(通常)不可能的所有解构魔法。
经常令人困惑的事情(并且没有向初学者很好地解释)是这样的一行:
(GET "/:greeting/" [greeting] (view greeting))
不是正常的“Clojure 代码”。相反,它是一种速记(准确地说是域特定语言或 DSL),GET
宏将摄取并用作如何生成“合法”Clojure 代码的指令。DSL 通常比最终生成的代码更短、更简单、更方便人类,就像 Clojure 比 Clojure 编译器生成的 Java 字节码或最终的机器汇编语言代码更短、更简单、更方便由 JVM 生成。
简而言之,每个宏都是一个“预编译器”,它将 DSL 转换为普通的 Clojure,然后由 Clojure 编译器摄取以生成 Java 字节码。
话虽如此,可以重新安排将所有宏魔术放入defroutes
宏中,以便GET
符号既不是函数也不是宏,而只是一种标记,如:get
实现中的关键字。作为用户,这些实现细节通常并不重要。
更新
最好仅在函数不起作用或非常尴尬时使用宏。决定因素通常是是否要使用裸(未引用)符号,但不提前评估它们。Core Clojure 本身将宏用于许多其他语言中的“内置”构造,包括defn
、for
、and
、or
、when
等。
另请注意,宏不能做函数可以做的某些事情,例如作为高阶函数的参数,如filter
.
总之,一个函数定义了一个行为。宏定义语言扩展。