和全部Lisp同樣,Clojure內在的同構性使得你能夠窮盡語言的特性,編寫生成代碼的子過程——「宏」。宏是一種按需調製語言的強大方式。html
當心!能夠用函數完成的事用宏去實現可不是什麼好事。你應該僅在須要控制參數是否或者什麼時候eval的時候使用宏。segmentfault
你應該熟悉Clojure.確保你瞭解Y分鐘學Clojure中的全部內容。app
使用defmacro定義宏。宏應該輸出一個能夠做爲clojure代碼演算的列表。less
如下宏的效果和直接寫(reverse "Hello World")一致。函數
(defmacro my-first-macro [] (list reverse "Hello World"))
使用macroexpand或macroexpand-1查看宏的結果。測試
注意,調用須要引用。spa
(macroexpand '(my-first-macro)) ;; -> (#<core$reverse clojure.core$reverse@xxxxxxxx> "Hello World")
你能夠直接eval macroexpand的結果code
(eval (macroexpand '(my-first-macro))) ; -> (\d \l \o \r \W \space \o \l \l \e \H)
不過通常使用如下形式,更簡短,更像函數:orm
(my-first-macro) ; -> (\d \l \o \r \W \space \o \l \l \e \H)
建立宏的時候可使用更簡短的引用形式來建立列表htm
(defmacro my-first-quoted-macro [] '(reverse "Hello World")) (macroexpand '(my-first-quoted-macro)) ;; -> (reverse "Hello World")
注意reverse再也不是一個函數對象,而是一個符號。
宏能夠傳入參數。
(defmacro inc2 [arg] (list + 2 arg)) (inc2 2) ; -> 4
不過,若是你嘗試配合使用引用列表,會致使錯誤,
由於參數也會被引用。
爲了不這個問題,clojure提供了引用宏的另外一種方式:`
在`以內,你可使用~得到外圈做用域的變量。
(defmacro inc2-quoted [arg] `(+ 2 ~arg)) (inc2-quoted 2)
你可使用一般的析構參數。用~@展開列表中的變量。
(defmacro unless [arg & body] `(if (not ~arg) (do ~@body))) ; 別忘了 do! (macroexpand '(unless true (reverse "Hello World"))) ;; -> ;; (if (clojure.core/not true) (do (reverse "Hello World")))
當第一個參數爲假時,(unless)會演算、返回主體。
不然返回nil。
(unless true "Hello") ; -> nil (unless false "Hello") ; -> "Hello"
須要當心,宏會搞亂你的變量
(defmacro define-x [] '(do (def x 2) (list x))) (def x 4) (define-x) ; -> (2) (list x) ; -> (2)
使用gensym來得到獨有的標識符
(gensym 'x) ; -> x1281 (or some such thing) (defmacro define-x-safely [] (let [sym (gensym 'x)] `(do (def ~sym 2) (list ~sym)))) (def x 4) (define-x-safely) ; -> (2) (list x) ; -> (4)
你能夠在 ` 中使用 # 爲每一個符號自動生成gensym
(defmacro define-x-hygenically [] `(do (def x# 2) (list x#))) (def x 4) (define-x-hygenically) ; -> (2) (list x) ; -> (4)
一般會配合宏使用幫助函數。
讓咱們建立一些幫助函數來支持(無聊的)算術語法:
(declare inline-2-helper) (defn clean-arg [arg] (if (seq? arg) (inline-2-helper arg) arg)) (defn apply-arg "Given args [x (+ y)], return (+ x y)" [val [op arg]] (list op val (clean-arg arg))) (defn inline-2-helper [[arg1 & ops-and-args]] (let [ops (partition 2 ops-and-args)] (reduce apply-arg (clean-arg arg1) ops)))
在建立宏前,咱們能夠先測試
(inline-2-helper '(a + (b - 2) - (c * 5))) ; -> (- (+ a (- b 2)) (* c 5))
然而,若是咱們但願它在編譯期執行,就須要建立宏
(defmacro inline-2 [form] (inline-2-helper form))) (macroexpand '(inline-2 (1 + (3 / 2) - (1 / 2) + 1))) ; -> (+ (- (+ 1 (/ 3 2)) (/ 1 2)) 1) (inline-2 (1 + (3 / 2) - (1 / 2) + 1)) ; -> 3 (事實上,結果是3N, 由於數字被轉化爲帶/的有理分數)
Clojure for the Brave and True系列的編寫宏
http://www.braveclojure.com/writing-macros/