[Emacs] 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)    ; 使用inc

x    ; 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 Lisp
Let Over Lambda

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容