Common Lisp 中需要 #' 和 funcall 符号,因为这种语言是所谓的“Lisp-2”,其中给定符号可以有两个独立且不相关的主要“含义”,通常列为
- 当用作表单的第一个元素时,它表示函数
- 在任何其他地方使用时,表示变量
这些是近似解释,正如您将在下面的示例中看到的那样,“表单的第一个元素”和“任何其他位置”不是正确的定义。
考虑例如:
上面的代码打印出144... 乍一看可能会令人惊讶,但原因是同名square有两种不同的含义:给定参数的函数返回参数相乘的结果本身和值为 12 的局部变量 square。
square 这个名字的第一次和第三次使用,意思是名为square 的函数,我把这个名字涂成了红色。第二个和第四个用途是关于一个名为square 的变量,并被涂成蓝色。
Common Lisp 如何决定哪个是哪个?重点是位置...在defun 之后,在这种情况下显然是一个函数名,就像它是(square square) 第一部分中的函数名一样。同样,作为let 表单中列表的第一个元素,它显然是一个变量名,它也是(square square) 第二部分中的一个变量名。
这看起来很精神病......不是吗?嗯,Lisp 社区确实存在一些分歧,即这种双重含义是否会使事情变得更简单或更复杂,这是 Common Lisp 和 Scheme 之间的主要区别之一。
在不深入细节的情况下,我只想说这个明显疯狂的选择是为了让 Lisp 宏更有用,提供足够的卫生以使它们能够很好地工作,而不会增加复杂性和完全卫生宏的删除表达能力。当然,这是一个复杂的问题,使得向学习它的人解释语言变得更加困难(这就是为什么 Scheme 被认为是一种更好(更简单)的教学语言),但许多专业的 lisper 认为这是一个很好的选择,使 Lisp 语言成为解决实际问题的更好工具。
同样在人类语言中,上下文无论如何都起着重要作用,对于人类来说,有时同一个词可以具有不同的含义(例如,作为名词或动词,例如“加利福尼亚是州),这并不是一个严重的问题我住在”或“陈述你的意见”)。
即使在 Lisp-2 中,您也需要将函数用作值,例如将它们作为参数传递或将它们存储到数据结构中,或者您需要将值用作函数,例如调用已接收的函数作为参数(您的情节案例)或已存储在某处。这就是#' 和funcall 发挥作用的地方...
#'foo 确实只是(function foo) 的快捷方式,就像'x 是(quote x) 的快捷方式一样。这个“函数”是一种特殊的形式,它给定一个名称(在这种情况下为foo)将关联的函数作为一个值返回,您可以将其存储在变量中或传递:
(defvar *fn* #'square)
例如,在上面的代码中,变量*fn* 将接收之前定义的函数。函数值可以作为任何其他值进行操作,例如字符串或数字。
funcall 是相反的,它允许不使用名称而是使用值来调用函数...
(print (funcall *fn* 12))
上面的代码将显示 144... 因为存储在变量 *fn* 中的函数现在被调用,并将 12 作为参数传递。
如果您知道“C”编程语言,类比考虑 (let ((p #'square))...) 就像获取函数的地址 square(与 { int (*p)(int) = □ ...} 一样),而不是 (funcall p 12) 就像使用指针调用函数(与(*p)(12) 一样,“C”允许缩写为p(12))。
Common Lisp 中令人困惑的部分是,您可以在同一范围内同时拥有一个名为 square 的函数和一个名为 square 的变量,并且该变量不会隐藏该函数。 funcall 和 function 是您可以分别在需要将变量的值用作函数或将函数用作值时使用的两个工具。