以下是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))
答案 0 :(得分:1)
你不会说这是怎么打算调用的,这段代码有点纠结,所以快速浏览一下,我无法说出它应该如何工作。但是,我可以回答你的问题。
首先,让我说那个
(if (functionp base) (funcall base) base)
编程风格很糟糕。这有效地将整体放在您的语义空间中,创建一个完全不同的函数处理作为对象而不是其他对象。在Common Lisp中,函数应该是一个可以选择传递的对象。如果你想打电话,你应该这样做,但是你不应该只对别人说'#34;如果我给你一个功能你就应该打电话给你,否则就不应该这样做。" (为什么这件事会在您阅读时看到。)
其次,正如Barmar所说,如果你写作,你基本上是在说#34;取代码然后插入它进行评估"。如果你写
#'(lambda () ,base)
你说的是把代码放在一个函数中,以便它的执行被延迟。现在,您将它传递给一个函数,当它接收函数时将调用它。而且,调用它将在调用者的词汇环境中对其进行评估,并且动态状态没有中间变化。因此,您认为这与仅在呼叫站点评估它是一回事(除了稍微增加一些开销)。但是,有一种情况会有所不同。
如果放在基础参数位置的东西是变量(让我们说X)或数字(让我们说3),那么你要么做(lrec。 .. X)或(lrec 3)或者你正在做
(lrec ... #'(lambda () X))
或
(lref ... #'(lambda () 3))
到目前为止一切顺利。如果它到达来电者,它会说"哦,你只是指X(或3)的值。"但还有......
如果你说的是一个表达式,它会在你对on-cdrs的调用的基本参数位置或你对alrec的调用中产生一个函数,那么你将会得到不同的结果,这取决于你是写作,基数还是# '(lambda(),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得到的是一个参数的函数,在评估时,它将返回评估其正文的结果,即返回一个函数。
重要的要点是:
逗号正在放入一段可评估的代码中,并用逗号包裹逗号(或用lambda包装任何表达式)延迟评估。
lrec定义中的条件应该指望该值已经被评估,并且不应该采取条件效果,因为它无法知道您是否已经完全基于类型评估了某些内容,除非它基本上把一堆功能搞得一流数据。
我希望这可以帮助你看到差异。它很微妙,但它是真实的。
所以使用
#'(lambda () ,base)
保护宏不会对可能产生函数的基础进行双重评估,但另一方面,坏样式是不应该(在我看来)发生的事情。我的建议是删除对base的条件函数调用,并使其始终或从不将函数调用为base。如果你从不调用函数,调用者肯定应该使用,base。如果你总是调用函数,调用者肯定应该包含lambda包装器。这将使评估的数量具有确定性。
另外,作为一个纯粹的实际问题,我认为它更像是Common Lisp的风格,只是使用,基础而不是打扰闭包,除非表达式要做的不仅仅是跨越函数呼叫边界立即被呼叫。这是浪费时间和精力,并且可能需要额外的努力才能发挥其无法满足任何有趣目的的功能。如果lrec函数的唯一目的是支持该工具,则尤其如此。如果lrec有独立的理由签订合同,那就是另一回事了,也许你可以编写你的宏来容纳。
在像Scheme这样具有不同美学的函数式语言中更常见,有一个常规函数作为任何宏的替代,并且让该函数将这样的零参数函数作为参数以防一些用户不喜欢使用宏。但是大多数Common Lisp程序员都不会打扰你,而你的问题是关于Common Lisp的,所以我把我的大部分写作都偏向于这个方言。