lisp中的`与,是怎么用的?


#1

看onlisp里有一段代码,用来创建别名的:

(defmacro defsynonym (old-name new-name)
  `(defmacro ,new-name (&rest args)
     `(, ',old-name ,@args)))

注意到old-name前面是 ,’, 这个我完全无法理解是个什么意思啊?

我用

(pp (macroexpand-1 '(defsynonym old-fn new-fn))) 

得到的结果是这样的

(defmacro new-fn
    (&rest args)
  `(,'old-fn ,@args))

貌似前面的=,‘= 保持不变,而=,old-name= 替换成了实际的值了。

然后我想这这个结果有点奇怪啊,实际上只要扩展为

(defmacro new-fn
    (&rest args)
  `(old-fn ,@args))

就可以了嘛,所以我修改了一下defsynonym的定义,变成了

(defmacro defsynonym (old-name new-name)
  `(defmacro ,new-name (&rest args)
     `(,old-name ,@args)))

然后很神奇的发现,

(pp (macroexpand-1 '(defsynonym old-fn new-fn))) 

的扩展结果变成了

(defmacro new-fn
    (&rest args)
  `(,old-name ,@args))

这里=,old-name= 并没有被替换为实际的值。

我现在觉得凌乱了:scream:,完全不知道这个规律是怎么样的呀~~~求高手指点


21 天学会 Emacs 之 第九天: Macro 与 Use-package
eval-after-load FROM参数前的引号是干什么的?
把一个变量添加到 List 中时,如果添加它的值?
#2

单独的’,'是eval,而‘,sym’是引用原值。


#3

注:格式问题,所有的 ``` 用 backquote 表示; , 用 逗号 表示

你的例子涉及到嵌套的 backquote,关键的几点在于

  1. 嵌套的 backquote 每次只展开一层,不递归展开
  2. 每次进行 backquote 的逗号替换的时候只替换和 backquote 同一层的
  3. 嵌套的 backquote 的逗号归属:最左边的(就是 Lisp reader 最先遇到的)逗号属于最内层的 backquote,依此类推。

回到你的例子:

(defmacro defsynonym (old-name new-name)
  `(defmacro ,new-name (&rest args)
     `(, ',old-name ,@args)))

问题在于 `(,’,old-name ,@args) 的替换:这个表达式在嵌套的 backquote 里面,根据我之前提到的第三点,第一个逗号属于内层的 backquote,第二个逗号属于外层的 backquote

于是:第一个 defmacro 替换的时候,,new-name 被替换了,同时,’,old-name 在嵌套的内层 backquote 里面,这时候只替换外层 backquote 对应的部分,第一个逗号属于内层的 backquote,因此原封不动,而第二个逗号属于外层的 backquote,此时应被替换。 所以

(defsynonym old-fn new-fn)

变成

(defmacro new-fn (&rest args)
     `(, 'old-fn ,@args))

于是你的例子也就好理解了:

(defmacro defsynonym (old-name new-name)
  `(defmacro ,new-name (&rest args)
     `(,old-name ,@args)))

由于 old-name 在嵌套的 backquote 里面,而前面只有一个逗号,因此这个逗号属于内层的 backquote,在外层的 backquote 展开的时候不被替换。

另一个角度: 你可以看看 Lisp reader 读取这个表达式的结果:

(read "(defmacro defsynonym (old-name new-name)
  `(defmacro ,new-name (&rest args)
     `(,',old-name ,@args)))")

变成

(defmacro defsynonym (old-name new-name)
  (\` (defmacro (\, new-name) (&rest args)
	(\` ((\, (quote (\, old-name))) (\,@ args))))))

连续的两个逗号,第二个逗号在第一个逗号的层级里面。另外 backquote 在 Elisp 也是一个宏,名字就叫 ```,是 backquote 这个宏的别名,也就是上面的 read 的结果中用 \ 转义的 backquote。


#4

回答的好清晰,谢谢


#5

有兴趣可以挑战一下能不能读懂`once-only’


#6

不敢… 那一堆loop,看的心里犯怵