引用自 CLICK May 18, 2010 By Alan Skorkin
有技术不错的程序员并不知道闭包的概念。我没有在这个问题上进行统计,只是根据经验凭直觉说的。但是,这种现象也是理所当然的,因为目前最流行的语言比如 java, c++ 并不支持闭包。一个程序员主要使用的语言不支持一个概念,那么这个概念也不会被放大他日程优先级最高的位置,甚至根本就不知道这个概念. 我个人比较赞成优秀的开发者应该尽量去多了解几门语言,也有很多语言支持闭包,你也很有可能陷入某个问题上。这体现了大多数开发者学习一门新的语言时,完全走错了方向。有时候你听到有人说他会一大堆语言时,他很可能是在夸大事实。下面就来讨论一下闭包 (个人觉得这里有点攻击性,不友好,不符合社会主义核心价值观 ^_^)
你很可能上大学的时候就遇到过闭包,但是你不记得了。大概过去 10 年里,软件相关的课程逐渐指出像闭包这样的概念不被强调,它主要是一个函数的概念,函数式编程语言已经过时,Java 成为主流。他们试图让学位更加工业化结果导致了一代程序员成了"跛子", 尽管这对他们来说没什么。你自认为这种教育是正确的,实际上你只有去信任,因为你知道这种信任是错误的时候你也无能为力。那我来说,在我上学期间,第一年学习 java, 第二年学习 C. 第一次接触函数式编程是学习人工智能的课程,我学习了一下 Lisp 语言以及一整套其他的概念。不必说,在这么课程中函数式编程不是被放在一个重要的位置上的。许多人都很困惑。我们当然或多或少地接触了一些闭包的知识,但是谁有时间去仔细考虑呢?好不容易花两年时间熟悉一门语言,大多数人都不会去接收这种风格迥异语言。参加工作以后,闭包和其他一些函数式编程的概念早就忘在脑后了。再次学习这些东西花了我很长的时间,作为一个开发者,这些东西在我上大学受基础教育的适合就应该被掌握。不过我那时没有学这些,现在就要补回来了。
讽刺的是 java 又要恢复函数式编程的风格了。完全函数式编程语言 ( Clojure ) 和 交替性函数式编程语言 ( Scala ) 所以概念有开始变得有意义了。但是过去这些年对这些概念的损害已经造成了影响并且这种影响还会继续。学术界也不急于调整。你不可能再对函数式概念毫不知情了,即使是 javascript, 这种 web 2.0 让我们深恶痛绝的语言--是函数式的杂交产物。开发者开始热衷于这些概念,好像他们的这些观点就可以代表这些概念:
在计算机科学中,闭包是 first-class function ,并且 把自由的变量绑定到语法环境里。
这里 first-class 我在维基百科上找了下面的一句解释: supports passing functions as arguments to other functions, returning them as the values from other functions, and assigning them to variables or storing them in data structures.
这到底是什么意思?维基百科的解释还好些,但是少了一些函数式的上下文,还是很难说得通。你也许听到这样的解释:
闭包是一个函数,据说是关闭的,是自由变量。
真的吗?closure 关闭,为什么。这么说直接跳过了问题。一般下个问题一定是来争论使问题明朗。但是想上面的解释还不如不解释,它只会让别人觉得很时髦,没有实际意义。但是,你想向某人解释什么事物时,自己要明白,而不是自欺欺人。现在让我来解释一下吧。(这部分似乎用处不大,而且很批判,我不是很喜欢)
闭包基本是一个 函数/方法,具有下列属性:
你可能猜到,你不会免费得到闭包,它需要被语言支持。而且语言要支持 first-class functions. first-class function 指函数可以看成对象,你可以把它存入集合,也可以作为参数传递给其他函数。正如我所说,可以被传递是闭包的首要特性。
一个普通的函数被定义在一个指定的环境中 (比如说 class) , 但是它只能在这个 class 中被调用。这个方法可以访问到这个 class 范围内定义的全部变量,比如说这个函数可以接受一些参数,或者函数内部可以使用类变量。而闭包可以在一个 scope(如 class) 被定义,而在完全不同的 scope 中被调用 (因为在调用之前可以将其作为传递), 因此,当闭包创建时,它保留了 scope 内的所有变量的值。闭包 被调用时,就算是变量已经不在那个 scope 了,但是在闭包内部这些变量依然还在。换言之,被定义时,闭包保留了其语法环境内所有的东西 (knowledge 不知道咋翻译).
没有例子就等于完全没有解释,例子才能使这些逐渐被理解。我要用 Ruby 语言,因为它支持闭包. 在 Ruby 中,闭包是通过 proc 和 lambda 支持的。它们非常相似,但是有一些微妙的区别。我们来创建一个闭包来看看实现上面的两个属性的:
class SomeClass
def initialize(value1)
@value1 = value1
end
def value_printer(value2)
lambda { puts "Value1: #{@value1}, Value2: #{value2}" }
end
end
def caller(some_closure)
some_closure.call
end
some_class = SomeClass.new(5)
printer = some_class.value_printer("some value")
caller(printer)
执行后输出了下面的内容:
Value1: 5, Value2: some value
value_printer 创建了一个闭包,通过使用 lambda 结构,然后返回闭包。然后把该闭包赋值给一个变量,传给另外一个函数,这个函数中调用了这个闭包。这就满足了闭包的第一个属性--可传递。
同时也需要注意调用闭包的时候,打印出来了 "5"和"some value" , 在程序剩下的部分调用闭包的时候, @value1 和 value2 都已经在 scope(class) 之外了,在闭包的内部这些变量还在 scope 内的,因为闭包在被定义的时候就保留了 scope 内所有变量的状态。因此,lambda 满足闭包的第二个属性。
当然,我们可以再深入些,当闭包被定义的时候,它如何保留了 scope 内的变量?这个必须要由语言支持,有两只途径实现:
- 闭包创建了它所需要的所有变量的一个备份,因此是这些副本随着闭包传递。
- 闭包 延长了它所需要的所有变量的生命周期。没有复制变量,而是保留了它们的引用,而且变量本身不可以被垃圾回收器回收掉。
如果语言支持第一种方式,那么如果我们创建两个或者更多闭包来访问相同的变量,每个闭包被调用时都有自己单独对变量的拷贝。如果语言支持第二中方式,所有的闭包都引用同一个变量,它们实际上处理的就是同一变量。Ruby 就是这么做的。看下面的例子:
class SomeClass
def initialize(value1)
@value1 = value1
end
def value_incrementer
lambda { @value1 += 1 }
end
def value_printer
lambda { puts "value: #{ @value1 }"}
end
end
some_class = SomeClass.new(2)
incrementer_closure = some_class.value_incrementer
printer_closure = some_class.value_printer
3.times do
incrementer_closure.call
printer_closure.call
end
运行结果:
#=>
value: 3
value: 4
value: 5
这一次我们创建了两个闭包,一个做增加 value 的 一个做打印。我们运行 3 次闭包,发现:每一次迭代 2 个闭包操作的都是同一个变量,因为值在增长。如果 Ruby 要是用复制变量的方法来实现闭包的话,我们将会看到打印的结果都是 2, 打印的闭包操作的变量是它单独的拷贝。
既然我们对于闭包的理解更好了一些,那么闭包可以干什么呢?嗯,这要看你用的是什么语言了。在函数式编程语言中它又很大的作用。函数式编程语言内部是无状态的,但是我们可以通过使用闭包来持久化一些状态,只要我们的闭包还在.(比如:如果闭包改变了一个变量的值,那么直到下次被调用时闭包会一直保持这个值). 我希望闭包的用途是不言而喻的。
像闭包以及其他几种结构的存在,让函数式编程语言在表达逻辑方面更加简明,你可以写更少的代码做更多的事情。
非函数式编程语言就显得没那么简明了。表示状态是命令式编程语言的强项。唯一让闭包引人注目的就是你可以用它们写出更加简明的代码而且还利用了命令式的风格。闭包的存在就是可以使用像 Ruby 这样的语言用更少的代码做更多的事情,而 java 就不能 (不支持闭包).