在大多数情况下最好避免使用#',因为它“大部分”是不必要的,并且会使您的代码更加冗长。当需要某种形式的引用时,有一些例外情况(参见下面的示例 4)。
注意:本文中的所有示例都在 Emacs Lisp (GNU Emacs 25.2.1) 中进行了测试,但它们在任何 ANSI 通用 lisp 中的工作方式应该相同。两种方言的基本概念是相同的。
简单说明
首先,让我们研究一个最好避免引用的案例。函数是对自己求值的第一类对象(例如,像任何其他对象一样对待,包括将它们传递给函数并将它们分配给变量的能力)。匿名函数(例如 lambda 形式)就是这样一个例子。在 Emacs Lisp (M-x ielm RET) 或任何 ANSI common lisp 上尝试以下操作。
((lambda (x) (+ x 10)) 20) -> 30
现在,试试引用的版本
(#'(lambda (x) (+ x 10)) 20) -> "function error" or "invalid function..."
如果你坚持使用#',你必须写
(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(例如第一个元素)并使用以下规则对其进行评估:
一个。如果是原子,只需返回它的值(例如 3 -> 3,“pablo” -> “pablo”)
湾。如果是 S 表达式,则使用相同的整体过程对其进行评估
C。如果是符号,则返回其函数单元格的内容
- 评估 S 表达式的 CDR 中的每个元素(例如,除了列表的第一个元素之外的所有元素)。
- 将从 CAR 获得的函数应用于从 CDR 中的每个元素获得的值。
上述过程意味着UNQUOTED S 表达式的 CAR 中的任何符号都必须在其函数单元格中具有有效的函数定义。
现在,让我们回到文章开头的示例。为什么
(#'(lambda (x) (+ x 10)) 20)
产生错误?之所以如此,是因为 #'(lambda (x) (+ x 10)),即 S 表达式的 CAR,由于函数引号 #' 而不会被 Lisp 解释器求值。
#'(lambda (x) (+ x 10))
不是函数,而是
(lambda (x) (+ x 10))
是。请记住,引用的目的是防止评估。另一方面,lambda 形式对自身求值,这是一种函数形式,它作为 UNQUOTED 列表的 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 通过检索存储在其函数单元格中的值来评估符号应用。它得到一个可以应用于参数列表的函数过程。但是,如果我不引用+,Lisp 将检索存储在其值单元格中的值,因为它不是 S 表达式中的第一个元素。因为我们将+ 的值单元格设置为“我是加号函数”,所以 Lisp 没有获得 + 号函数单元格中保存的函数定义。实际上,如果我们没有将它的值单元格设置为“I am the plus function”,Lisp 会检索到 nil,这不是 apply 所要求的函数。
有没有办法使用+ unquoted with apply?就在这里。您可以只评估以下代码:
(setq + (symbol-function '+))
(apply + '(1 2 3))
这将评估为6,正如预期的那样,因为当 Lisp 评估 (apply + '(1 2 3)) 时,它现在发现 + 的函数定义存储在 + 的值单元格中。
示例 2 - 在值单元格中存储功能定义
假设您将函数定义存储在符号的值单元格中。实现方式如下:
(setq AFunc (lambda (x) (* 10 x)))
评估
(AFunc 2)
生成错误,因为 Lisp 在AFunc 的函数单元格中找不到函数。你可以通过使用 funcall 来解决这个问题,它告诉 Lisp 使用符号值单元格中的值作为函数定义。您可以使用“funcall”来执行此操作。
(funcall AFunc 2)
假设存储在符号值单元格中的函数定义是有效的,
(funcall AFunc 2) -> 20
您可以避免使用funcall,方法是使用fset 将lambda 形式放在符号的函数单元格中:
(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)
和
(symbol-function 'ASymbol)
因为否则 Lisp 会在每种情况下获取 ASymbol 的值,从而阻止 fset、符号值和符号函数正常工作。
我希望这篇冗长的帖子对某人有用。