首先, 须要强调一点, 这里谈论的 闭包(closure) 概念是指数学上的, 不是咱们编程界通常谈论的那个闭包.java
在编程实践中, 闭包另有定义, 是一种为表示带有自由变量的过程而用的实现技术.程序员
但另外一方面, 这个数学上的闭包概念在编程实践中依然是有体现, 虽然不一样于编程界通常谈论的那个闭包, 后面会举一些例子加以说明.编程
闭包在数学上是一个比较抽象的概念, 来自于抽象代数, 所以这里不打算直接给出它的定义, 不然你们看了估计仍是一头雾水, 为便于理解, 仍是先从具体的例子出发, 最后才给出它的定义.数组
咱们先考察一个很简单的例子, 就是加法在天然数集合中的操做及其结果.数据结构
首先, 天然数集这个很好理解, 就是0, 1, 2, 3..., 这些整数的集合, 固然须要注意的一点是它是一个无穷的集合.闭包
而后是加法这个操做, 咱们也很熟悉, 它须要两个操做数, 从刚才的天然数集合中任意取出两个数, 而后执行加法操做:编程语言
好比, 1 + 2, 3 + 5, 6 + 4 等等ide
而后这些加法操做会有一个结果, 好比 1 + 2 = 3, 3 + 5 = 8, 6 + 4 = 10 等等, 当咱们观察这些结果, 好比 3, 8, 10 等等时, 不难发现它们也仍是属于天然数集合.设计
说到这里, 你可能会想, 这不是显而易见的嘛, 感受像是说了一堆废话! 先别急着下定论.对象
至此, 咱们就能够初步给出闭包的定义, 首先有一个集合(天然数集合), 而后有一个操做(加法操做), 这个操做须要集合中的两个元素, 最后操做的结果仍然属于这个集合.
由于结果仍然属于这个集合, 咱们就说, 天然数集合对于加法操做来讲是封闭的(closed).
这个是针对结果来讲的, 也就是不管你怎么操做来操做去, 结果都还在集合内, 有点孙悟空怎么翻筋斗也逃不出如来佛手掌心那种感受.
天然数集合在加法这样一种操做下是封闭的, 咱们就说它知足(satisfy)一种闭包性质(clousre property).
更通常化的说, 一个集合被认为是知足一种闭包性质, 若是它在一个(或一系列)操做下是封闭的.
A set that is closed under an operation or collection of operations is said to satisfy a closure property.
上面的定义涉及到一个或一系列操做, 下面就说说另外一个操做, 好比乘法, 一样仍是天然数集合, 随便取两个数, 而后乘起来:
好比, 2 × 3, 3 × 7, 5 × 4 等等.
而后也会有一个结果, 好比 2 × 3 = 6, 3 × 7 = 21, 5 × 4 = 20 等等, 当咱们观察这些结果, 好比 6, 21, 20 等等时, 不难发现它们也仍是属于天然数集合.
所以, 根据前面的定义, 咱们能够说, 天然数集合对于乘法操做来讲也是封闭的(closed).
毕竟来讲, 乘法在某种意识上讲也是一种加法, 所以天然数集合对其封闭也就不难理解了.
天然数集合对于加法操做来讲是封闭的(closed), 同时对于乘法操做来讲也是封闭的(closed), 所以, 能够说它在一系列操做(加法及乘法)下都是封闭的.
也便是一个集合能够不只对一个操做是封闭的, 它还可能对不少操做都是封闭的.
说完了加法和乘法, 估计有些同窗已经不耐烦了, 说, 这有啥稀奇的呢? 难道有什么操做是不封闭的吗? 下面就来讲说再另外一个操做, 减法, 而后咱们会发现, 事情会有一些变化.
若是草率地去看, 仍是针对天然数集合来讲, 随便取两数, 而后执行减法操做:
好比, 3 - 2, 7 - 4, 5 - 1 等等;
而后也会有一个结果, 好比 3 - 2 = 1, 7 - 4 = 3, 5 - 1 = 4 等等, 当咱们观察这些结果, 好比 1, 3, 4 等等时, 不难发现它们也仍是属于天然数集合.
从以上来看, 咱们彷佛也能够说, 天然数集合对于减法操做来讲一样也是封闭的(closed).
但只要咱们多考察一些情形, 好比 2 - 4, 3 - 6 等等, 就会发现, 不对劲了, 2 - 4 = -2, 3 - 6 = -3, 像 -2, -3 这些结果它们并不属于天然数集合!
在古代, 人们的认知能力还很弱时, 面对像 2 - 4 这么骚气的操做时, 干脆就像鸵鸟把头埋进沙子里同样, 对其视而不见了, 人们认为这样的操做是没有意义的!
因而, 天然数集合对于减法操做来讲并非封闭的. 但若是咱们扩大天然数集合, 变成整数集合, 它包含了负整数集合, 0 和 正整数集合.
这时候, 对于一个整数集合而言, 则不管你怎么去取数进行减法操做, 结果老是还在这个整数集合中.
又一次的, 孙悟空怎么翻筋斗也逃不出如来佛手掌心, 这是个更大的手掌心!
因而, 虽然天然数集合在减法的操做下不是封闭的, 但一旦扩大到整数集合, 咱们又能够说, 整数集合对于减法操做是封闭的.
另外一种情形则是, 对一个有限的集合而言, 好比只有一个元素 0 的集合, 表示为 { 0 }, 咱们也照样能够说, 它对于加法, 乘法和减法都是封闭的, 由于 0 + 0 = 0, 0 × 0 = 0, 0 - 0 = 0, 结果不管怎么搞都仍是 0, 所以也是封闭的. 这个例子中没有扩大集合的范围, 甚至是相反, 缩小了集合的范围, 也能得出封闭的性质, 固然这就属于比较特殊的状况了.
前面提到的一个闭包的定义是指集合知足的一种闭包性质, 对于闭包还有另一种定义.
当一个集合 S 在某些操做下不是封闭的,
好比天然数集合对于减法操做不是封闭的.
咱们能够找到一个包含 S 的最小集合使得操做是封闭的,
好比, 能够找到 整数集合 这个包含 天然数集合 的最小集合, 它对于减法操做是封闭的.
那么, 这个最小的集合就叫作 S 集合(针对那些操做而言)的闭包(closure).
整数集合就是天然数集合(针对减法操做而言)的闭包.
具体的英文定义以下:
When a set S is not closed under some operations, one can usually find the smallest set containing S that is closed. This smallest closed set is called the closure of S (with respect to these operations)
这个定义跟闭包性质的定义有差别, 但二者仍是有紧密关系的, 只不过是说法的侧重点不一样, 但都是围绕着操做结果的封闭性去说的.
整数集合对于加法, 乘法和减法都是封闭的, 但若是继续引入其它操做, 好比除法, 那么新的问题又会来了, 虽然像 6 ÷ 2, 10 ÷ 5 等整除的情形, 结果还在集合中, 但更多的诸如 2 ÷ 3, 5 ÷ 8 结果就不在集合里面了.
此时若是想让除法操做封闭, 还得继续扩展集合到好比有理数集合.
此外还得施加一个限制, 除数还不能为 0.
而对于有理数集合, 当继续扩大操做时, 好比引入了开方运算, 不少结果又不是封闭的了, 这时得扩大到无理数集合; 而即使是到了无理数集合, 对于负数的开平方依然是一筹莫展的, 这时就须要进一步的扩大到复数集合了...
显然的, 当引入愈来愈多的操做, 想继续的保持封闭性就很困难了.
至此, 咱们已经说完了数学上的闭包这一律念, 天然也只是走马观花地说一下, 让你们有个概念而已, 更深的我们也不懂, 也不打算去涉及, 毕竟太艰深了.
经过以上的介绍, 相信你们对数学上的闭包这一律念多少也有了一些了解, 但不少人可能会问, 去知道这些干啥呢? 对咱们编程彷佛也没啥用. 而前面一开始也说了, 这里谈的闭包还不是编程界一般谈论的那个闭包, 那这里谈论的这个数学上的闭包对于咱们的编程语言到底有什么影响呢?
怎么说好呢, 要说没有影响恐怕是不可能的, 但若是说有多少大的影响恐怕不少人又不认同了, 问题就在于这些影响每每是很是基础性的, 以致于咱们不以为这算什么影响.
举个很是简单的例子, 咱们在编程中极可能随手就写出了这样的表达式:
2 + 4 + 5
而从不去问到底为何, 甚至说这为何是可能的, 咱们以为这些彷佛是天经地义, 不值一提的.
但从闭包的角度去看, 2 + 4 + 5 之因此成为可能, 是由于 2 + 4 的结果仍然属于集合以内, 才所以能够继续参与下一次的操做.
当咱们例举另外的例子, 好比另外一个表达式:
(3 > 2) + 1
状况就有点微妙了. 对于有些编程语言而言, 这样的操做仍然是可行的, 但实际上是由于包含了一种隐式的转换; 而对于另外的一些编程语言来讲, 这可能就是语法错误了.
从闭包的角度去看, 3 > 2 这个操做的结果并不属于数的集合, 而是一个只有两个有限元素的 boolean 集合: {true, false}.
由于这个操做的结果并非一个数, 因此后续的操做其实就没有意义了.
好比 true + 1, 或者是 false + 1 都是没有意义的了.
有些编程语言之因此可以继续操做下去, 是由于内部作了隐式转换, 好比把 true 转为 1, false 变为 0, 因此继续执行的实际上是 1 + 1 或者 0 + 1.
有些语言限制会比较严格, 好比 java, 你写这样的语句是要报错的:
if (age = 60) { // 能够退休了 }
这里的一个陷阱就是, 单个的等号是一个赋值的操做, 而不是比较, 比较是用两个等号 == 这样:
if (age == 60) { // 能够退休了 }
这样写的 java 程序才能编译经过, 但有的语言两种写法都能编译, 但第一种状况实际会变成:
if (60) { // 能够退休了 }
但 if 其实要求的是一个 boolean 集合的操做数, 而此刻会执行一次转换, 通常而言, 0 会转成 false, 其它则是 true, 因此 60 就转换为 true, 最后语句实质变成了:
if (true) { // 能够退休了 }
这样一来, 整个判断就彻底多余了, 结果老是为真, 里面的语句始终都能执行, 在不少状况下, 这就是一个程序的 bug 了.
可见, 不严格的遵循闭包性质可能会给咱们带来麻烦; 另外一方面, 设计良好的闭包性质有可能带给咱们便利.
好比说, 有的语言容许数组的元素还能够是数组, 这就给不少的操做带来了不少方便, 这实际上是闭包性质的一个体现.
而有的语言则不容许这样, 数组的元素只能是其它元素, 但不能是数组, 这就削弱了语言的表达能力, 进而在编程中给咱们带来不便.
又好比, 对于不少语言来讲, 对象的属性能够继续是对象, 又或者说, map 的元素还能够是另外一个 map, 这样一来, 咱们就能够嵌套地表达不少复杂的数据结构, 而对它们的操做又能够比较简化, 由于操做的结果还在集合内, 就能够反复运用同一种操做去操做它.
举个例子来讲, 一颗树的节点能够是一个叶子节点, 还还能够是一棵子树, 对于文件夹及里面的文件及(或)文件夹而言就是这么一种情形, 所以能够用同一个操做递归地遍历全部的节点.
而上述这些, 往深了说, 其实都是都是闭包性质的体现, 由于这些性质的存在, 才使得这些成为可能.
对于咱们的编程实践来讲, 某种组合数据对象的操做知足闭包性质, 那就是说,经过它组合起数据对象获得的结果自己还能够经过一样的操做再进行组合.
闭包性质是任何一种组合功能的威力的关键要素, 由于它使咱们可以创建起层次性的结构, 这种结构由一些部分构成, 而其中的各个部分又是由它们的部分构成, 而且能够如此继续下去.
你也许以为这些是天经地义, 理所固然的, 缘由也许在于它过于本质, 过于基础了, 以至于你都没有觉察到它, 但它仍是在那里发挥着它的做用.
我以为深刻的理解这些概念是有助于咱们成为一个更好的程序员的, 这也是这篇文章介绍这一律念的缘由, 另外的一个缘由则是, 大量的介绍闭包概念的文章讲的都是编程上的闭包, 而不是数学上的闭包.
固然这点对于咱们程序员而言也是无可厚非的, 毕竟咱们更关注编程相关的, 只是我以为, 既然这个概念有多重的解析, 咱们多了解一些也不无坏处.
固然因为我也不是什么深刻研究数学的, 这也仅是一篇介绍性的文章, 免不了挂一漏万, 若是你有什么意见或建议, 欢迎留言, 关于数学上的闭包概念及其在编程中的影响就介绍到此.