闭包,编程思考

闭包,编程思考

缘由:经常会文章里看到闭包,对闭包有种感念,但有时候会绕进去。正好加上,android端的构建使用了gradle,gradle脚本基本上都是由闭包构成和撰写的,便研究了一番。更加巧合的是,参与iOS问题讨论的时候,发现了Block这种编写方式,越发觉得这些都是速途同归的玩意。

摘要:

闭包定义

Wiki上闭包的解读(很精彩))

闭包的定义:

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一直和closure1closure2存在,赶紧回去看一下刚才最初的定义。

下面是我最喜欢的一段对于闭包的解读:

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函数结束后还一直坚挺的活着。(这是我们刚才已经知道了的。)

不仅活着,还可以被更改,然后再使用!

那么问题到底是什么呢?

  1. 难以管理。
  2. 持有。

所以…

JAVA 以及Object-C对闭包的‘实现’

直接说,

所以基于这点来讲,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);
    };
}

有意思吧。

这样既能完成传值,又成功的避开了作用域的问题。

当然,本质上来讲,这已经不是闭包了。javaonChang中的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)

思考

说两句感悟,大家随便感受一下。

—-—-

comments powered by Disqus