最好在大多数情况下避免使用 # ',因为它通常是不必要的,并且会让你的代码变得更加冗长。但有一些例外情况需要引用(见下面的示例 4)。
注意:本文中的所有示例都已在 Emacs Lisp(GNU Emacs 25.2.1)中测试过,但它们应该在任何 ANSI Common Lisp 中表现相同。这两种方言的基本概念是相同的。
简单解释:
首先,让我们研究一个最好避免引用的情况。函数是一等对象(例如,像任何其他对象一样对待它们,包括将它们传递给函数并将它们赋值给变量),它们评估为它们自己。匿名函数(例如 lambda 表单)就是其中之一。在 Emacs Lisp(M-x ielm RET)或任何 ANSI Common Lisp 上尝试以下操作。
((lambda (x) (+ x 10)) 20) -> 30
现在,请尝试引用的版本。
(
如果您坚持使用 #',则必须编写:
如果您坚持使用 #',则必须编写
(funcall #'(lambda (x) (+ x 10)) 20) -> 30
详细解释
要真正理解何时需要引用,一个人必须知道Lisp如何评估表达式。请继续阅读。我承诺让这个过程简明扼要。
您需要了解一些有关Lisp的基本事实:
- Lisp“总是”评估每个表达式。除非表达式被引用,否则它将被返回未经评估的状态。
- 原子会自我评估。原子表达式不是列表。例如数字、字符串、哈希表和向量。
- 符号(变量名)存储两种类型的值。它们可以保存常规值和函数定义。因此,Lisp符号具有两个称为单元格的插槽来存储这两种类型。非功能性内容通常保存在符号的值单元格中,而函数保存在函数单元格中。同时保存非功能性和功能性定义的能力将Emacs Lisp和Common Lisp置于2-Lisp类别中。符号在表达式中使用哪个单元格取决于符号在列表中的位置。相比之下,在某些方言的Lisp中,Scheme是最著名的,符号只能保存一个值。Scheme没有价值和功能单元格的概念。这样的Lisps集体称为1-Lisps。
现在,您需要大致了解Lisp如何评估S表达式(圆括号表达式)。每个S表达式的评估方法大致如下:
- 如果引用,则返回未经评估的状态
- 如果未引用,请获取它的CAR(例如第一个元素)并使用以下规则对其进行评估:
a. 如果是原子,则只需返回其值(例如3-> 3,“pablo”->“pablo”)
b. 如果是S表达式,请使用相同的总体过程对其进行评估
c. 如果是符号,请返回其函数单元格中的内容
- 评估S表达式的CDR中的每个元素(例如列表的除第一个元素外的所有元素)。
- 将从CDR中的每个元素获得的值应用于从CAR获取的函数。
上述过程意味着,在未引用的S表达式的CAR中出现的任何符号都必须具有其函数单元格中的有效功能定义。
现在,让我们回到此帖子开头的示例。为什么
(#'(lambda (x) (+ x 10)) 20)
为什么会产生错误?因为S表达式的CAR是#'(lambda (x) (+ x 10)),由于函数引用#',Lisp解释器不会对其进行求值。
#'(lambda (x) (+ x 10))
是不是一个函数,但是
(lambda (x) (+ x 10))
需要注意的是引用的目的是为了防止求值,在另一方面,lambda表达式会自我求值,它是一个函数形式,可以作为未引用列表的CAR(首要元素)。当Lisp对CAR进行求值时
((lambda (x) (+ x 10)) 20)
它得到了(lambda (x) (+ x 20))
,这是一个函数,可以应用于列表中其余的参数(前提是CDR的长度等于lambda表达式允许的参数数量)。因此,
((lambda (x) (+ x 10)) 20) -> 30
问题是何时引用包含函数定义的函数或符号。答案几乎从不是,除非你做得“不正确”。 “不正确”意味着将函数定义放置在符号的值单元格或功能单元格中,而应该反过来。 请参阅以下示例以获得更好的理解:
示例1-存储在值单元格中的函数
假设您需要使用apply
与期望可变数量的参数的函数。其中一个示例是符号+
。Lisp将+
视为常规符号。函数定义存储在+
的功能单元格中。您可以使用以下方式将其值分配给其值单元格。
(setq + "I am the plus function").
如果您进行评估
+ -> "I am the plus function"
然而,(+ 1 2)
的功能仍然如预期一样。
(+ 1 2) -> 3
在递归中,函数apply非常有用。假设你想要对列表中的所有元素求和。你不可以写出如下代码:
(+ '(1 2 3)) -> Wrong type...
之所以会出现这种情况是因为+号操作符预期其参数为数字类型,而apply函数可以解决这个问题。
(apply #'+ '(1 2 3)) -> (+ 1 2 3) -> 6
为什么我引用了 + 符号?请记住我上面概述的评估规则。Lisp评估符号apply,通过检索其函数单元中存储的值来实现。它获得一个可以应用于一系列参数的功能过程。但是,如果我不使用引号引用+
,Lisp将会从其值单元中检索到存储的值,因为它不是S表达式中的第一个元素。由于我们将+
的值单元设置为“I am the plus function”,所以Lisp没有获取+函数单元中保存的函数定义。实际上,如果我们没有将其值单元设置为“我是加法函数”,Lisp将会检索nil,这不是apply所需的函数。
有没有一种方法可以在apply中不加引号使用+
?是的,有。您可以直接评估以下代码:
(setq + (symbol-function '+))
(apply + '(1 2 3))
由于Lisp在执行(apply + '(1 2 3))
时会找到存储在+的值单元中的函数定义,因此这将计算为6
。
示例2 - 在值单元中存储函数定义
假设您要将函数定义存储在符号的值单元中,可以通过以下方式实现:
(setq AFunc (lambda (x) (* 10 x)))
评估
(AFunc 2)
由于Lisp在AFunc
的函数单元中找不到一个函数,因此会生成一个错误。您可以通过使用funcall来解决这个问题,这告诉Lisp使用符号值单元中的值作为函数定义。您可以使用"funcall"来完成这个操作。
(funcall AFunc 2)
假设符号值单元格中存储的功能定义是有效的,
(funcall AFunc 2) -> 20
您可以使用fset
将lambda表达式放置在符号的函数单元中,从而避免使用funcall
:
(fset 'AFunc (lambda (x) (* 10 x)))
(AFunc 2)
这段代码块将返回20
,因为Lisp在AFunc
的函数单元中找到了一个函数定义。
例子3 - 本地函数
假设你正在编写一个函数,并需要一个仅在此函数范围内使用的函数。 一种典型的解决方案是定义一个仅在主函数作用域内有效的函数。试试这个:
(defun SquareNumberList (AListOfIntegers)
"A silly function with an unnecessary
local function."
(let ((Square (lambda (ANumber) (* ANumber ANumber))))
(mapcar Square AListOfIntegers)
)
)
(SquareNumberList '(1 2 3))
这个代码块将会返回
(1 4 9)
以上示例未引用Square的原因是S表达式根据我上面概述的规则进行评估。首先,Lisp提取mapcar
的函数定义。接下来,Lisp提取其第二个参数(例如'Square)值单元格的内容。最后,它将未经评估的(1 2 3)
作为第三个参数返回。
示例4 - 符号值和函数单元格的内容
以下是需要使用引号的一个示例。
(setq ASymbol "Symbol's Value")
(fset 'ASymbol (lambda () "Symbol's Function"))
(progn
(print (format "Symbol's value -> %s" (symbol-value 'ASymbol)))
(print (format "Symbol's function -> %s" (symbol-function 'ASymbol)))
)
上面的代码将被评估为
"Symbol's value -> Symbol's Value"
"Symbol's function -> (lambda nil Symbol's Function)"
nil
需要报价
(fset 'ASymbol (lambda () "Symbol's Function"))
和
(symbol-value 'ASymbol)
and
(symbol-function 'ASymbol)
否则的话,Lisp 会在每种情况下都得到 ASymbol 的值,从而阻止 fset
、symbol-value 和 symbol-function 正确工作。
希望这篇冗长的帖子能对某些人有所帮助。