1. 反引用
上文我們介紹了如何使用defmacro
定義宏蒙畴,
(defmacro inc (var)
(list 'setq var (list '1+ var)))
我們定義了inc
宏受楼,(inc x)
會(huì)被展開(kāi)為(setq x (1+ x))
转唉,因此,
(defvar x 0)
(inc x)
x ; 1
宏做的是語(yǔ)法對(duì)象的變換操作贩绕,因此幾乎每個(gè)宏最后都返回一個(gè)列表火的,
可是,類似上述inc
宏那樣丧叽,每次都使用list
來(lái)創(chuàng)建列表卫玖,是一件麻煩的事情,
所以踊淳,Lisp提供了反引用(quasiquote/backquote)假瞬,可以便捷的生成列表。
例如迂尝,以上inc
宏使用反引用來(lái)生成列表脱茉,可以修改為,
(defmacro inc (var)
`(setq ,var (1+ ,var)))
可以看到垄开,反引用``(setq ,var (1+ ,var)))與
(inc x)的展開(kāi)式
(setq x (1+ x))非常相像琴许, 我們只需要將反引號(hào)
` 去掉,然后將反引用表達(dá)式中的逗號(hào)表達(dá)式
,var溉躲,替換為
var綁定的值
x`即可榜田。
2. 反引用表達(dá)式的求值規(guī)則
下面我們通過(guò)幾個(gè)例子來(lái)說(shuō)明反引用的使用方式益兄,其中=>
表示“求值為”。
求值規(guī)則:
(1)如果反引用表達(dá)式中不包含逗號(hào),
箭券,那么它和引用表達(dá)式是一樣的净捅,
因此反引用通常被看做是一種特殊的引用(quote)
`(a list of (+ 2 3) elements)
=> (a list of (+ 2 3) elements)
(2)反引用表達(dá)式中的逗號(hào)表達(dá)式會(huì)被求值
`(a list of ,(+ 2 3) elements)
=> (a list of 5 elements)
(3)反引用表達(dá)式中的,@
表達(dá)式,也會(huì)被求值辩块,但是要求其結(jié)果必須是一個(gè)列表蛔六,
,@
會(huì)去掉列表的括號(hào),將列表中的元素放到,@
表達(dá)式出現(xiàn)的位置
(defvar x '(2 3))
`(1 ,@x 4)
=> (1 2 3 4)
`(1 ,@(cdr '(1 2 3)) 4)
=> (1 2 3 4)
3. 生成宏定義的宏
以上废亭,我們定義了宏inc
国章,
宏調(diào)用(inc x)
,會(huì)被展開(kāi)為(setq x (1+ x))
豆村。
在編寫(xiě)宏的時(shí)候液兽,一個(gè)常用的思路是,
先考慮展開(kāi)關(guān)系你画,即我們期望將A展開(kāi)為B抵碟,再根據(jù)這個(gè)線索編寫(xiě)相應(yīng)的宏。
那么坏匪,我們可否編寫(xiě)一個(gè)宏,讓它展開(kāi)成(defmacro ...)
呢撬统?
是可以的适滓,這是一種展開(kāi)為宏定義的宏,它可以作為defmacro
來(lái)使用恋追。
考慮展開(kāi)關(guān)系凭迹,我們期望將(create-inc)
展開(kāi)為
(defmacro inc (var)
`(setq ,var (1+ ,var)))
于是,宏create-inc
就應(yīng)該被這樣定義苦囱,
(defmacro create-inc ()
`(defmacro inc (var)
`(setq ,var (1+ ,var))))
我們來(lái)試驗(yàn)一下嗅绸,
(create-inc) ; 定義了inc
(defvar x 0)
(inc x) ; 使用inc
x ; 1
我們還可以給create-inc
加上參數(shù)。
考慮展開(kāi)關(guān)系撕彤,我們將(create-inc-n y)
展開(kāi)為鱼鸠,
(defmacro inc-n (var)
`(setq ,var (+ y ,var)))
那么create-inc-n
應(yīng)該怎么定義呢?事實(shí)上羹铅,
(defmacro create-inc-n (num)
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
第一次看到,',num
的時(shí)候蚀狰,我非常驚訝,這到底是什么职员?
4. 嵌套反引用
嵌套反引用指的是麻蹋,一個(gè)反引用表達(dá)式中嵌套出現(xiàn)了另一個(gè)反引用表達(dá)式。
在生成宏定義的宏中焊切,嵌套反引用經(jīng)常出現(xiàn)扮授。
嵌套反引用表達(dá)式中芳室,經(jīng)常會(huì)出現(xiàn)類似,',num
這樣的表達(dá)式,
它不能被寫(xiě)成,num
刹勃,也不能被寫(xiě)成,,num
堪侯,下面我們進(jìn)行仔細(xì)的分析。
(1),num
為什么不正確
先看一下展開(kāi)關(guān)系深夯,我們期望將(create-inc-n y)
展開(kāi)為抖格,
(defmacro inc-n (var)
`(setq ,var (+ y ,var)))
即,嵌套反引用表達(dá)式咕晋,應(yīng)該按下述方式求值雹拄,
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ y ,var)))
其中,,var
是不應(yīng)該被求值的掌呜,因?yàn)檫@是內(nèi)層反引用需要的滓玖,
如果我們將,',num
寫(xiě)成,num
,那么它就和,var
一樣不會(huì)被求值了质蕉,
`(defmacro inc-n (var)
`(setq ,var (+ ,num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,num ,var)))
這和我們期望的展開(kāi)關(guān)系不同势篡。
(2),,num
為什么不正確
寫(xiě)成,,num
在求值最外層反引用表達(dá)式的時(shí)候,確實(shí)會(huì)求值num
的值模暗,
但是禁悠,在求值內(nèi)層反引用表達(dá)式的時(shí)候,這個(gè)值還會(huì)被再求值一次兑宇。
(create-inc-n y)
將被展開(kāi)為碍侦,
`(defmacro inc-n (var)
`(setq ,var (+ ,,num ,var)))
=> (defmacro inc-n (var)
`(setq ,var (+ ,y ,var)))
可是,在進(jìn)行宏調(diào)用(create-inc-n y)
的時(shí)候隶糕,我們不應(yīng)該關(guān)心y
的值是什么瓷产,
因?yàn)樵诤暾归_(kāi)階段,y
可能還沒(méi)有值枚驻。
而且濒旦,該展開(kāi)式和我們預(yù)期的展開(kāi)結(jié)果也不相同。
(3),',num
是怎么來(lái)的
綜上分析再登,我們需要在外層反引用表達(dá)式被求值的時(shí)候尔邓,求值num
,
而在內(nèi)層反引用表達(dá)式被求值的時(shí)候霎冯,不再繼續(xù)求值num
的值铃拇,
因此,我們需要給num
的值加上一個(gè)引用來(lái)“阻止”求值沈撞。
因此慷荔,(create-inc-n y)
會(huì)被展開(kāi)為,
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,'y ,var)))
而內(nèi)層反引用表達(dá)式被求值的時(shí)候,,'y
將求值為y
显晶。
所以贷岸,(inc-n x)
將被展開(kāi)為
`(setq ,var (+ ,'y ,var))
=> (setq x (+ y x))
和我們期望的展開(kāi)結(jié)果相同。
5. 嵌套反引用的求值規(guī)則
在生成宏定義的宏中磷雇,經(jīng)常會(huì)出現(xiàn)嵌套反引用偿警,
如果我們定義了另一個(gè)宏other-macro
來(lái)生成create-inc-n
的定義,
(defmacro other-macro ()
`(defmacro create-inc-n (num)
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var)))))
那么唯笙,將出現(xiàn)三層嵌套反引用螟蒸。
不過(guò),不用擔(dān)心崩掘,嵌套反引用也是有求值規(guī)則的七嫌,以下我們用兩層嵌套反引用作為例子來(lái)說(shuō)明。
求值規(guī)則:
(1)嵌套反引用被求值的時(shí)候苞慢,一次求值诵原,只去掉一層反引用,內(nèi)層反引用不受影響挽放,
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,'y ,var)))
(2)嵌套反引用表達(dá)式中的逗號(hào)表達(dá)式绍赛,是否被求值,要根據(jù)情況來(lái)定辑畦,
如果最外層嵌套反引用總共有n
層吗蚌,那么一定不會(huì)出現(xiàn)包含大于n
個(gè)逗號(hào)的表達(dá)式,
且包含逗號(hào)數(shù)目小于n
的表達(dá)式不會(huì)被求值纯出,只有逗號(hào)數(shù)目等于n
的表達(dá)式才會(huì)被求值褪测。
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,'y ,var)))
最外層嵌套反引用總共有n=2
層,
,var
表達(dá)式包含一個(gè)逗號(hào)潦刃,1<n
,不會(huì)被求值懈叹,
,',num
表達(dá)式包含兩個(gè)逗號(hào)乖杠,2=n
,會(huì)被求值澄成。
(3)被求值的逗號(hào)表達(dá)式胧洒,其求值方式是,
去掉最右邊的一個(gè)逗號(hào)墨状,然后將表達(dá)式替換成它的值卫漫。
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,'y ,var)))
,',num
,去掉最右邊的逗號(hào),'num
肾砂,然后將num
替換成它的值y
列赎,
于是得到了,'y
。
參考
GNU Emacs Lisp Reference Manual
ANSI Common Lisp
On Lisp
Let Over Lambda