Common lisp 不确定OnLisp示例中宏的定义

Common lisp 不确定OnLisp示例中宏的定义,common-lisp,Common Lisp,以下是来自OnLisp文本的一些示例代码。 我的问题是为什么要麻烦使用lambda函数 `(funcall(alrec,rec#'(lambda(),base)),@lsts)) 作为在CDR上定义的alrec的第二个参数 如果我只定义它而不使用lambda,有什么区别 `(funcall(alrec,rec,base),@lsts)) 你没有说这是如何被调用的,而这段代码有点纠结,所以快速看一眼,我就说不出它应该如何工作。不过,我可以回答你的问题 首先,让我这么说 (if (functionp

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

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

作为在CDR上定义的alrec的第二个参数

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

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


你没有说这是如何被调用的,而这段代码有点纠结,所以快速看一眼,我就说不出它应该如何工作。不过,我可以回答你的问题

首先,让我这么说

(if (functionp base) (funcall base) base)
这是一种糟糕的编程风格。这有效地将一个整体放在语义空间中,创建了一种完全不同于其他对象的功能处理方式。在CommonLisp中,函数应该是可以选择传递的对象。如果你想调用它,你应该这样做,但你不应该只对某人说“如果我给你一个函数,你应该调用它,否则你不应该。”(为什么这件事会在你继续阅读时被看到。)

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

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

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

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

到目前为止还不错。如果它到达调用方,它会说“哦,你只是指X的值(或3的值)。”但还有更多

如果改为使用一个表达式,该表达式在对on cdrs的调用或对alrec的调用的基参数位置生成一个函数,那么根据您编写的是base还是#’(lambda(),base),将得到不同的结果。例如,您可能已经将

#'f 

或者更糟糕的是

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

如果您使用

#'(lambda () ,base)
然后,lrec将收到评估其中一个参数的结果作为参数

#'(lambda () #'f)

这取决于你从上面的例子中给出的论点。但在任何情况下,lrec得到的是一个参数的函数,当对该参数求值时,将返回对其主体求值的结果,也就是说,将返回一个函数

重要的收获如下:

  • 逗号放在一段可计算代码中,用lambda包装逗号的表达式(或用lambda包装任何表达式)会延迟计算

  • lrec定义中的条件应该期望值已经计算过或没有计算过,并且不应该产生条件效应,因为它不知道是否已经纯粹基于类型计算过某个值,除非它基本上把函数作为一级数据弄乱了

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

    所以使用

    #'(lambda () ,base)
    
    保护宏免受可能产生函数的基的双重求值,但另一方面,坏样式是不应该发生的(在我看来)。我的建议是删除对base的条件函数调用,并使其始终或从不将base作为函数调用。如果您使其从不调用该函数,则调用者肯定应该使用,base。如果让它总是调用函数,那么调用者肯定应该包含lambda包装器。这将使评估的数量具有确定性

    另外,作为一个纯粹的实际问题,我认为它更像是普通Lisp的风格,只是为了使用,而不是为了闭包,除非表达式要做的不仅仅是跨越函数调用边界来立即调用。这是一种浪费时间和精力的行为,而且可能需要额外考虑它的功能,因为它实际上没有任何有趣的用途。如果lrec功能的唯一目的是支持该设施,则尤其如此。如果lrec有一个独立的理由拥有它所拥有的合同,那是另一回事,也许您应该编写宏来适应


    在函数式语言(如Scheme)中更常见的是,它具有不同的美学,使用常规函数作为任何宏的替代,并让该函数将零参数函数作为参数,以防某些用户不喜欢使用宏。但是大多数普通的Lisp程序员都不介意,你的问题是关于Common Lisp的,所以我在这里的大部分文章都偏向于这种方言。

    lrec的参数是函数
    alrec
    将其第二个参数传递给
    lrec
    ,因此它必须是一个函数。@Barmar:最好将您的注释变成一个答案,因为它是一个答案。嗨,肯特,非常感谢您的详细解释。非常感谢您的努力!
    #'(lambda () ,base)
    
    #'(lambda () #'f)
    
    #'(lambda () #'(lambda () 3))
    
    #'(lambda () #'(lambda (x) x))
    
    #'(lambda () ,base)