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之魂(九):讀取器宏
推薦閱讀: