首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >不确定OnLisp示例中宏的定义

不确定OnLisp示例中宏的定义
EN

Stack Overflow用户
提问于 2014-04-11 01:11:28
回答 1查看 123关注 0票数 0

以下是OnLisp文本中的一些示例代码。我的问题是为什么使用lambda函数很麻烦,

`(funcall (alrec,rec #‘)(lambda (),base),@lst)

作为在cdrs定义中的第二个论点?

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

`(功能(alrec,rec,base),@lst)

代码语言:javascript
复制
(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))
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-04-18 01:03:09

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

首先,让我说

代码语言:javascript
复制
(if (functionp base) (funcall base) base)

是糟糕的编程风格。这实际上在语义空间中放置了一个整体,创建了与作为对象的其他事物完全不同的函数处理方式。在通用Lisp中,函数应该是可以选择传递的对象。如果你想要调用它,你应该这样做,但你不应该只是对某人说“如果我给你一个函数,你应该调用它,否则你不应该。”(当你读到这件事的时候,你会发现这很重要。)

第二,正如Barmar指出的那样,如果你写的话,你基本上是在说“拿起代码并插入到这里进行计算”。如果你写

代码语言:javascript
复制
#'(lambda () ,base) 

您是说将代码放入函数中,以便延迟其执行。现在,您将它传递给一个函数,当它接收到该函数时,该函数将调用它。而且,调用它将在调用者的词法环境中评估它,并且在动态状态中不存在任何干预变化。因此,您可能会认为这与在呼叫站点上对其进行评估是一样的(除了稍微增加一些开销)。然而,有一种情况是不同的。

如果您输入的基本参数位置是一个变量(假设X)或一个数字(假设3),那么您将要么执行(lrec .X)或(lrec 3),否则你会做的

代码语言:javascript
复制
(lrec ... #'(lambda () X)) 

代码语言:javascript
复制
(lref ... #'(lambda () 3))

到目前一切尚好。如果它到达调用者,它会说:“哦,您只是指X(或3)的值。”但还有更多..。

如果您使用一个表达式来表示在调用on -cdr或调用alrec的基参数位置上生成一个函数,那么您将得到不同的结果,这取决于您是编写、base还是#'(lambda (),base)。例如,您可能会将

代码语言:javascript
复制
#'f 

代码语言:javascript
复制
#'(lambda () x) 

或者更糟的是,

代码语言:javascript
复制
#'(lambda (x) x)

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

如果您将其实现为

代码语言:javascript
复制
#'(lambda () ,base)

然后,lrec将收到评估

代码语言:javascript
复制
#'(lambda () #'f)

代码语言:javascript
复制
#'(lambda () #'(lambda () 3))

代码语言:javascript
复制
#'(lambda () #'(lambda (x) x))

这取决于你从上面的例子中给出的论点。但是在任何情况下,lrec得到的都是一个参数的函数,在计算时,它将返回评估其身体的结果,即返回一个函数。

最重要的措施是:

  1. 逗号正在删除一段可评估代码,而用lambda包装逗号的经验(或用lambda包装任何表达式)会延迟计算。
  2. lrec定义中的条件应该要么预期值已经计算过了,要么不应该产生条件效果,因为它不能知道您是否已经完全基于类型计算了某个值,除非它基本上将函数作为第一类数据造成了混乱。

我希望这能帮助你看清不同之处。它很微妙,但它是真实的。

所以用

代码语言:javascript
复制
#'(lambda () ,base)

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

此外,作为一个纯粹的实际问题,我认为它更符合Common的风格,只是使用它,而不是使用这个闭包,除非表达式要做的不仅仅是跨越函数调用边界,而是立即调用。这是对时间和精力的浪费,也许是额外的奉承,因为它实际上并没有达到任何有趣的目的。如果lrec函数的唯一用途是支持此功能,则尤其如此。如果lrec有一个独立的理由来拥有它那样的契约,那就另当别论了,也许您会编写自己的宏来适应。

在像Scheme这样的函数式语言中更常见的是,它有着不同的美学,它有一个规则的函数作为任何宏的替代,并且让这个函数使用这样的零参数函数作为参数,以防一些用户不喜欢使用宏。但是大多数情况下,通用Lisp程序员并不在意,您的问题是关于Common的,所以我的大部分写作都偏向于这种方言。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23001817

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档