【问题标题】:not sure about the definition of a macro in a sample of OnLisp不确定 OnLisp 示例中宏的定义
【发布时间】:2014-04-11 01:11:28
【问题描述】:

这里有一些来自 OnLisp 文本的示例代码。 我的问题是为什么要使用 lambda 函数,

`(funcall (alrec ,rec #'(lambda () ,base)) ,@lsts))

作为 on-cdrs 定义中 alrec 的第二个参数?

如果我只定义它而不使用 lambda 有什么区别?

`(funcall (alrec ,rec ,base) ,@lsts))

(defun lrec (rec &optional base)
  (labels ((self (lst)
             (if (null lst)
                 (if (functionp base)
                     (funcall base)
                   base)
               (funcall rec (car lst)
                        #'(lambda ()
                            (self (cdr lst)))))))
    #'self))


(defmacro alrec (rec &optional base)
  "cltl2 version"
  (let ((gfn (gensym)))
    `(lrec #'(lambda (it ,gfn)
               (symbol-macrolet ((rec (funcall ,gfn)))
                                ,rec))
           ,base)))

(defmacro on-cdrs (rec base &rest lsts)
  `(funcall (alrec ,rec #'(lambda () ,base)) ,@lsts))

【问题讨论】:

  • lrec 的参数是函数。 alrec 将它的第二个参数传递给lrec,所以它必须是一个函数。
  • @Barmar:不妨将您的评论变成答案,因为它是一个。

标签: common-lisp


【解决方案1】:

您没有说明它是如何被调用的,而且这段代码有点混乱,所以一眼看去,我无法说出它应该如何工作。不过,我可以回答你的问题。

首先,让我这么说

(if (functionp base) (funcall base) base)

是糟糕的编程风格。这有效地将一个整体置于您的语义空间中,创建了与作为对象的其他事物完全不同的作为对象的函数处理。在 Common Lisp 中,函数应该是您可以选择传递的对象。如果你想调用它,你应该这样做,但你不应该只是对某人说“如果我给你一个函数,你应该调用它,否则你不应该调用它”。 (为什么这很重要,您将在继续阅读时看到。)

其次,正如 Barmar 所说,如果您编写 ,base 基本上是在说“获取代码并将其插入此处进行评估”。如果你写

#'(lambda () ,base) 

您是说将代码放入函数中,以便延迟执行。现在,您将它传递给一个函数,当它接收到该函数时将调用它。而且,调用它将在调用者的词法环境中对其进行评估,并且动态状态没有任何干预变化。所以你会认为这与在呼叫站点评估它是一样的(除了更多的开销)。但是,也有不同的情况。

如果你放在基本参数位置的东西是一个变量(比如 X)或数字(比如 3),那么你要么在做 (lrec ... X) 要么 (lrec 3) 要么否则你会做的

(lrec ... #'(lambda () X)) 

(lref ... #'(lambda () 3))

到目前为止一切顺利。如果它到达调用者,它会说“哦,你的意思是 X(或 3)的值”。但还有更多……

如果您改为在调用 on-cdrs 或调用 alrec 的基本参数位置生成函数的表达式,则根据您编写的是 ,base 还是 #'(拉姆达(),基地)。例如,你可能已经把

#'f 

#'(lambda () x) 

或者,更糟糕的是,

#'(lambda (x) x)

在基本参数位置。在这种情况下,如果您使用了 ,base,那么在将参数传递给 lrec 之前,将立即计算该表达式,然后 lrec 将接收一个函数。 然后它会被第二次调用(这可能不是宏用户所期望的,除非文档非常清楚地说明了这种不优雅,并且宏的用户已经足够细心地阅读了文档)。 第一种情况会返回 3,第二种情况会返回 x 的值,第三种情况会出现错误情况,因为调用的参数个数错误。

如果你用

来实现它
#'(lambda () ,base)

然后 lrec 将接收评估结果作为参数

#'(lambda () #'f)

#'(lambda () #'(lambda () 3))

#'(lambda () #'(lambda (x) x))

取决于您在上面的示例中作为参数给出的内容。但无论如何, lrec 得到的是一个参数的函数,当评估时,将返回评估其主体的结果,也就是说,将返回一个函数。

重要的要点是:

  1. 逗号被放入一段可评估的代码中,并且用 lambda 包装逗号的表达式(或用 lambda 包装任何表达式)延迟评估。

  2. lrec 定义中的条件应该期望该值是否已经被评估,并且不应该产生条件效果,因为它无法知道您是否已经完全基于类型评估了某些东西,除非它基本上使一堆函数作为一流的数据。

我希望这可以帮助您了解不同之处。这很微妙,但它是真实的。

所以使用

#'(lambda () ,base)

保护宏免受可能产生函数的基础的双重评估,但另一方面,不良风格是不应该(在我看来)发生的事情。我的建议是删除对 base 的条件函数调用,并使其始终或从不将 base 作为函数调用。如果你让它永远不调用函数,调用者绝对应该使用 ,base。如果你让它总是调用函数,调用者肯定应该包含 lambda 包装器。这将使评估的数量具有确定性。

另外,作为一个纯粹的实际问题,我认为它更像是 Common Lisp 的风格,只是使用 ,base 而不会打扰闭包,除非表达式要做的不仅仅是跨越函数调用边界立即打电话。这是在浪费时间和精力,并且可能会在实际上没有任何有趣目的的情况下使用该功能。如果 lrec 函数的唯一目的是支持此功能,则尤其如此。如果 lrec 有一个独立的理由来拥有它所做的合同,那是另一回事,也许你会编写你的宏来适应。

在像 Scheme 这样的函数式语言中更常见,它具有不同的审美,有一个常规函数作为任何宏的替代,并且让该函数将这样一个零参数函数作为参数,以防万一用户不喜欢使用宏。但是大多数 Common Lisp 程序员都不会打扰,而且你的问题是关于 Common Lisp,所以我在这里的大部分写作都偏向于这种方言。

【讨论】:

  • 嗨,肯特,非常感谢您的详细解释。非常感谢您的努力!
猜你喜欢
  • 1970-01-01
  • 2011-07-13
  • 2011-03-03
  • 2013-12-28
  • 1970-01-01
  • 2014-02-19
  • 1970-01-01
  • 2016-02-26
相关资源
最近更新 更多