本文所讲闭包是计算机中的闭包

如果一上来就一大段理论,估计连我自己也看不懂
以具体例子入手来理解,因为在函数式语言中闭包是一个非常常见的概念,
所以我们以一种函数式语言 Scheme 为例来讲解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
; 以斜截式的方式定义一条直线 y = ax + b
(define straight-line
  (lambda (a b)
    (lambda (x)
      (+ (* a x) b))))

; 定义一条斜率为 1, 截距为 1 的直线
(define straight-line-1-1 (straight-line 1 1))

; 定义一条斜率为 2, 截距为 2 的直线
(define straight-line-2-2 (straight-line 2 2))

; 求当 x 等于 1 时,这条直线的 y 值
(straight-line-1-1 1)   ; => 2
(straight-line-1-1 0)   ; => 1

(straight-line-2-2 1)   ; => 4
(straight-line-2-2 0)   ; => 2

最近正好看了会 Go 语言,下面是 Go 代码,功能和写法都和上面的 Scheme 代码一样。
也是我对 Go 语言中闭包的一种练习。
如果你习惯于看 C 类的代码,那么 Scheme 代码可能不好理解,相反下面的代码就好理解一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import "fmt"

// 以斜截式的方式定义一条直线 y = ax + b
func straight_line(a, b float64) func(x float64) float64 {
    return func(x float64) float64 {
        return a * x + b
    }
}

func main() {
    // 定义一条斜率为 1, 截距为 1 的直线
    straight_line_1_1 := straight_line(1, 1)

    // 定义一条斜率为 2, 截距为 2 的直线
    straight_line_2_2 := straight_line(2, 2)

    // 求当 x 等于 1 时,这条直线的 y 值
    fmt.Println(straight_line_1_1(1))
    fmt.Println(straight_line_1_1(0))

    fmt.Println(straight_line_2_2(1))
    fmt.Println(straight_line_2_2(0))
}

// 输出:
// 2
// 1
// 4
// 2

以上面中的 straight-line-1-1 为例,它是什么呢?或者换种问法,我们希望它是什么呢?

至少我希望它是这个东西

1
2
3
4
(lambda (x)
  (+ (* a x) b))))

且 a 为 1, b 为 1

这样我们可以很方便地表示直线,而且将 a 和 b 自由变量绑定到某个值,不至于混乱

东西的样子出来了,我们得给这个东西取个名字吧,就叫「闭包」吧
将其表示地形式一点,如下;

1
2
3
4
(Closure
 '(lambda (x)
    (+ (* a x) b))
 '((a . 1) (b . 1)))

我们也可以这样说,闭包就是函数和创建这个函数时的环境总和(你也可以理解为相关数据)
而这个环境指的是自由变量,如果一个函数并没有引用自由变量,那么它只是一个函数而已
在这个例子中,函数就是 (lambda (x) (+ (* a x) b)), 环境就是 ((a . 1) (b . 1))

闭包就是这么个东西,是不是感觉很简单?
都是我们能够很容易理解的概念,没有网上一大段一大段那么复杂,有些东西细细品味确实就简单了

在多说一点,闭包和函数的区别就是:

  • 闭包包含了函数和环境
  • 函数只是一个过程,并不包括创建这个函数的环境,或者说它也有环境,但是这个环境为空

其实理解了闭包是什么,你也就知道闭包有哪些特性了,在以后的使用过程中不至于出错

你可能又会想到对象这个概念,是的,用闭包可以实现对象的封装,继承等,具体可见:http://okmij.org/ftp/Scheme/oop-in-fp.txt


参考:http://www.yinwang.org/blog-cn/2013/03/26/lisp-dead-alive/