Graham的Ansi Common Lisp:p.170无法理解例子

rep*_*pos 3 lisp common-lisp

(defmacro random-choice (&rest exprs)
    `(case (random ,(length exprs))
        ,@(let ((key -1))
            (mapcar #'(lambda (expr)
                        `(,(incf key) ,expr))
                    exprs))))      
Run Code Online (Sandbox Code Playgroud)

所以我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))
Run Code Online (Sandbox Code Playgroud)

被评估为类似于列表的东西'((0 a_0) (1 a_1) ... (n a_n)),因为我们已经,@将它"拼接"成了

((0 a_0))
((1 a_n))
    .
    .
    .
((n a_n))
Run Code Online (Sandbox Code Playgroud)

最后(case (random ,(length exprs))进行评估 case (random n),它也给了我们外括号,留给我们

(case (random n)
    ((0 a_0))
    ((1 a_n))
        .
        .
        .
    ((n a_n)))
Run Code Online (Sandbox Code Playgroud)

我的事件序列是否正确?我在网上找不到任何资源来验证,格雷厄姆的书并没有像这样打破它.

Rai*_*wig 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))
Run Code Online (Sandbox Code Playgroud)

宏使用带有计算的反引号形式.我们可以提取计算并将部分分配给变量:

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))
Run Code Online (Sandbox Code Playgroud)

正如您所看到的那样,反引号形式可以独立计算,并在最后以反引号形式组装.

当宏函数包含代码片段时,最好将它们保留为带引号或反引号的形式 - >这使它们更容易在宏函数中识别.用列表计算替换它们(使用list,, cons......)不太方便且可读性差.但是,然后需要将反引号/不引号的顺序正确.在我的例子中,它稍微容易一些,因为部件是独立计算的.这有助于理解宏,因为它更符合以下语法case:

CASE keyform {normal-clause}* [otherwise-clause]

normal-clause::= (keys form*) 
Run Code Online (Sandbox Code Playgroud)

这里我们只使用keyform和0..n-1子句{normal-clause}*.我们也不使用otherwise-clause.