(defmacro random-choice (&rest exprs)
`(case (random ,(length exprs))
,@(let ((key -1))
(mapcar #'(lambda (expr)
`(,(incf key) ,expr))
exprs))))
因此,我对此功能执行了macroexpand-1
,并且我大致了解了此宏的工作原理,但是我对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)))
我知道事件的顺序正确吗?我在网上找不到任何资源可以验证,格雷厄姆的书也没有将其分解成这样。
答案 0 :(得分:7)
另一种编写方式(摆脱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
。
答案 1 :(得分:4)
什么时候可以嵌套反引号?
您始终可以嵌套反引号。但是请注意,此代码不会嵌套它们:
`(foo ; in 1 backquote here
,@(cons 'bar ; in 0 backquotes here
`(baz ; in 1 backquotes here
,(random 3))))
嵌套反引号如下:
`(let ((x `(,,y ,z))) ...)
在此示例中,为什么格雷厄姆嵌套了反引号?
他没有嵌套它们。他使用第一个反引号生成案例的外部主体,然后将其填充以mapcar
生成的案例。为了编写每种情况下要生成的代码,他使用了第二个反引号
为什么
,@
将案例扩展为(random ,(length exprs))
案例?
它不会那样做。它扩展为(length exprs)
个案例。严格地,它合并在其内部内容返回的事物列表中,在这种情况下,是一个表达式列表。
我知道mapcar的主要作用是使我们可以增加密钥,但是此宏如何知道总共
(random ,(length exprs))
次应用mapcar?
这不是mapcar
的用途或用途。
逗号,@正在拼接的隐式列表是如何形成的?
这就是mapcar的作用。
要解决您的修改问题,您可以将某些事情纠正一半,但paren太多。
mapcar
将函数按顺序应用于列表的每个元素,将结果收集到列表中:
CL-USER> (mapcar #'1+ '(1 2 3))
(2 3 4)
CL-USER>(let ((key -1))
(mapcar (lambda (x)
`(,incf key) ,x))
'(foo bar (baz wazoo)))
((0 FOO) (1 BAR) (2 (BAZ WAZOO)))
这将进入case
表达式的主体:如果随机值是0,则返回FOO
,如果它是1,则返回BAR
,依此类推。
要获取此随机值,请对0到2之间(含2和2)的随机整数执行(random 3)
。