2.14 函数作为对象 (Functions as Objects)

函数在 Lisp 里,和符号、字符串或列表一样,是稀松平常的对象。如果我们把函数的名字传给 function ,它会返回相关联的对象。和 quote 类似, function 是一个特殊操作符,所以我们无需引用(quote)它的实参:

  1. > (function +)
  2. #<Compiled-Function + 17BA4E>

这看起来很奇怪的返回值,是在典型的 Common Lisp 实现里,函数可能的打印表示法。

到目前为止,我们仅讨论过,不管是 Lisp 打印它们,还是我们输入它们,看起来都是一样的对象。但这个惯例对函数不适用。一个像是 + 的内置函数 ,在内部可能是一段机器语言代码(machine language code)。每个 Common Lisp 实现,可以选择任何它喜欢的外部表示法(external representation)。

如同我们可以用 ' 作为 quote 的缩写,也可以用 #' 作为 function 的缩写:

  1. > #'+
  2. #<Compiled-Function + 17BA4E>

这个缩写称之为升引号(sharp-quote)。

和别种对象类似,可以把函数当作实参传入。有个接受函数作为实参的函数是 applyapply 接受一个函数和实参列表,并返回把传入函数应用在实参列表的结果:

  1. > (apply #'+ '(1 2 3))
  2. 6
  3. > (+ 1 2 3)
  4. 6

apply 可以接受任意数量的实参,只要最后一个实参是列表即可:

  1. > (apply #'+ 1 2 '(3 4 5))
  2. 15

函数 funcall 做的是一样的事情,但不需要把实参包装成列表。

  1. > (funcall #'+ 1 2 3)
  2. 6

Note

什么是 lambda

lambda 表达式里的 lambda 不是一个操作符。而只是个符号。 在早期的 Lisp 方言里, lambda 存在的原因是:由于函数在内部是用列表来表示, 因此辨别列表与函数的方法,就是检查第一个元素是否为 lambda

在 Common Lisp 里,你可以用列表来表达函数, 函数在内部会被表示成独特的函数对象。因此不再需要 lambda 了。 如果需要把函数记为

  1. ((x) (+ x 100))

而不是

  1. (lambda (x) (+ x 100))

也是可以的。

但 Lisp 程序员习惯用符号 lambda ,来撰写函数, 因此 Common Lisp 为了传统,而保留了 lambda

defun 宏,创建一个函数并给函数命名。但函数不需要有名字,而且我们不需要 defun 来定义他们。和大多数的 Lisp 对象一样,我们可以直接引用函数。

要直接引用整数,我们使用一系列的数字;要直接引用一个函数,我们使用所谓的lambda 表达式。一个 lambda 表达式是一个列表,列表包含符号 lambda ,接着是形参列表,以及由零个或多个表达式所组成的函数体。

下面的 lambda 表达式,表示一个接受两个数字并返回两者之和的函数:

  1. (lambda (x y)
  2. (+ x y))

列表 (x y) 是形参列表,跟在它后面的是函数主体。

一个 lambda 表达式可以作为函数名。和普通的函数名称一样, lambda 表达式也可以是函数调用的第一个元素,

  1. > ((lambda (x) (+ x 100)) 1)
  2. 101

而通过在 lambda 表达式前面贴上 #' ,我们得到对应的函数,

  1. > (funcall #'(lambda (x) (+ x 100))
  2. 1)

lambda 表示法除上述用途以外,还允许我们使用匿名函数。