Skip to content

LISP - 作为参数的函数

在 Lisp 中,函数是 一等公民。这是函数式编程的基石,意味着函数被视为与数字或字符串等其他任何数据类型一样。

  • 可赋值给变量:函数可以存储在变量中。
  • 可作为参数传递:函数可以作为参数传递给另一个函数。
  • 可作为函数的返回值:函数可以作为另一个函数的返回值。

一个接受另一个函数作为参数或返回一个函数的函数称为高阶函数 (HOF)。此功能允许创建高度抽象和可重用的代码。Common Lisp 提供了许多内置的高阶函数,例如 mapcar、reduce、find-if 和 sort。

lambda 表达式 创建一个匿名函数——一个没有名称的函数。这对于定义一个简单的、一次性使用的函数并将其作为参数传递给高阶函数非常有用,而无需使用 defun 正式定义它。

要传递一个函数,我们必须引用函数对象本身,而不是它的名称。特殊操作符 #'( (function ...) 的简写)是实现此目的的标准方式。

;; 一个接受函数和数字的高阶函数。
(defun operate-on-five (op)
(funcall op 5))
(defun square (x) (* x x))
(defun cube (x) (* x x x))
;; 将 `square` 函数对象传递给 `operate-on-five`
(format t "Square of 5 is ~d~%" (operate-on-five #'square))
;; 传递 `cube` 函数对象
(format t "Cube of 5 is ~d~%" (operate-on-five #'cube))
Square of 5 is 25
Cube of 5 is 125

这里,operate-on-five 是一个高阶函数。funcall 用于以参数 5 调用函数对象 op。

mapcar 是一个经典的高阶函数,它将一个函数应用于列表的每个元素,返回一个包含结果的新列表。

;; `mapcar` 接受一个函数和一个或多个列表。
(let ((numbers '(1 2 3 4)))
;; 使用 lambda 表达式动态定义一个函数
;; 该函数将其参数加倍。
(let ((doubled (mapcar (lambda (x) (* x 2)) numbers)))
(format t "Doubled list: ~a~%" doubled)))
Doubled list: (2 4 6 8)

有两个重要的高阶函数用于动态调用函数:

  • funcall:用独立提供的参数调用函数。(funcall #'+ 1 2 3)
  • apply:用一个列表作为参数来调用函数。(apply #'+ '(1 2 3))
(let ((args '(10 5)))
;; 使用 funcall,我们必须从列表中提取参数。
(format t "funcall: ~d~%" (funcall #'- (first args) (second args)))
;; 使用 apply,我们可以直接传递参数列表。
(format t "apply: ~d~%" (apply #'- args)))
funcall: 5
apply: 5
  • 抽象和可重用性:高阶函数允许您编写用于映射、过滤和归约数据的通用函数,然后可以使用特定函数进行定制。例如,通过传递不同的谓词,一个单一的 filter 函数可以用于查找偶数、长字符串或活跃用户。
  • 清晰且富有表现力的代码:使用 mapcar 和 reduce 等高阶函数通常会生成比手动编写循环更简洁、更具声明性的代码。
  • 回调和事件处理:这是一种常见的模式,其中一个函数被注册(作为参数传递)以便在特定事件发生时(例如按钮点击或网络响应)被稍后调用。