Emacs之魂(八):反引用與嵌套反引用
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
宏做的是語法對象的變換操作,因此幾乎每個宏最後都返回一個列表,
可是,類似上述inc宏那樣,每次都使用list來創建列表,是一件麻煩的事情,所以,Lisp提供了反引用(quasiquote/backquote),可以便捷的生成列表。
例如,以上inc宏使用反引用來生成列表,可以修改為,
(defmacro inc (var) `(setq ,var (1+ ,var)))
可以看到,反引用`(setq ,var (1+ ,var)))與(inc x)的展開式(setq x (1+ x))非常相像,
,var,替換為var綁定的值x即可。
2. 反引用表達式的求值規則
下面我們通過幾個例子來說明反引用的使用方式,其中=>表示「求值為」。
求值規則:
(1)如果反引用表達式中不包含逗號,,那麼它和引用表達式是一樣的,因此反引用通常被看做是一種特殊的引用(quote)`(a list of (+ 2 3) elements)=> (a list of (+ 2 3) elements)
(2)反引用表達式中的逗號表達式會被求值
`(a list of ,(+ 2 3) elements)=> (a list of 5 elements)
(3)反引用表達式中的,@表達式,也會被求值,但是要求其結果必須是一個列表,
,@會去掉列表的括弧,將列表中的元素放到,@表達式出現的位置
(defvar x (2 3))`(1 ,@x 4)=> (1 2 3 4)`(1 ,@(cdr (1 2 3)) 4)=> (1 2 3 4)
3. 生成宏定義的宏

以上,我們定義了宏inc,
(inc x),會被展開為(setq x (1+ x))。在編寫宏的時候,一個常用的思路是,
先考慮展開關係,即我們期望將A展開為B,再根據這個線索編寫相應的宏。那麼,我們可否編寫一個宏,讓它展開成(defmacro ...)呢?
defmacro來使用。考慮展開關係,我們期望將(create-inc)展開為
(defmacro inc (var) `(setq ,var (1+ ,var)))
於是,宏create-inc就應該被這樣定義,
(defmacro create-inc () `(defmacro inc (var) `(setq ,var (1+ ,var))))
我們來試驗一下,
(create-inc) ; 定義了inc(defvar x 0)(inc x) ; 使用incx ; 1
我們還可以給create-inc加上參數。
(create-inc-n y)展開為,(defmacro inc-n (var) `(setq ,var (+ y ,var)))
那麼create-inc-n應該怎麼定義呢?事實上,
(defmacro create-inc-n (num) `(defmacro inc-n (var) `(setq ,var (+ ,,num ,var))))
第一次看到,,num的時候,我非常驚訝,這到底是什麼?
4. 嵌套反引用

嵌套反引用指的是,一個反引用表達式中嵌套出現了另一個反引用表達式。
在生成宏定義的宏中,嵌套反引用經常出現。嵌套反引用表達式中,經常會出現類似,,num這樣的表達式,
,num,也不能被寫成,,num,下面我們進行仔細的分析。(1),num為什麼不正確
先看一下展開關係,我們期望將(create-inc-n y)展開為,
(defmacro inc-n (var) `(setq ,var (+ y ,var)))
即,嵌套反引用表達式,應該按下述方式求值,
`(defmacro inc-n (var) `(setq ,var (+ ,,num ,var))))=> (defmacro inc-n (var) `(setq ,var (+ y ,var)))
其中,,var是不應該被求值的,因為這是內層反引用需要的,
,,num寫成,num,那麼它就和,var一樣不會被求值了,`(defmacro inc-n (var) `(setq ,var (+ ,num ,var))))=> (defmacro inc-n (var) `(setq ,var (+ ,num ,var)))
這和我們期望的展開關係不同。
(2),,num為什麼不正確
寫成,,num在求值最外層反引用表達式的時候,確實會求值num的值,
(create-inc-n y)將被展開為,
`(defmacro inc-n (var) `(setq ,var (+ ,,num ,var)))=> (defmacro inc-n (var) `(setq ,var (+ ,y ,var)))
可是,在進行宏調用(create-inc-n y)的時候,我們不應該關心y的值是什麼,
因為在宏展開階段,y可能還沒有值。
而且,該展開式和我們預期的展開結果也不相同。
(3),,num是怎麼來的
綜上分析,我們需要在外層反引用表達式被求值的時候,求值num,
num的值,因此,我們需要給num的值加上一個引用來「阻止」求值。因此,(create-inc-n y)會被展開為,
`(defmacro inc-n (var) `(setq ,var (+ ,,num ,var))))=> (defmacro inc-n (var) `(setq ,var (+ ,y ,var)))
而內層反引用表達式被求值的時候,,y將求值為y。
所以,(inc-n x)將被展開為
`(setq ,var (+ ,y ,var))=> (setq x (+ y x))
和我們期望的展開結果相同。
5. 嵌套反引用的求值規則
在生成宏定義的宏中,經常會出現嵌套反引用,
如果我們定義了另一個宏other-macro來生成create-inc-n的定義,(defmacro other-macro () `(defmacro create-inc-n (num) `(defmacro inc-n (var) `(setq ,var (+ ,,num ,var)))))
那麼,將出現三層嵌套反引用。
不過,不用擔心,嵌套反引用也是有求值規則的,以下我們用兩層嵌套反引用作為例子來說明。求值規則:
(1)嵌套反引用被求值的時候,一次求值,只去掉一層反引用,內層反引用不受影響,`(defmacro inc-n (var) `(setq ,var (+ ,,num ,var))))=> (defmacro inc-n (var) `(setq ,var (+ ,y ,var)))
(2)嵌套反引用表達式中的逗號表達式,是否被求值,要根據情況來定,
如果最外層嵌套反引用總共有n層,那麼一定不會出現包含大於n個逗號的表達式,且包含逗號數目小於n的表達式不會被求值,只有逗號數目等於n的表達式才會被求值。
`(defmacro inc-n (var) `(setq ,var (+ ,,num ,var))))=> (defmacro inc-n (var) `(setq ,var (+ ,y ,var)))
最外層嵌套反引用總共有n=2層,
,var表達式包含一個逗號,1<n,不會被求值,,,num表達式包含兩個逗號,2=n,會被求值。(3)被求值的逗號表達式,其求值方式是,
去掉最右邊的一個逗號,然後將表達式替換成它的值。`(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 LispLet Over Lambda
下一篇:Emacs之魂(九):讀取器宏
推薦閱讀:

