Skip to content

LISP - 作为返回值的函数

在 Lisp 中,函数是一等公民(first-class citizens)。这意味着函数是一种数据类型,就像数字、字符串或列表一样。这一原则允许强大的编程范式,包括函数可以返回另一个函数作为其结果的能力。

一等函数具备以下能力:

  • 可以存储在变量中:(let ((my-func #'+)) ...)
  • 可以作为参数传递:(mapcar #'1+ '(1 2 3))
  • 可以从另一个函数返回:这是本章的重点。

一个接受另一个函数作为参数,或返回一个函数的函数,被称为高阶函数(higher-order function)。mapcar、reduce 和 remove-if 等函数是常见的内置高阶函数。一个返回函数的函数通常被称为函数工厂(function factory)。

当一个函数返回另一个函数时,返回的函数不仅包含代码;它还捕获其创建时的词法环境(lexical environment)。函数与其捕获的环境的这种组合被称为闭包(closure)。闭包“记住”了其创建作用域中的变量,即使外部函数执行完毕后也是如此。

示例:一个用于加法器的函数工厂

Section titled “示例:一个用于加法器的函数工厂”

让我们创建一个高阶函数 make-adder,它充当一个工厂。它接受一个数字 n,并返回一个新函数(一个闭包),该函数将其自身参数加上 n。

;; A higher-order function that returns a function.
(defun make-adder (n)
"This is a function factory. It creates and returns a new function
that will add N to any number given to it."
(lambda (y)
(+ n y))) ; The returned lambda CLOSES OVER the variable 'n'.
;; --- Using the function factory ---
;; Create a specific "add-5" function.
(let ((add-5 (make-adder 5)))
;; `add-5` is now a function we can call.
;; We use `funcall` to invoke a function stored in a variable.
(print (funcall add-5 10)) ; -> 15
(print (funcall add-5 100)); -> 105
)
;; We can create another adder on the fly.
(let ((add-10 (make-adder 10)))
(print (funcall add-10 10)) ; -> 20
)

当你执行这段代码时,它会返回以下结果:

15
105
20
  • 可定制行为:你可以创建泛型函数并通过返回专门的版本来定制它们,减少代码重复。
  • 有状态函数:闭包可用于在调用之间维护状态,无需使用全局变量或对象。一个经典示例是计数器函数,它会“记住”其上一个值。
  • 延迟执行(回调):在事件驱动编程中(例如图形用户界面(GUI)或异步网络编程),你通常需要定义当事件发生时稍后应该发生什么。闭包非常适合创建这些回调函数,因为它们可以捕获必要的上下文,以便在事件最终触发时使用。
(defun make-counter ()
(let ((count 0))
(lambda ()
(incf count)))) ; incf increments the 'count' variable
(let ((counter-a (make-counter))
(counter-b (make-counter)))
(print (funcall counter-a)) ; -> 1
(print (funcall counter-a)) ; -> 2
(print (funcall counter-b)) ; -> 1 (A separate closure with its own 'count')
(print (funcall counter-a)) ; -> 3
)

每次调用 make-counter 都会创建一个新的闭包,拥有其自己的私有 count 变量,这展示了闭包如何封装行为和状态。