源代碼下載:?learnclojuremacros-zh.clj
和所有Lisp一樣,Clojure內(nèi)在的同構(gòu)性使得你可以窮盡語言的特性,編寫生成代碼的子過程——“宏”。宏是一種按需調(diào)制語言的強(qiáng)大方式。
小心!可以用函數(shù)完成的事用宏去實(shí)現(xiàn)可不是什么好事。你應(yīng)該僅在需要控制參數(shù)是否或者何時(shí)eval的時(shí)候使用宏。
你應(yīng)該熟悉Clojure.確保你了解Y分鐘學(xué)Clojure中的所有內(nèi)容。
;; 使用defmacro定義宏。宏應(yīng)該輸出一個(gè)可以作為clojure代碼演算的列表。
;;
;; 以下宏的效果和直接寫(reverse "Hello World")一致。
(defmacro my-first-macro []
(list reverse "Hello World"))
;; 使用macroexpand或macroexpand-1查看宏的結(jié)果。
;;
;; 注意,調(diào)用需要引用。
(macroexpand '(my-first-macro))
;; -> (#<core$reverse clojure.core$reverse@xxxxxxxx> "Hello World")
;; 你可以直接eval macroexpand的結(jié)果
(eval (macroexpand '(my-first-macro)))
; -> (\d \l \o \r \W \space \o \l \l \e \H)
;; 不過一般使用以下形式,更簡短,更像函數(shù):
(my-first-macro) ; -> (\d \l \o \r \W \space \o \l \l \e \H)
;; 創(chuàng)建宏的時(shí)候可以使用更簡短的引用形式來創(chuàng)建列表
(defmacro my-first-quoted-macro []
'(reverse "Hello World"))
(macroexpand '(my-first-quoted-macro))
;; -> (reverse "Hello World")
;; 注意reverse不再是一個(gè)函數(shù)對象,而是一個(gè)符號(hào)。
;; 宏可以傳入?yún)?shù)。
(defmacro inc2 [arg]
(list + 2 arg))
(inc2 2) ; -> 4
;; 不過,如果你嘗試配合使用引用列表,會(huì)導(dǎo)致錯(cuò)誤,
;; 因?yàn)閰?shù)也會(huì)被引用。
;; 為了避免這個(gè)問題,clojure提供了引用宏的另一種方式:`
;; 在`之內(nèi),你可以使用~獲得外圈作用域的變量。
(defmacro inc2-quoted [arg]
`(+ 2 ~arg))
(inc2-quoted 2)
;; 你可以使用通常的析構(gòu)參數(shù)。用~@展開列表中的變量。
(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")))
;; 當(dāng)?shù)谝粋€(gè)參數(shù)為假時(shí),(unless)會(huì)演算、返回主體。
;; 否則返回nil。
(unless true "Hello") ; -> nil
(unless false "Hello") ; -> "Hello"
;; 需要小心,宏會(huì)搞亂你的變量
(defmacro define-x []
'(do
(def x 2)
(list x)))
(def x 4)
(define-x) ; -> (2)
(list x) ; -> (2)
;; 使用gensym來獲得獨(dú)有的標(biāo)識(shí)符
(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)
;; 你可以在 ` 中使用 # 為每個(gè)符號(hào)自動(dòng)生成gensym
(defmacro define-x-hygenically []
`(do
(def x# 2)
(list x#)))
(def x 4)
(define-x-hygenically) ; -> (2)
(list x) ; -> (4)
;; 通常會(huì)配合宏使用幫助函數(shù)。
;; 讓我們創(chuàng)建一些幫助函數(shù)來支持(無聊的)算術(shù)語法:
(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)))
;; 在創(chuàng)建宏前,我們可以先測試
(inline-2-helper '(a + (b - 2) - (c * 5))) ; -> (- (+ a (- b 2)) (* c 5))
; 然而,如果我們希望它在編譯期執(zhí)行,就需要?jiǎng)?chuàng)建宏
(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 (事實(shí)上,結(jié)果是3N, 因?yàn)閿?shù)字被轉(zhuǎn)化為帶/的有理分?jǐn)?shù))
Clojure for the Brave and True系列的編寫宏 http://www.braveclojure.com/writing-macros/
官方文檔 http://clojure.org/macros
何時(shí)使用宏? http://dunsmor.com/lisp/onlisp/onlisp_12.html
更多建議: