lisp - Graham 的 Ansi Common Lisp : p. 170 难以理解示例

标签 lisp common-lisp

(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))

如您所见,反引号形式可以独立计算并在最后组装成反引号形式。

当宏函数包含代码片段时,最好将它们保留为引号或反引号形式 -> 这使它们更容易在宏函数中识别。将它们替换为列表计算(使用 listcons,...)不太方便且可读性较差。但是然后需要正确排序反引号/反引号。在我的示例中,它稍微容易一些,因为这些部分是独立计算的。这有助于理解宏,因为它更符合 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/

相关文章:

emacs - 如何在 slime 中运行 lisp 缓冲区并在 emacs 中查看其输出?

lisp - 变量没有在 lisp 循环中递增

common-lisp - setq 与未声明的自由变量(常见的 lisp)

common-lisp - Common Lisp 中在加载/编译时将选项传递给库的常见做法

scheme - 程序与内置数据

recursion - Paul Graham 在他的 Bel 引用文献中如何解决 mac 的循环性?

lisp - 用 lisp 进行机器人编程?

emacs - 史莱姆检查员评估 : how to get bindings in the inspector?

macros - Common Lisp 宏中的词法绑定(bind)

macros - 为什么这个 Lisp 宏作为一个整体可以工作,即使每个部分都不起作用?