在Emacs中,(function)有什么作用吗?

7

函数文档的介绍中得知:

像`quote'一样,但更适用于函数作为对象。 在字节编译中,`function'会导致其参数被编译。 `quote'不能做到这一点。

因此,可以进行#'(lambda ...)操作以启用 lambda 形式的字节编译。

另一方面,在官方手册中有提到,现在已经不再需要这样做了。

lambda 表达式有一个额外的作用:通过使用 function 作为子程序告诉 Emacs 评估器和字节编译器其参数是一个函数。
[...] 以下形式都是等效的:

(lambda (x) (* x x)) 
(function (lambda (x) (* x x))) 
#'(lambda (x) (* x x))

在这种情况下,该函数形式无用。

是否有其他情况需要使用函数形式?
是否存在既不必要也不等同于quote的情况?


请注意,我知道这个相关的问题。https://dev59.com/xXI-5IYBdhLWcg3wcn3m - Malabarba
请注意,(lambda ...) 并不总是等同于 (function (lambda ...)),因此对于您的问题的一个答案是:“是的,在足够旧的 Emacs 版本中是这样的”。 - phils
现在不必再使用 (function (lambda ...)) 而不是 (lambda ...)。但第一个引号将其与 quote 进行了比较。你应该优先选择 (funcall (lambda ...) ...)(funcall (function (lambda ...)) ...) 而不是 (funcall (quote (lambda ...)) ...)。在前者中,你得到了一个编译后的函数;而在后者中,你有一个字面上的列表。 - Joshua Taylor
@phils:lambda作为一个宏被添加到了1992年(由Jim Blandy)的function中,即Emacs-19。因此,虽然在Emacs-18中等价性不成立,但那是很久以前的事了。我仍然在这里使用Emacs-19,但我没有Emacs-18二进制文件。 - Stefan
谢谢,Stefan。我确信“足够老”的定义会比任何人实际担心的要古老得多,但听到确切的细节总是很有趣的 :) (我的评论只是为了指出曾经有过差异)。 - phils
3个回答

11

#'(也称为function)在lambda内部使用,因为lambda被定义为将自己包装在一个function中的宏。

但是除此之外,您确实可以编写几乎任何Elisp代码而不使用#'。然而还有一个微妙之处:如果您编写#'foo,则告诉Emacs您认为foo是一个函数的名称,因此最近版本的字节编译器将在foo不是已知函数时发出警告(就像他们已经多年警告调用(foo...)一样)。

正如@Bruce指出的那样,与'foo相比,使用#'foo对通过cl-fletcl-labels本地定义的函数也会产生真正的影响:在这种情况下,#'foo是指局部定义的函数,而'foo只是指与本地函数定义实际上无关的foo符号,可能未绑定或绑定到另一个函数:

(cl-flet ((a () 1)) (list 'a (functionp 'a) #'a))

返回值

(a nil (lambda nil 1))

这位Reddit上的评论者发现了另一个区别。函数进行词法绑定。http://www.reddit.com/r/emacs/comments/2ddwgc/does_the_function_form_serve_any_actual_purpose/cjooj19 - Malabarba
确实,Bruce,我忘记了这一点,但是#'是必需的,以便引用通过cl-fletcl-labels定义的本地函数。 - Stefan
-1 这解释了(lambda ...)和(function (lambda ...))之间的(非)区别,但它根本没有涉及问题的第一部分。在Emacs中,您仍然可以执行(funcall (quote (lambda ...)) ...),并且在这种情况下,您不会将函数形式编译为函数,而是将其编译为列表。不是因为(function (lambda ...))优于(lambda ...),而是因为(function (lambda ...))优于(quote (lambda ...))。 - Joshua Taylor
1
我不明白你所指的“问题的第一部分”。但确实,#'(lambda ...)(lambda ...)没有优先选择。 - Stefan
不是前者扩展到后者,而是在(quote (lambda …))(function (lambda …))之间的区别。在Emacs中,你仍然可以使用以lambda开头的列表进行funcall(例如,(funcall'(lambda(x) x)2);=>2),但你不会得到任何编译。这就是(function…)(或缩写为#'…)的好处所在。 - Joshua Taylor
显示剩余4条评论

4

lambda是一个宏。它必须被展开并且每次都会被展开。就像这样:

(lambda (x) x) -> (function (lambda (x) x)) ; #'(lambda (x) x)

function 是一个特殊的运算符。实际上,它对 lambda 表达式进行了特殊处理,否则我们将永远无法扩展 lambda 宏:

(lambda (x) x) ->
  (function (lambda (x) x)) ->
    (function (function (lambda (x) x))) ->
      ...

function函数检查其参数,如果是一个lambda形式,则编译该函数。如果参数是一个符号,则搜索与该符号相关联的已编译函数。

因此,我们现在看到,function(像quote一样)不会对其参数进行求值。它必须具有特殊行为,因此它是一个特殊运算符。


从技术上讲,函数本身并不编译任何内容,它只是启用了编译。我之所以问这个问题,是因为 lambda 本身已经启用了编译,而且我不知道编译器实际上编译符号的任何情况。 - Malabarba
@Bruce,当应用于lambda时,function返回词法闭包。它是否被编译取决于实现。例如,在SBCL中一切都是编译的。每个闭包都是如此。至于Emacs Lisp,我真的不知道它是如何工作的。我使用“编译”这个词是为了简化。 - Mark Karpov
我明白你的观点。我提到的是emacs lisp,在那里(lambda)与(function (lambda))是完全相同的。但是现在我明白了你回答的价值。 - Malabarba
@BruceConnor 问题中的第一个引用是关键,它说明了emacs如何处理绝对不会编译的代码。例如,在Emacs中,但不在CL中,您可以(funcall'(lambda(x)x)2)。由于在大多数Lisp中都无法这样做,因此差异并不明显,但这里的重要区别在于(quote(lambda…))(function(lambda…))之间的区别,而不是(function(lambda…))(lambda…)之间的区别(因为后者只是扩展为前者)。 - Joshua Taylor

4
所以要启用 lambda 形式的字节编译,可以使用 "#'(lambda ...)"。另一方面,正如手册中所述,这不再是必需的。现在程序员无需编写 "(function (lambda …))"(或者简写为 "#'(lambda …)"),因为 "(lambda …)" 会展开为 "(function (lambda …))"。其他答案已经解释得很好了。你首先引用了 function 的文档。从函数形式的文档中,我们可以看到:“像 quote 一样,但更适用于函数对象。在字节编译中,function 会导致其参数被编译。quote 不能这样做。”因此,function 的文档并没有涉及 "(function (lambda …))" 和 "(lambda …)" 之间的区别,而是讲述了 "(quote (lambda …))" 和 "(function (lambda …))" 之间的区别。在大多数现代 Lisp 中(如 Common Lisp、Scheme 等),"(quote (lambda …))" 或简单地说 "'(lambda …))" 是一个列表,它不能被调用。例如,在 SBCL 中:
* (funcall '(lambda () 42))
; debugger invoked on a TYPE-ERROR in thread #<THREAD "initial thread" RUNNING {1002978E71}>:
;  The value (LAMBDA () 42) is not of type (OR FUNCTION SYMBOL).

然而,在Emacs Lisp中,您可以调用一个列表:

(funcall '(lambda () 42))
;=> 42

在询问函数是否有任何用途时,我们必须问“没有它我们能做什么”或“有什么替代品?” 我们不能回答“我们只需编写(lambda …)”,因为正如其他人指出的那样,它只会扩展为(function (lambda …))。如果我们没有函数,我们仍然可以使用quote。我们仍然可以编写一个lambda宏,将其扩展为(quote(lambda …)),从而编写(funcall(lambda …)),代码看起来相同。问题是,“有什么区别?”不同之处在于,在基于引号的版本中,我们传递文字列表,并且这些列表不能编译为函数,因为我们仍然必须能够对它们执行类似列表的操作(例如,获取它们的car和cdr)。这就是function有用的地方,无论是我们自己编写它,还是依赖宏在扩展中使用它。它提供了一种编写函数对象而不是列表的方法。

确实。这也在https://dev59.com/VmQn5IYBdhLWcg3we3EE#16802304上讨论过。 - Stefan
@Stefan 现在这是一个很好的回答,讨论了这个问题。如果你触及到那一点,我很乐意在这里+1你的答案。我认为这是回答“(函数)在Emacs中有任何用途吗?”的重要部分。如果您无法对列表进行funcall,则(lambda…)已经类似于(function(lambda…))。除了本地函数(如flet/labels)之外,它只是真正需要的,因为有一个带引号的替代方案。 - Joshua Taylor

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