Macro是函數(shù)式編程里面很重要的一個(gè)概念郑兴,在之前蚕捉,我們已經(jīng)使用了Clojure里面的一些macro薪前,譬如when润努,and等,我們可以通過macroexpand獲知:
user=> (macroexpand '(when true [1 2 3])))
(if true (do [1 2 3]))
user=> (doc when)
-------------------------
clojure.core/when
([test & body])
Macro
Evaluates test. If logical true, evaluates body in an implicit do.
nil
可以看到序六,when其實(shí)就是if + do的封裝任连,很類似C語言里面的macro。
defmacro
我們可以通過defmacro來定義macro:
user=> (defmacro my-plus
#_=> "Another plus for a + b"
#_=> [args]
#_=> (list (second args) (first args) (last args)))
#'user/my-plus
user=> (my-plus (1 + 1))
2
user=> (macroexpand '(my-plus (1 + 1)))
(+ 1 1)
macro的定義比較類似函數(shù)的定義例诀,我們需要定義一個(gè)macro name,譬如上面的my-plus裁着,一個(gè)可選擇的macro document繁涂,一個(gè)參數(shù)列表以及macro body。body通常會(huì)返回一個(gè)list用于后續(xù)被Clojure進(jìn)行執(zhí)行二驰。
我們可以在macro body里面使用任何function扔罪,macro以及special form,然后使用macro的時(shí)候就跟函數(shù)調(diào)用一樣桶雀。但是跟函數(shù)不一樣的地方在于函數(shù)在調(diào)用的時(shí)候矿酵,參數(shù)都是先被evaluated,然后才被傳入函數(shù)里面的矗积,但是對(duì)于macro來說全肮,參數(shù)是直接傳入macro,而沒有預(yù)先被evaluated棘捣。
我們也能在macro里面使用argument destructuring技術(shù)辜腺,進(jìn)行參數(shù)綁定:
user=> (defmacro my-plus2
#_=> [[op1 op op2]]
#_=> (list op op1 op2))
#'user/my-plus2
user=> (my-plus2 (1 + 1))
Symbol and Value
編寫macro的時(shí)候,我們其實(shí)就是構(gòu)建list供Clojure去evaluate乍恐,所以在macro里面评疗,我們需要quote expression,這樣才能給Clojure返回一個(gè)沒有evaluated的list茵烈,而不是在macro里面就自己evaluate了百匆。也就是說,我們需要明確了解symbol和value的區(qū)別呜投。
譬如加匈,現(xiàn)在我們要實(shí)現(xiàn)這樣一個(gè)功能寄症,一個(gè)macro,接受一個(gè)expression矩动,打印并且輸出它的值有巧,可能看起來像這樣:
user=> (let [result 1] (println result) result)
1
1
然后我們定義這個(gè)macro:
user=> (defmacro my-print
#_=> [expression]
#_=> (list let [result expression]
#_=> (list println result)
#_=> result))
我們會(huì)發(fā)現(xiàn)出錯(cuò)了,錯(cuò)誤為"Can't take value of a macro: #'clojure.core/let"悲没,為什么呢篮迎?在上面這個(gè)例子中,我們其實(shí)想得到的是let symbol示姿,而不是得到let這個(gè)symbol引用的value甜橱,這里let并不能夠被evaluate。
所以為了解決這個(gè)問題栈戳,我們需要quote let岂傲,只是返回let這個(gè)symbol,然后讓Clojure外面去負(fù)責(zé)evaluate子檀,如下:
user=> (defmacro my-print
#_=> [expression]
#_=> (list 'let ['result expression]
#_=> (list 'println 'result)
#_=> 'result))
#'user/my-print
user=> (my-print 1)
1
1
Quote
Simple Quoting
如果我們僅僅想得到一個(gè)沒有evaluated的symbol镊掖,我們可以使用quote:
user=> (+ 1 2)
3
user=> (quote (+ 1 2))
(+ 1 2)
user=> '(+ 1 2)
(+ 1 2)
user=> '123
123
user=> 123
123
user=> 'hello
hello
user=> hello
CompilerException java.lang.RuntimeException: Unable to resolve symbol: hello in this context
Syntax Quoting
在前面,我們通過'
以及quote了解了simple quoting褂痰,Clojure還提供了syntax quoting `
user=> `1
1
user=> `+
clojure.core/+
user=> '+
+
可以看到亩进,syntax quoting會(huì)返回fully qualified symbol,所以使用syntax quoting能夠讓我們避免命名沖突缩歪。
另一個(gè)syntax quoting跟simple quoting不同的地方在于归薛,我們可以在syntax quoting里面使用~
來unquote一些form,這等于是說匪蝙,我要quote這一個(gè)expression主籍,但是這個(gè)expression里面某一個(gè)form先evaluate,譬如:
user=> `(+ 1 ~(inc 1))
(clojure.core/+ 1 2)
user=> `(+ 1 (inc 1))
(clojure.core/+ 1 (clojure.core/inc 1))
這里還需要注意一下unquote splicing:
user=> `(+ ~(list 1 2 3))
(clojure.core/+ (1 2 3))
user=> `(+ ~@(list 1 2 3))
(clojure.core/+ 1 2 3)
syntax quoting會(huì)讓代碼更加簡潔逛球,具體到前面print那個(gè)例子千元,我們let這些都加了quote,代碼看起來挺丑陋的需忿,如果用syntax quoting诅炉,如下:
user=> (defmacro my-print2
#_=> [expression]
#_=> `(let [result# ~expression]
#_=> (println result#)
#_=> result#))
#'user/my-print2
user=> (my-print2 1)
1
1