Hello Clojure - Macro

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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市屋厘,隨后出現(xiàn)的幾起案子涕烧,更是在濱河造成了極大的恐慌,老刑警劉巖汗洒,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件议纯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡溢谤,警方通過查閱死者的電腦和手機(jī)瞻凤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門憨攒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人阀参,你說我怎么就攤上這事肝集。” “怎么了蛛壳?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵杏瞻,是天一觀的道長。 經(jīng)常有香客問我衙荐,道長捞挥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任忧吟,我火速辦了婚禮砌函,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘溜族。我一直安慰自己讹俊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布斩祭。 她就那樣靜靜地躺著劣像,像睡著了一般。 火紅的嫁衣襯著肌膚如雪摧玫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天绑青,我揣著相機(jī)與錄音诬像,去河邊找鬼。 笑死闸婴,一個(gè)胖子當(dāng)著我的面吹牛坏挠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播邪乍,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼降狠,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了庇楞?” 一聲冷哼從身側(cè)響起榜配,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吕晌,沒想到半個(gè)月后蛋褥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡睛驳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年烙心,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了膜廊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡淫茵,死狀恐怖爪瓜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情匙瘪,我是刑警寧澤铆铆,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站辆苔,受9級(jí)特大地震影響算灸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驻啤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一菲驴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧骑冗,春花似錦赊瞬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至遥倦,卻和暖如春谤绳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背袒哥。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國打工缩筛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人堡称。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓瞎抛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親却紧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子桐臊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容