Lisp.闭包

一个函数可以作为一个表达式的值返回,就像其它对象一样。这里有一个接受一个参数的函数,返回对应参数类型的合并函数:

(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


我们甚至可以弄出几个共享变量的闭包。这里我们定义了一个共享counter的函数。
(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独创的牛逼技能之一。他们为做到很多在其它编程语言中不可思议的编程技术开启了大门。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值