跳转到内容

Common Lisp/基础主题/函数

来自维基教科书,开放的书籍,开放的世界

函数是一个几乎在每种编程语言中都会遇到的概念,但在 Lisp 中,函数尤其重要。从历史上看,Lisp 受 lambda 演算 的启发,其中每个对象都是一个函数。另一方面,在许多编程语言中,函数几乎不是对象。Lisp 不是这样:这里的函数与其他对象具有相同的权限,我们将在本章中讨论它。

定义函数

[编辑 | 编辑源代码]

函数最常使用defun(DEfine FUNction)宏来创建。此宏接受一个参数列表和一个 Lisp 表达式序列,称为函数的主体。defun 的典型用法如下

 (defun print-arguments-and-return-sum (x1 x2)
    (print x1)
    (print x2)
    (+ x1 x2))

这里,参数列表是 (x1 x2),主体是 (print x1) (print x2) (+ x1 x2)。当调用函数时,主体中的每个表达式都会按顺序(从第一个到最后一个)求值。如果发生了一些事情,我们想在到达最后一个表达式之前从函数中返回怎么办?return-from 宏允许我们做到这一点。例如

  (defun print-arguments-and-return-sum (x1 x2)
    (print x1)
    (print x2)
    (unless (and (numberp x1) (numberp x2))
       (return-from print-arguments-and-return-sum "Error!"))
    (+ x1 x2))

return-from 的第二个参数是可选的,这意味着它可以只使用一个参数调用 - 在这种情况下,函数将返回nil。但是等等:他们是怎么做到的?我们的函数只接受两个参数:不多也不少。答案是我们所说的“参数列表”并不像看起来那样简单。它实际上被称为lambda 列表,而我们将在其他地方遇到它。稍后,我将解释如何在函数中允许可选关键字参数。

函数作为数据

[编辑 | 编辑源代码]

如本章开头所述,Lisp 函数可以像任何其他对象一样使用:它们可以存储在变量中,作为参数传递给其他函数,并作为函数的值返回。在上一节中,我们定义了一个函数。该函数现在存储在符号print-arguments-and-return-sum函数单元中 - defun 将其放在那里。但是,它并不永远绑定到这个位置 - 我们可以将其提取出来,并放到另一个符号中,例如。为了访问符号的函数单元,我们可以使用访问器symbol-function。让我们将我们的函数存储在另一个符号中

 (setf paars (symbol-function 'print-arguments-and-return-sum))

第一个反应是做这样的事情

 >(paars 1 1)
 EVAL: undefined function PAARS
   [Condition of type SYSTEM::SIMPLE-UNDEFINED-FUNCTION]

这是因为我们将函数放入了符号的值单元而不是其函数单元。很容易修复

 (setf (symbol-function 'paars)
       (symbol-function 'print-arguments-and-return-sum))

由于symbol-function 是一个访问器,我们可以使用setf 与其一起使用。现在 (paars 1 1) 会产生它应该产生的结果。

虽然symbol-function 存在是有原因的,但它几乎从未在真实代码中使用。这是因为它被 Lisp 的其他几个特性所取代。其中之一是function 特殊运算符。它与symbol-function 特殊运算符的工作方式类似,只是它不会求值其参数,并且它返回当前绑定到符号的函数,这可能实际上不是其函数单元。(function foo) 也可以缩写为 #',这极大地提高了它的实用性。另一方面,不可能写

 (setf (function paars) (function print-arguments-and-return-sum))

幸运的是,可以从除符号的函数单元以外的其他地方调用函数。函数设计器要么是一个符号(在这种情况下使用其函数单元),要么是函数本身。funcallapply 用于通过其函数设计器调用函数。请记住,符号paars 现在在其函数单元和值单元中包含相同的函数。让我们改变它的值单元,以便差异显而易见

 (setf paars #'+)

现在让我们以不同的方式调用它

 (funcall paars 1 2)   ;equivalent to (+ 1 2)
 (funcall 'paars 1 2)  ;equivalent to (funcall (symbol-function paars) 1 2)
 (funcall #'paars 1 2) ;equivalent to (paars 1 2)

第二个和第三个例子之间的区别在于,如果paars 暂时绑定(使用fletlabels)到其他函数,则第三个 funcall 将使用此临时函数,而第二个 funcall 将仍然使用其函数单元。

华夏公益教科书