为什么在这个宏定义中需要使用 @ 符号?

8
在以下的when宏中:
(defmacro when (condition &rest body)
  `(if ,condition (progn ,@body)))

Why is there an "at" @ sign?

3个回答

17

在插入计算值到 quasiquote 的部分时,有两个运算符:

  • “逗号”运算符 ,
  • “逗号展开”运算符 ,@

逗号 , 插入 quasiquote sexpr 中下一个表达式的值,而逗号展开则要求其后的表达式是一个列表,并且只能在 quasiquote 列表中使用:效果是将表达式的所有元素插入到运算符出现的位置。

可以通过进行小实验很容易看出这种差异。

> (let ((x '(1 2 3 4))) `(this is an example ,x of expansion))
(THIS IS AN EXAMPLE (1 2 3 4) OF EXPANSION)

> (let ((x '(1 2 3 4))) `(this is an example ,@x of expansion))
(THIS IS AN EXAMPLE 1 2 3 4 OF EXPANSION)

如您所见,使用 ,@ 会将列表的元素直接放置在扩展中。如果没有这样做,您将得到列表放置在扩展中。

对于表达式不会产生列表的情况,使用 ,@ 将在执行替换时导致错误:

* (defun f (x) `(here ,@x we go))
F
* (f '(1 2 3))
(HERE 1 2 3 WE GO)
* (f '99)

debugger invoked on a TYPE-ERROR in thread
#<THREAD "main thread" RUNNING {10009F80D3}>:
  The value
    99
  is not of type
    LIST
  when binding SB-IMPL::X

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-IMPL::APPEND2 99 (WE GO)) [external]
0] 

在非列表中使用 @ 会导致解析 quasi-quoted 部分时出现错误:

* (defun g (x) `,@x)

debugger invoked on a SB-INT:SIMPLE-READER-ERROR in thread
#<THREAD "main thread" RUNNING {10009F80D3}>:
  `,@X is not a well-formed backquote expression

    Stream: #<SYNONYM-STREAM :SYMBOL SB-SYS:*STDIN* {10000279E3}>

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-IMPL::BACKQUOTE-CHARMACRO #<SYNONYM-STREAM :SYMBOL SB-SYS:*STDIN* {10000279E3}> #<unused argument>)
0] 

请注意,这不是 @x 导致扩展,而是 ,@x - Erlis Vidal
正确...已修复 - 6502

2
"@"也可以被看作是将列表析取并附加到所述列表中,如Practical Common Lisp所述。"
`(a ,@(list 1 2) c) 

这句话的意思是:“等同于:”。
(append (list 'a) (list 1 2) (list 'c)) 

它的翻译是:"产生:"。
(a 1 2 c)

0

那个宏定义等同于

(defmacro when (condition &rest body) 
  (list 'if condition (cons 'progn body)))

但是如果没有@,它就相当于

(defmacro when (condition &rest body) 
  (list 'if condition (list 'progn body)))

由于body是一个列表,这会导致它被评估为带有括号的函数调用,例如(when t 1 2 3)将被扩展为

(if t (progn (1 2 3)))

应该使用正确的

(if t (progn 1 2 3))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接