(defmacro random-choice (&rest exprs)
`(case (random ,(length exprs))
,@(let ((key -1))
(mapcar #'(lambda (expr)
`(,(incf key) ,expr))
exprs))))
所以我对这个函数执行了 macroexpand-1
并且我大致了解这个宏是如何工作的,但是我对 Graham 如何嵌套反引号 ` 以及他如何使用 ,@ 展开感到非常困惑案例。
- 我们什么时候可以嵌套反引号?
- 为什么 Graham 在这个例子中嵌套反引号?
- 为什么
,@
将案例扩展为(random ,(length exprs))
案例? - 我知道
mapcar
主要是为了让我们可以增加key
,但是这个宏是怎么知道应用mapcar
的(random ,(length exprs))
次? - 逗号
,@
拼接的隐式列表是如何形成的?
注意我很笨所以请用最基本的术语解释。
编辑:
我现在明白最里面的反引号 (,(incf key) ,expr)
确保这个函数首先被评估所以它大致等同于 (list (incf key) expr)
,然后
,@(let ((i 0))
(mapcar #'(lambda (expr)
`(,(incf i) ,expr))
args))
被计算成类似列表'((0 a_0) (1 a_1) ... (n a_n))
,因为我们有 ,@
然后这是“拼接”成
((0 a_0))
((1 a_n))
.
.
.
((n a_n))
最后 (case (random ,(length exprs))
被评估为
case (random n)
它也给了我们外括号,留给我们
(case (random n)
((0 a_0))
((1 a_n))
.
.
.
((n a_n)))
我的事件顺序是否正确?我在网上找不到任何资源来验证,Graham 的书也没有这样分解。
最佳答案
另一种编写方式(摆脱 LET、MAPCAR + 副作用 INCF 代码):
CL-USER 44 > (defmacro random-choice (&rest exprs &aux (n (length exprs)))
`(case (random ,n)
,@(loop for ci below n and expr in exprs
collect `(,ci ,expr))))
RANDOM-CHOICE
CL-USER 45 > (macroexpand-1 '(random-choice 10 21 32 43))
(CASE (RANDOM 4) (0 10) (1 21) (2 32) (3 43))
宏使用反引号形式,内部有计算。我们可以提取计算并将部分分配给变量:
CL-USER 46 > (defmacro random-choice (&rest exprs &aux (n (length exprs)))
(let ((keyform `(random ,n))
(clauses (loop for ci below n and expr in exprs
collect `(,ci ,expr))))
`(case ,keyform
,@clauses)))
RANDOM-CHOICE
CL-USER 47 > (macroexpand-1 '(random-choice 10 21 32 43))
(CASE (RANDOM 4) (0 10) (1 21) (2 32) (3 43))
如您所见,反引号形式可以独立计算并在最后组装成反引号形式。
当宏函数包含代码片段时,最好将它们保留为引号或反引号形式 -> 这使它们更容易在宏函数中识别。将它们替换为列表计算(使用 list
、cons
,...)不太方便且可读性较差。但是然后需要正确排序反引号/反引号。在我的示例中,它稍微容易一些,因为这些部分是独立计算的。这有助于理解宏,因为它更符合 case
的语法:
CASE keyform {normal-clause}* [otherwise-clause]
normal-clause::= (keys form*)
这里我们只使用 keyform
和 {normal-clause}*
中的 0..n-1 子句。我们也不使用 otherwise-clause
。
关于lisp - Graham 的 Ansi Common Lisp : p. 170 难以理解示例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53405865/