格雷厄姆';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
    总共
    (随机,(长度表达式))
  • ,@
    处的逗号是如何形成的隐式列表
请注意,我非常愚蠢,所以请用最基本的术语解释

编辑:

我现在了解到,最内部的backquote
(,(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*)