一个函数可以作为一个表达式的值返回,就像其它对象一样。这里有一个接受一个参数的函数,返回对应参数类型的合并函数:
(defun combiner (x)
(typecase x
(number #'+)
(list #'append)
(t #'list)))在这个函数之上,我们弄了一个通用的合并函数:
(defun combine (&rest args)
(apply (combiner (car args)) args))这个函数接收任意类型的参数并且用对应类型的方式合并它们。
[3]> (combine 2 3)
5
[4]> (combine '(a b) '(c d))
(A B C D)我们知道词法变量只在定义它的上下文中起作用。由于这个限制我们就能保证,只要在这个上下文中,它们就能持续有效。
如果一个函数被定义在一个词法变量的域内,那么它就能继续引用那个变量,即使这个函数作为值返回到了创建这个变量的上下文之外。这里我们创建了一个函数,这个函数将对它的参数做加3操作:
[5]> (setf fn (let ((i 3))
#'(lambda (x) (+ x i))))
#<FUNCTION :LAMBDA (X) (+ X I)>
[6]> (funcall fn 2)
5当一个函数引用在它之外的一个变量时,这个变量就叫做自由变量。而引用这个自由词法变量的函数叫做闭包。只要这个函数存在,这个变量就必须存在。
一个闭包是一个函数和一个环境的组合。当一个函数从周围的词法环境中引用某些东西的时候,就隐式地创建了一个闭包。这种事情是在无声中发生的,就像下面的这个,但是想法是一样的:
(defun add-to-list (num lst)
(mapcar #'(lambda (x) (+ x num)) lst))这个函数接收一个数字和一个列表,并返回一个列表,列表里的每个元素是原列表的每个元素和这个数字的和。lambda表达式中的变量num是自由的,所以像这种情况下,我们就是将一个闭包传递给了mapcar。
一个更加明显的例子是每次调用都返回不同闭包的函数。下面的函数返回一个加法器:
(defun make-adder (n)
#'(lambda (x) (+ x n)))[8]> (setf add3 (make-adder 3))
#<FUNCTION :LAMBDA (X) (+ X N)>
[9]> (funcall add3 2)
5
[10]> (setf add27 (make-adder 27))
#<FUNCTION :LAMBDA (X) (+ X N)>
[11]> (funcall add27 2)
29
(let ((counter 0))
(defun reset ()
(setf counter 0))
(defun stamp ()
(setf counter (+ counter 1))))这样的一对函数可以被用来创建时间戳。每次我们调用stamp,我们就会获得一个比先前大1的数字,通过调用reset我们可以将counter设置回0:
[13]> (list (stamp) (stamp) (reset) (stamp) )
(1 2 0 1)你也可以用一个全局counter来做同样的事情,但是这样的方式会将counter保护起来,防止意外的引用。
Common Lisp有一个内置的函数complement,它会接收一个断言,然后返回断言的反面:
[17]> (mapcar (complement #'oddp ) '(1 2 3 4 5 6))
(NIL T NIL T NIL T)使用闭包,这样的函数是很容易写出的:
(defun our-complement (f)
#'(lambda (&rest args)
(not (apply f args))))[19]> (mapcar (our-complement #'oddp ) '(1 2 3 4 5 6))
(NIL T NIL T NIL T)如果你这个时候停下来,想一下,这确实是个非常好的小例子;尽管这仅仅是冰山的一角。闭包是Lisp独创的牛逼技能之一。他们为做到很多在其它编程语言中不可思议的编程技术开启了大门。
本文介绍了Lisp语言中的闭包概念及其应用。详细解释了如何定义和使用闭包来实现灵活的功能,如合并不同类型的数据、创建带有共享状态的函数及反转谓词等。
973

被折叠的 条评论
为什么被折叠?



