使用宏的一個原因是用戶可以編寫函數和符號名稱而不必引用所有內容。就拿這個例子from the 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 Bytecode或機器彙編語言代碼最終更簡單,更簡單由JVM生成。
簡而言之,每個宏都是一個「預編譯器」,它將DSL變成簡單的Clojure,Clojure然後被Clojure編譯器接收並生成Java字節碼。
話雖如此,它可能被重新安排把所有的宏觀幻成defroutes
宏,以便在GET
符號既不是函數也不是宏,而只是一個喜歡的類型標記:get
關鍵字在執行。作爲用戶,這些類型的實現細節通常並不重要。
更新
最好是隻使用某一功能將無法正常工作或者是很尷尬的宏。決定性因素通常是如果想要使用裸(未加引號)的符號,但不提前對其進行評估。 Core Clojure本身使用宏作爲其他語言「內置」的許多構造宏,包括defn
,for
,and
,or
,when
等。
另請注意,宏不能做一些功能可以做的事情,如作爲filter
等高階函數的參數。
總之,一個函數定義了一個行爲。宏定義了語言擴展。
如果GET是一個函數,那麼''(view greeting)'根本就沒有意義。你需要寫一些更像'(GET「/:greeting」(fn [req](let-request [[greeting] req](view greeting))))',這很麻煩但不是不可能的。 – amalloy
函數方法需要對大多數特性使用'eval',這就是宏觀解決方案非常受歡迎的原因。 –
@AlanThompson--純粹好奇。我不是宏的狂熱粉絲,因爲我相信他們經常會對真正發生的事情產生誤解。當項目大量使用它們時(設計決策等),始終感興趣。剛剛發現「bidi」 - 數據結構方法在精神層面上對我說。 – beoliver