闭包,编程思考
Jan 20, 2016 · 2 minute read · Comments开发思考
闭包,编程思考
缘由:经常会文章里看到闭包,对闭包有种感念,但有时候会绕进去。正好加上,android端的构建使用了gradle,gradle脚本基本上都是由闭包构成和撰写的,便研究了一番。更加巧合的是,参与iOS问题讨论的时候,发现了Block这种编写方式,越发觉得这些都是速途同归的玩意。
摘要:
- 闭包定义
- 解读闭包
- 闭包的意义
- 不同语言对于闭包的另类实现
- 思考
闭包定义
闭包的定义:
closures (also lexical closures or function closures) are a technique for implementing lexically scoped name binding in languages with first-class functions.
可以给个中文版本的:
闭包,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
解读闭包
好吧好吧,无论是英文还是中文,读起来感觉都特别的绕口,理解起来就更绕口了。
还是直接暴力点吧,直接来代码吧:
function startAt(x)
function incrementBy(y)
return x + y
return incrementBy
variable closure1 = startAt(1)
variable closure2 = startAt(5)
这是基于python的闭包的写法。
(在我的接触中,python语言是较为简单的,入门比较低,即便没有入门,看代码也不是困难,所以拿python举例,当然,建议大家都学学这个语言,一般一个星期写简单的程序就没问题了)
(python中,缩进代表 {} 符号)
解读:
startAt
是一个函数,或者叫方法。它的返回值也是一个函数,或者叫,它返回一个函数类型的值。
那么有意思的来了:
closure1(3) = 4
closure2(3) = 8
那么,startAt
到底是什么?
解释它之前,先解释一下变量closure1 和变量 closure2
closure1 = 1 + 参数
closure2 = 5 + 参数
//所以 closure2(3) = 5 + 3 = 8
可以发觉,startAt
是定义了一种加法。但属于它的变量x
却离开了startAt
一直和closure1
和closure2
存在,赶紧回去看一下刚才最初的定义。
下面是我最喜欢的一段对于闭包的解读:
Closures are typically implemented with a special data structure that contains a pointer to the function code, plus a representation of the function’s lexical environment (i.e., the set of available variables) at the time when the closure was created. The referencing environment binds the non-local names to the corresponding variables in the lexical environment at the time the closure is created, additionally extending their lifetime to at least as long as the lifetime of the closure itself. When the closure is *entered* at a later time, possibly with a different lexical environment, the function is executed with its non-local variables referring to the ones captured by the closure, not the current environment.
闭包的意义
因为闭包只有在被调用时才执行操作,即“惰性求值”,所以它可以被用来定义控制结构。例如:在Smalltalk语言中,所有的控制结构,包括分歧条件(if/then/else)和循环(while和for),都是通过闭包实现的。用户也可以使用闭包定义自己的控制结构。
感受一下,一会儿看到各个语言的实现,就能有那种醍醐灌顶的感觉了。
不同语言对于闭包的另类实现
解读完闭包了,聊一下,这个玩意最大的问题是什么?
作用域 –> 自由变量的作用域,导致自由变量的混乱管理。
我将先以javascript
为例,介绍最大的问题。
然后,我再会分别介绍一下java
以及Object-C
为例子,解释一下其他语言是如何另类实现闭包,并怎么处理作用域的问题。
javascript
的闭包
// ECMAScript , javascript
var f, g;
function foo() {
var x; //
f = function() { x = x + 1 ; return x; }; //
g = function() { x = x +5 ; return x; }; //
x = 1;
alert('inside foo, call to f(): ' + f()); // 2
}
foo();
alert('call to g(): ' + g()); // 7
alert('call to f(): ' + f()); // 8
可以看到结果为。
如果我将输出顺序换一下呢?
alert('call to f(): ' + f()); // 3
alert('call to g(): ' + g()); // 8
给你们两分钟时间,再想一下。
可以看到。在foo
中声明的变量x
,由于闭包的存在,导致x
的作用域一直扩展到程序的底部,在foo
函数结束后还一直坚挺的活着。(这是我们刚才已经知道了的。)
不仅活着,还可以被更改,然后再使用!
那么问题到底是什么呢?
- 难以管理。
- 持有。
所以…
JAVA
以及Object-C
对闭包的‘实现’
直接说,
java
中是 匿名内部类object-c
中是 Block运用
所以基于这点来讲,java
更加面向对象一点,真的是很努力做到一切都是对象了。
先看一下java
代码吧:
public void foo(){
final int x = 1 ; //
//f(lambda (x) -> person.setAge(x));
f(new OnChangListner(){
void onChang(){
person.setAge(x);
}
});
// 当然方法f必须事先定义好
}
然后再看一下object-c
的BLOCK:
- (void)foo {
NSInteger x = 1;
void (^f) (NSInteger *) = ^(NSInteger *x)
{
x = 4; // 异常
NSLog(@"foodname:%@", x);
};
}
有意思吧。
这样既能完成传值,又成功的避开了作用域的问题。
当然,本质上来讲,这已经不是闭包了。java
里onChang
中的x
和定义的x
其实已经不是一个内存了,而我们需要的不是一个内存,而是一个相同的值。
这也是我为什么将实现两个字打上了引号。
贴一下对于其他语言对于‘类’闭包的结构或者设计:
Callbacks (C)
Local classes and Lambda functions (Java)
Blocks (C, C++, Objective-C 2.0)
Delegates (C#, D)
Function objects (C++)
Inline agents (Eiffel)
思考
说两句感悟,大家随便感受一下。
- 所谓的设计模式也好,这种和闭包语法类似的东西也好。原因大多不是语言设计者要装x而搞出来的,大多数都是因为,内建不够。一个语言设计模式越多,或者说,为了一种实现折腾的过程越多,其实越代表语言的局限性越大。
- 因为同时兼顾Android和iOS开发,所以可以明显感觉到,
object-c
的限制太多,或者说内建较差。所以才有swift,我也简单学习了swift的基础语法,可以说内建相当的不错。当然,java也不怎么样,新兴的很多语言对于设计要比java优秀很多(比如,方法可以多个返回)。 - 学习其他语言。比我们想象的要简单的多。(抽空单独出文章)
—-—-