格雷厄姆';s Ansi Common Lisp:p.170无法理解示例
因此,我对这个函数执行了格雷厄姆';s Ansi Common Lisp:p.170无法理解示例,lisp,common-lisp,Lisp,Common Lisp,因此,我对这个函数执行了macroexpand-1,我大致了解这个宏是如何工作的,但我对Graham如何嵌套backquote`,以及他如何使用@来扩展案例感到非常困惑 我们什么时候可以取消报价 为什么Graham在这个例子中嵌套了反引号 为什么,@将案例扩展为(随机,(长度表达式))案例 我知道mapcar主要是为了增加键,但是这个宏如何知道应用mapcar总共(随机,(长度表达式))次 ,@处的逗号是如何形成的隐式列表 请注意,我非常愚蠢,所以请用最基本的术语解释 编辑: 我现在了解到,
macroexpand-1
,我大致了解这个宏是如何工作的,但我对Graham如何嵌套backquote`,以及他如何使用@来扩展案例感到非常困惑
- 我们什么时候可以取消报价李>
- 为什么Graham在这个例子中嵌套了反引号
- 为什么
将案例扩展为,@
案例李>(随机,(长度表达式))
- 我知道
主要是为了增加mapcar
,但是这个宏如何知道应用键
总共mapcar
次李>(随机,(长度表达式))
处的逗号是如何形成的隐式列表,@
(,(incf键),expr)
确保首先计算此函数,使其大致相当于(list(incf键)expr)
,然后
(defmacro random-choice (&rest exprs)
`(case (random ,(length exprs))
,@(let ((key -1))
(mapcar #'(lambda (expr)
`(,(incf key) ,expr))
exprs))))
被计算成类似于列表”((0a_0)(1a_1)…(n a_n))
,因为我们有,@
,所以这是“拼接”到
,@(let ((i 0))
(mapcar #'(lambda (expr)
`(,(incf i) ,expr))
args))
最后,(case(random,(length exprs))
计算为
case(random n)
它也给了我们外括号,给我们留下了
((0 a_0))
((1 a_n))
.
.
.
((n a_n))
我的事件顺序正确吗?我在网上找不到任何资源进行验证,格雷厄姆的书也没有这样分解
我们什么时候可以取消报价
您始终可以嵌套反引号。但是请注意,此代码不会嵌套反引号:
`(foo;在这里的1个后引号中
,@(图标栏;此处为0反引号)
`(baz;在这里有1个反引号
,(随机3)
嵌套反引号如下所示:
`(let((x`(,y,z))…)
为什么Graham在这个例子中嵌套了反引号
他不嵌套它们。他用第一个反引号生成案例的外部主体,然后用mapcar
生成的案例填充它。要为每个案例编写要生成的代码,他使用第二个反引号
为什么,@
将案例扩展为(随机,(长度表达式))
案例
它不这样做。它扩展为(length exprs)
大小写。严格地说,它合并到由其中任何内容返回的内容列表中,在本例中是表达式列表
我知道mapcar主要是为了增加关键点,但是这个宏如何知道应用mapcar总共(随机,(长度exprs))
次
这不是mapcar
的功能或用途
逗号所在的隐式列表是如何形成的
这就是mapcar所做的
为了解决您的编辑问题,您的某些内容有一半是正确的,但您有太多的参数
mapcar
将函数按顺序应用于列表的每个元素,将结果收集到列表中:
CL-USER>(地图车#'1+'(1 2 3))
(2 3 4)
CL-USER>(let((键-1))
(地图车(lambda(x)
`(,(incf键),x))
"(富吧(巴兹瓦祖))
(0英尺(1巴)(2英尺(巴兹瓦祖)))
这将进入case
表达式的主体:如果随机值为0,则返回FOO
,如果为1,则返回BAR
,依此类推
要获取此随机值,请对0和2(含0和2)之间的随机整数执行(随机3)
。另一种写入方法(摆脱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))
正如您所看到的,反向报价表单可以独立计算,并在最后组装到反向报价表单中
当宏函数包含代码段时,最好将其保留为引号或反引号形式->这使它们更容易在宏函数中识别。将其替换为列表计算(使用list
,cons
,…)不太方便,可读性也不太好。但是需要正确地确定反引号/反引号的顺序。在我的示例中,这稍微容易一些,因为部分是独立计算的。这有助于理解宏,因为它更符合case
的语法:
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))
这里我们只使用
keyform
和{normal clause}*
中的0..n-1子句。我们也不使用否则子句
谢谢!我还有一个问题,在地图车中,为什么计算中需要“(,(incf key),expr))
,但是expr
不需要逗号/反引号。啊!!我想是我自己回答的…&rest
隐式地将exprs
放入一个列表中。再次感谢。((0 a_0))
不是有效的大小写
子句。括号太多了。@RainerJoswig当我阅读规范时,((0a_0))
是一个有效的子句,它只是没有达到预期的效果。具体来说,它与((0a_0)(progn))
相同,因此子句的意思是“如果对象为0或a_0
,则求值为零”。尽管如果所有子句都在一个case中,并且是这种形式,那么case实际上不会起任何作用。@DanRobertson:在我们所讨论的随机选择情况下,它不会起任何作用,参见上面的例子-这意味着它不是一个有效的条款。总的来说,你是对的-CASE子句根据CL语法是有效的。嘿,谢谢!您的示例使宏的作用更加明确。对于函数头(defmacro random choice(&rest exprs&aux(n(length exprs))
变量n
是否没有捕获变量的风险?如果是,如何合并gensymCL-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))
CASE keyform {normal-clause}* [otherwise-clause]
normal-clause::= (keys form*)