Emacs Lisp:(function (lambda ...)) 和 (lambda ...) 之间的区别是什么?

10

什么是两者之间的区别?

(function (lambda ...))

并且

(lambda ...) 

'(lambda ...)

它们在很多情况下似乎是可以互换的。

2个回答

14
它们是相当可互换的。答案是,function使lambda表达式能够被字节编译,而另外两个则不行(并且等效)。注意:这并不意味着function实际上会字节编译lambda表达式。
怎么才能弄清楚呢?一些Emacs Lisp内省提供了一些线索。首先:C-h f function RET

function是'C源代码'中的特殊形式。

(function arg)

像'quote'一样,但更适用于函数对象。在字节编译中,'function'使其参数被编译。'quote'无法做到这一点。

好的,这就是(function (lambda ...))'(lambda ...)之间的区别,前者告诉字节编译器可以安全地编译表达式。而'修饰的表达式可能不一定被编译(因为它们可能只是数字列表)。
那么仅使用裸的(lambda ...)呢?C-h f lambda RET显示:

lambda是`subr.el'中的Lisp宏。

(lambda args [docstring] [interactive] body)

返回一个lambda表达式。调用形式(lambda args docstring interactive body)是自引用的;求值lambda表达式的结果就是表达式本身。然后可以将lambda表达式视为函数,即存储为符号的函数值,传递给'funcall'或'mapcar'等。

因此,(lambda ...)'(lambda ...)是等效的。
此外,还有记法#'(lambda ...),它是(function (lambda ...))的语法糖。
有关Emacs Lisp中函数的更多信息,请阅读函数信息页面
只是为了检查所有这些,您可以将以下内容键入*scratch*缓冲区并评估表达式:
(caddr '(lambda (x) (+ x x)))
(+ x x)

(caddr (lambda (x) (+ x x)))
(+ x x)

(caddr (function (lambda (x) (+ x x))))
(+ x x)

(equal '(lambda (x) (+ x x))
       (function (lambda (x) (+ x x))))
t

(equal '(lambda (x) (+ x x))
       (lambda (x) (+ x x)))
t

所以,lambda的三种变体都会构建列表,这些列表可以用作函数(其中一种可能被编译为字节码)。

但是,在表达式前面使用单引号不会使LISP解释器对其进行评估而是原样返回它吗?这意味着,'(+ 1 2)将返回为(+ 1 2),而(+ 1 2)将返回为3... - Joscha
@Joscha,不确定你在评论哪个部分。Lambda是自引用的,这意味着当解释器评估lambda表达式时,结果就是相同的lambda表达式。我认为这与大多数其他Lisp不同,因为Emacs使用了变量查找(无限范围和动态范围)。Scheme创建了一些带有环境信息的小闭包,这是由于词法范围(我认为)。 - Trey Jackson
2
(lambda ...) 实际上等同于 (function (lambda ...)),而不是 '(lambda ...)',你可以通过评估 (macroexpand (lambda (x) (+ x x))) 来尝试。 - nschum
@nschum 试试 (macroexpand '(lambda (x) (+ x x))) - 同样的事情。我相信我在我的答案中已经涵盖了这一点(请参见倒数第二个 equal 语句)。 - Trey Jackson
我的错,宏展开需要一个'(单引号)。 (我的示例实际上首先计算lambda ...)因此,区别实际上是(macroexpand '(lambda ...))(macroexpand''(lambda ...))。 你的结论不受影响,因为functionquote都被实现为return Fcar(args);并且实际上只是给编译器的提示。 但是lambda确实返回了function提示。 - nschum
这个答案似乎随着时间的推移部分错误了,(lambda ...) 现在等同于 (function (lambda ...)) 而不是 (quote (lambda ...)) - undefined

4

好的,(quote (lambda...))(lambda...)在字节编译时并不等效。引用的lambda表达式不会被字节编译,而其他所有内容都会被编译。

例如:

(defun foo (a)  
    (byte-code-function-p a))

(defun bar ()  
    (foo '(lambda () (ignore 'me))))

(defun bar2 ()  
   (foo (lambda () (ignore 'me))))

(defun bar3 ()  
    (foo (function (lambda () (ignore 'me)))))

(defun bar4 ()  
    (foo #'(lambda () (ignore 'me))))

(byte-compile 'bar)  
(byte-compile 'bar2)  
(byte-compile 'bar3)  
(byte-compile 'bar4)  

(bar)  ; -> nil  

(bar2) ; -> t  
(bar3) ; -> t  
(bar4) ; -> t

通常情况下,你不需要引用lambda函数,除非你要将其传递给的函数与仅调用(funcall)它有所不同。


2
我相信随着时间变化这已经改变。看看“(info"(elisp)Anonymous Functions")”,它说"现在可以完全省略'function'...这是因为'lambda'本身意味着'function'。"第一次阅读该页面时可能有点混乱,但你的示例可以很好地澄清:)(a)(function(lambda ...))(quote(lambda ...))的一个变体,它使得字节编译成为可能。(b)未引用的(lambda ...)现在与(function(lambda ...))相同! - phils

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接