【问题标题】:Graham's Ansi Common Lisp: p.170 having trouble understanding exampleGraham 的 Ansi Common Lisp:p.170 无法理解示例
【发布时间】:2018-11-21 05:41:39
【问题描述】:
(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))次呢?
  • comma-at ,@ 正在拼接的隐式列表是如何形成的?

请注意,我很愚蠢,所以请用最基本的术语解释一下。

编辑:

我现在明白,最里面的反引号 (,(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 a_0)) 不是有效的 case 子句。括号太多了。
  • @RainerJoswig 当我阅读规范时,((0 a_0)) 是一个有效的子句,它只是不符合人们的期望。具体来说,它与((0 a_0) (progn)) 相同,因此该子句的含义是“如果事物为0 或a_0,则评估为nil”。尽管如果所有子句都在一个 case 中并且采用这种形式,那么 case 不会真正做任何事情。
  • @DanRobertson:在我们正在讨论的 RANDOM-CHOICE 案例中,它不会做任何有用的事情,请参阅上面的示例 - 这意味着它不是用于此处目的的有效子句。一般来说,你是对的 - CASE 子句根据 CL 语法是有效的。

标签: lisp common-lisp


【解决方案1】:

另一种写法(去掉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))

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

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

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

normal-clause::= (keys form*) 

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

【讨论】:

  • 嘿,谢谢!您的示例更清楚地说明了宏在做什么。
  • 对于函数头(defmacro random-choice (&rest exprs &aux (n (length exprs))) 变量n 的变量捕获是否没有风险?如果是这样,您如何在函数头中合并gensym? @RainerJoswig @rainer-joswig
  • @repos:你觉得有风险吗?在哪里? N 是否在生成的代码中?
【解决方案2】:

我们什么时候可以嵌套反引号?

你总是可以嵌套反引号。但是请注意,此代码不会嵌套它们:

`(foo ; in 1 backquote here
   ,@(cons 'bar ; in 0 backquotes here
           `(baz ; in 1 backquotes here
              ,(random 3))))

嵌套反引号看起来像:

`(let ((x `(,,y ,z))) ...)

为什么 Graham 在这个例子中要嵌套反引号?

他没有嵌套它们。他使用第一个反引号生成案例的外部主体,然后用mapcar 生成的案例填充它。为了编写为每个案例生成的代码,他使用了第二个反引号

为什么,@ 将 case 扩展为 (random ,(length exprs)) case?

它不会那样做。它扩展到(length exprs) 案例。严格来说,它会合并到由其中的任何内容返回的事物列表中,在本例中是表达式列表。

我知道mapcar主要是为了让我们可以增加key,但是这个宏怎么知道应用mapcar一共(random ,(length exprs))次呢?

这不是mapcar 的作用或用途。

comma-at,@正在拼接的隐式列表是如何形成的?

这就是地图车的作用。


为了解决您的编辑问题,您有一些事情是正确的,但您有太多的括号。

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,依此类推。

要获得这个随机值,您可以通过(random 3) 获取介于 0 和 2 之间的随机整数。

【讨论】:

  • 谢谢!我还有一个问题,在地图车中,为什么在评估中需要`(,(incf key) ,expr)),但exprs 不需要逗号/反引号。啊!我想我自己回答了...&rest 隐含地将exprs 放入列表中。再次感谢。
猜你喜欢
  • 1970-01-01
  • 2020-06-26
  • 1970-01-01
  • 2019-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-24
  • 1970-01-01
相关资源
最近更新 更多