什么是闭包?

在讨论行为参数化的时候,闭包/closure是一个经常被涉及到概念。它在支持函数柯里化的编程语言中(见下一节),是一个天然存在的,似乎不需要定义的概念。很多人把自己看成虚拟机的开发人员或计算机科学家去研究闭包,于是闭包概念难以理解。给出一个通用于各种编程语言的关于闭包的定义,比较困难。因为各种编程语言对闭包的支持方式有一些差异,因此要明确地强调和说明闭包的关键要点及其意义,以达到求同存异。这里从程序员或“源代码”的角度定义闭包。

★闭包/closure ,指一个外包函数的内部定义的嵌套函数,而且(外包函数的)外界能够使用该函数。它通常作为返回值。

回顾【实验4:行为参数化】,组合函数的代码如下:

    default Condition and(Condition other){
        //return (n) -> this.test(n) && other.test(n);
        return new Condition(){
            @Override public boolean test(int n){
                return Condition.this.test(n) && other.test(n);
            }
        };
    }
    default Condition or(Condition other){
        return (t) -> this.test(t) || other.test(t);
    }
    default Condition not(){//negate
        return (t) -> !this.test(t);
    }

这个例程中,and(Condition)、or(Condition)和not()方法的“返回值”都是闭包。

1. 嵌套函数是核心。嵌套函数有什么重要作用呢?产生闭包。闭包必须是由直接或间接嵌套方式定义的函数,脱离嵌套函数不讨论闭包。。对于函数式编程语言,函数内部定义新函数,将新函数作为返回值,十分自然。在支持函数柯里化的编程语言中,闭包是天然存在的概念;Java语言不直接支持嵌套函数,而是通过内部类的方式间接地支持嵌套函数。在外包函数and()中,通过匿名类定义了一个函数(因此称为间接的嵌套函数) test(),将参数other与本对象this求与,并返回结果对象。Java程序员要注意,Java社区常常把lambda表达式称为闭包,可以说,是在误用闭包概念。例如,有人说“Java8将闭包引入Java”。事实上,lambda表达式能够做的事情,Java7都能够做到,Java7就支持闭包;所以,Java8并不是将闭包引入Java,只不过“Java8将lambda表达式引入Java”。

2.闭包必须是返回值吗?将函数作为返回值,是一种重要的高阶函数形式。因此谈论闭包时常常提到高阶函数,毕竟大多数编程实践中,以及大多数语言中,闭包是作为返回值的。C语言的函数能够返回一个已有的函数,但是C语言不支持嵌套函数因此不可能让高阶函数返回一个新(嵌套)函数。Java等面向对象语言中,可以将闭包赋值给外包函数外边的一个成员变量,而且一个外包函数中可以定义多个闭包,参见例程2-15。所以,是否作为返回值,至少语法上,不是Java等语言规范闭包的要素。闭包通常以返回值的方式,供外包函数的外部使用。

3.闭包是"带数据的行为"

维基百科上的解释为:『在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体』。在很多场合,人们简单地称闭包是"带数据的行为"。

为什么要强调该行为"带数据"?函数not()的返回值是闭包吗?按照本书的定义,它是闭包;按照"带数据的行为",显然它不带数据,就不是闭包。如果忽略not()的返回值这种(不带数据的)闭包的存在,称闭包是"带数据的行为",并没有什么大问题。真正的问题在于,有人看见"带数据的行为",就浮想联翩,维基百科上的解释,类似说“玫瑰是红的”。“玫瑰是红的”没有问题,但是有些人会错误地认为,X是红的,所以X是玫瑰。登峰造极的说法:“对象是穷人的闭包”——注意:Java类中的方法,通常不是闭包,虽然它们可以使用成员变量。C语言中函数能够捕获全局变量,它没有闭包概念;

并非 “引用了自由变量的函数”就是闭包,并非红色的东西就是玫瑰。

再问一下,为什么要强调该行为/闭包"带数据"?

唯一可接受的依据是需要考虑如下情况:当外包函数执行完毕,通常它使用的局部变量会被函数调用栈弹出,而嵌套函数如何在自己执行时能够使用这些被弹出的外包函数的局部变量呢?

为了让嵌套函数/闭包能够按照人们期望的方式工作,编程语言或运行环境需要考虑采用某种机制将这些局部变量识别出来并加以保存。而“环境的某种机制”,就是强调闭包"带数据"的原因。程序员通常不需要研究这些,只需要知道的是闭包的作用,即在函数内部定义并返回的新函数。

为什么说到闭包,需要谈及词法作用域呢?词法作用域(lexical scoping),也可以称为(static scoping)静态作用域,现在主流语言使用的都是词法作用域,词法作用域的意思是:变量的作用范围取决于它在源代码中定义的位置,在词法解析阶段即可确定。但是,计算机科学家研究时,还存在动态作用域,变量的作用范围取决于函数的调用链。动态作用域使得“环境的某种机制”研制十分困难。闭包——词法闭包(Lexical Closure)的简称,使得程序员们不需要关心动态作用域。因此,程序员可以简化地认为,按照词法作用域,外包函数的参数或局部变量的作用域能够覆盖其内部的嵌套函数,因此嵌套函数使用外包函数的局部变量是最自然地要求。所以,闭包仅仅表现了对词法作用域的尊重。(注:Scheme中,存在某些嵌套函数,作为封装的手段——私有函数)

例如函数and()的返回值是一个闭包,它使用了and()的参数other;当然函数not()的返回值也是闭包,它不需要外包函数的局部变量。但是,需不需要是not()的事。假设不允许嵌套函数使用外包函数的参数或局部变量的话,嵌套函数的用途和使用场景很难想象。

4. 最终值陷阱

另外一个问题是,对闭包所需的、编程环境特别保存下来的局部变量,其值能否改变呢?换言之,闭包“闭关时”所带到数据,在闭包真正运行时,其值是否一致呢?

Java中要求这些局部变量的值不可修改,Java8之前在匿名类中要使用final修饰;Java8后匿名类、lambda表达式中可以省略final,要求为“事实上”不能被重新赋值。C#中,则允许修改局部变量的值,因此闭包捕获的将是局部变量的最终值。严格说,"最终值陷阱"在Java中也存在,如果一个匿名类、lambda表达式使用了成员变量,因为成员变量可变,所以匿名类/lambda表达式(含闭包)捕获的将是成员变量的最终值。

 


 

 

 

 

 

 

 

 

 

 

 

2.1.4 柯里化和闭包

将函数作为返回值,C语言只能够返回已有的函数,因为它不支持嵌套函数。嵌套函数有什么重要作用呢?它涉及到几个术语:函数柯里化、闭包和偏应用

柯里化/currying,指将多元函数转化为多个一元函数的连续调用。丘奇的λ表达式要求单参数。例如有数学函数f(x,y) =2x+3y,丘奇的λ表达式写作λx. λy. ( 2x+3y)。柯里化的理论意义,在于说明人们只需要研究一元函数,其所有结论和规则能够运用到多元函数。实际的函数设计中,多元函数更实用。
在函数柯里化过程中,可以了解闭包和嵌套函数的意义。使用Scheme,多元函数和多个单元函数可以方便地转化。

 

(define F 
   (lambda ( x y) 
     (+ x y) 
   )
);;; 多个单元函数。或者
(define (curryingF x) 
    (lambda ( y)
         (+ x y)
    )
)

 

curryingF的返回值,即闭包。

 

((curryingF 2) 3)         → 5

((curryingF 2) 3)的计算过程,先计算(curryingF 2) 返回一个匿名函数,然后该匿名函数以3为实参进行计算。这个匿名函数为一个重要特点:它带有一个数据2。

 

在Java中,嵌套函数不一定要作为返回值,所以需要将定义扩大,外界能够使用就OK。例如赋值出去,

【对于,人们有一个基本理解——填肚子。正是因为有一个公认的基础,所以人们容易理解更复杂的说法,如吃饭/吃酒、吃苦/吃醋/吃亏、吃货、吃人。我以Java作为通常的工作语言时,yqj2065一般不使用闭包这个术语——我不希望你从Java中知道了闭包概念,在别的语言中又要重新理解这个概念。由于在讲行为参数化时,需要讲到嵌套函数,所以,这里给大家一个比较完整的闭包(计算机科学)的定义,作为“公认”基础。】

2.1.5闭包的用途

组合函数

延迟计算

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值