使用宏的一个原因是用户可以编写函数和符号名称,而不必引用所有内容。以from the Clojure Cookbook为例:
; Routing
(defroutes main-routes
(GET "/" [] (index))
(GET "/en/" [] (index))
(GET "/fr/" [] (index-fr))
(GET "/:greeting/" [greeting] (view greeting)))
如果GET 是一个函数,则必须引用所有index* 符号,加上view 和greeting:
; 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 代码”。相反,GET 宏将摄取并用作如何生成“合法”Clojure 代码的一种简写形式(准确地说是域特定语言或 DSL)。 DSL 通常比最终生成的代码更短、更简单、更方便人类,就像 Clojure 比 Clojure 编译器生成的 Java 字节码或最终的机器汇编语言代码更短、更简单、更方便由 JVM 生成。
简而言之,每个宏都是一个“预编译器”,它将 DSL 转换为普通的 Clojure,然后由 Clojure 编译器摄取以生成 Java 字节码。
话虽如此,可以重新安排将所有宏魔法放入defroutes 宏中,这样GET 符号既不是函数也不是宏,而只是一种标记,如实现中的:get 关键字。作为用户,这些实现细节通常并不重要。
更新
最好只在函数不起作用或非常笨拙时使用宏。决定因素通常是是否要使用裸(未引用)符号,但不提前评估它们。 Core Clojure 本身为许多其他语言中的“内置”构造使用宏,包括defn、for、and、or、when 等。
还要注意,宏不能做一些函数可以做的事情,例如作为filter等高阶函数的参数。
总之,一个函数定义了一个行为。宏定义了语言扩展。