1. 反引用
上文我們介紹了如何使用defmacro定義宏,
(defmacro inc (var)
(list 'setq var (list '1+ var)))
我們定義了inc宏,(inc x)會被展開為(setq x (1+ x)),因此,
(defvar x 0)
(inc x)
x ; 1
宏做的是語法對象的變換操作,因此幾乎每個(gè)宏最后都返回一個(gè)列表,
可是,類似上述inc宏那樣,每次都使用list來創(chuàng)建列表,是一件麻煩的事情,
所以,Lisp提供了反引用(quasiquote/backquote),可以便捷的生成列表。
例如,以上inc宏使用反引用來生成列表,可以修改為,
(defmacro inc (var)
`(setq ,var (1+ ,var)))
可以看到,反引用``(setq ,var (1+ ,var)))與(inc x)的展開式(setq x (1+ x))非常相像, 我們只需要將反引號` 去掉,然后將反引用表達(dá)式中的逗號表達(dá)式,var,替換為var綁定的值x`即可。
2. 反引用表達(dá)式的求值規(guī)則
下面我們通過幾個(gè)例子來說明反引用的使用方式,其中=>表示“求值為”。
求值規(guī)則:
(1)如果反引用表達(dá)式中不包含逗號,,那么它和引用表達(dá)式是一樣的,
因此反引用通常被看做是一種特殊的引用(quote)
`(a list of (+ 2 3) elements)
=> (a list of (+ 2 3) elements)
(2)反引用表達(dá)式中的逗號表達(dá)式會被求值
`(a list of ,(+ 2 3) elements)
=> (a list of 5 elements)
(3)反引用表達(dá)式中的,@表達(dá)式,也會被求值,但是要求其結(jié)果必須是一個(gè)列表,
,@會去掉列表的括號,將列表中的元素放到,@表達(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),會被展開為(setq x (1+ x))。
在編寫宏的時(shí)候,一個(gè)常用的思路是,
先考慮展開關(guān)系,即我們期望將A展開為B,再根據(jù)這個(gè)線索編寫相應(yīng)的宏。
那么,我們可否編寫一個(gè)宏,讓它展開成(defmacro ...)呢?
是可以的,這是一種展開為宏定義的宏,它可以作為defmacro來使用。
考慮展開關(guān)系,我們期望將(create-inc)展開為
(defmacro inc (var)
`(setq ,var (1+ ,var)))
于是,宏create-inc就應(yīng)該被這樣定義,
(defmacro create-inc ()
`(defmacro inc (var)
`(setq ,var (1+ ,var))))
我們來試驗(yàn)一下,
(create-inc) ; 定義了inc
(defvar x 0)
(inc x) ; 使用inc
x ; 1
我們還可以給create-inc加上參數(shù)。
考慮展開關(guān)系,我們將(create-inc-n y)展開為,
(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)常會出現(xiàn)類似,',num這樣的表達(dá)式,
它不能被寫成,num,也不能被寫成,,num,下面我們進(jìn)行仔細(xì)的分析。
(1),num為什么不正確
先看一下展開關(guān)系,我們期望將(create-inc-n y)展開為,
(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寫成,num,那么它就和,var一樣不會被求值了,
`(defmacro inc-n (var)
`(setq ,var (+ ,num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,num ,var)))
這和我們期望的展開關(guān)系不同。
(2),,num為什么不正確
寫成,,num在求值最外層反引用表達(dá)式的時(shí)候,確實(shí)會求值num的值,
但是,在求值內(nèi)層反引用表達(dá)式的時(shí)候,這個(gè)值還會被再求值一次。
(create-inc-n y)將被展開為,
`(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)樵诤暾归_階段,y可能還沒有值。
而且,該展開式和我們預(yù)期的展開結(jié)果也不相同。
(3),',num是怎么來的
綜上分析,我們需要在外層反引用表達(dá)式被求值的時(shí)候,求值num,
而在內(nèi)層反引用表達(dá)式被求值的時(shí)候,不再繼續(xù)求值num的值,
因此,我們需要給num的值加上一個(gè)引用來“阻止”求值。
因此,(create-inc-n y)會被展開為,
`(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)將被展開為
`(setq ,var (+ ,'y ,var))
=> (setq x (+ y x))
和我們期望的展開結(jié)果相同。
5. 嵌套反引用的求值規(guī)則

在生成宏定義的宏中,經(jīng)常會出現(xiàn)嵌套反引用,
如果我們定義了另一個(gè)宏other-macro來生成create-inc-n的定義,
(defmacro other-macro ()
`(defmacro create-inc-n (num)
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var)))))
那么,將出現(xiàn)三層嵌套反引用。
不過,不用擔(dān)心,嵌套反引用也是有求值規(guī)則的,以下我們用兩層嵌套反引用作為例子來說明。
求值規(guī)則:
(1)嵌套反引用被求值的時(shí)候,一次求值,只去掉一層反引用,內(nèi)層反引用不受影響,
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,'y ,var)))
(2)嵌套反引用表達(dá)式中的逗號表達(dá)式,是否被求值,要根據(jù)情況來定,
如果最外層嵌套反引用總共有n層,那么一定不會出現(xiàn)包含大于n個(gè)逗號的表達(dá)式,
且包含逗號數(shù)目小于n的表達(dá)式不會被求值,只有逗號數(shù)目等于n的表達(dá)式才會被求值。
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,'y ,var)))
最外層嵌套反引用總共有n=2層,
,var表達(dá)式包含一個(gè)逗號,1<n,不會被求值,
,',num表達(dá)式包含兩個(gè)逗號,2=n,會被求值。
(3)被求值的逗號表達(dá)式,其求值方式是,
去掉最右邊的一個(gè)逗號,然后將表達(dá)式替換成它的值。
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,'y ,var)))
,',num,去掉最右邊的逗號,'num,然后將num替換成它的值y,
于是得到了,'y。
參考
GNU Emacs Lisp Reference Manual
ANSI Common Lisp
On Lisp
Let Over Lambda