闭包的概念 [翻译]

晨曦之光 发布于 2012/05/23 11:01
阅读 300
收藏 0

解释函数式编程中的闭包概念,非完全翻译自 wiki: closure

文章中使用英文名词,以防止翻译不当和歧义

原文: wiki: closure
译文: 闭包的概念
译者: Breaker <breaker.zy_AT_gmail>

译文


closure 概念

closure 亦称词法闭包 (lexical closure)、函数闭包 (function closure) 或函数值 (function value),是由 一个函数 和 这个函数的 non-local 变量的引用环境 (referencing environment) 组成的实体。这里的 non-local 变量和 C++ 中的 static 和全局变量是不同概念,见 wiki:non-local variable

closure 用途

第一级函数 (first-class function): 函数如同整数和字符串等基本类型一样,作为参数传递、返回值 和 绑定变量名

状态表达 (state representation): closure 在一个函数和一组私有 upvalue 变量之间建立关联关系,多次调用中保持 upvalue 值,并且只能从 closure 函数内访问 upvalue,这些可用在状态式语言中的状态表达范型和信息隐藏

控制结构: 闭包只在被调用时才执行函数,即延迟求值 (delay evaluation),可用来定义控制结构,如 Smalltalk 中的 if 条件分支和 while 循环控制结构都通过 closure 实现

多个 closure 函数用共享环境进行消息传递

实现对象系统: 见 Re: FP, OO and relations. Does anyone trump the others?

upvalue 和引用环境

引用环境绑定在 closure 上的自由变量 (free variable) 或 non-local 名字称为 upvalue,upvalue 的生存期可持续到 closure 的生存期结束

当执行进入 closure 函数时,函数可以访问 closure 绑定的 upvalue,只要 closure 不释放,则调用之间保持 upvalue 的值

在一些语言中,在函数中定义另一个函数时,如果内部函数引用了外部函数的局部变量,则可能产生 closure。运行时,一旦外部函数执行,就会形成 closure,其包含了内部函数的代码 和 其到外部函数所需变量的引用 (upvalue)

closure 和匿名函数

在大多数支持 closure 的语言中,经常用匿名函数来构造 closure,但它们是不同的两个概念

可以认为匿名函数是函数字面量 (function literal),偏向右值和函数体的概念;而 closure 是函数变量 (function value),偏向左值和对象的概念

closure 会保持引用环境,如 upvalue 的当前值,而匿名函数不必如此

closure 的历史

closure 术语最早由 Peter J. Landin 在其 SECD machine 中定义,用以表达式求值中的 environment part 和 control part (1964)

后来 Joel Moses 用 closure 指代 lambda 表达式 其绑定开放的 free variable 被词法环境 (lexical environment) 关闭绑定 (bound in) 时,形成的关闭表达式 (closed expression) 即 closure

后来 Sussman 和 Steele 采用了 closure 概念,并首次在 Scheme 中实现 (1975),以支持词法作用域 (lexically-scoped) 的 first-class function

上面的 lexical environment, lexically-scoped 可以非正式的理解为语言中的作用域

closure 最显著的使用是 ML 和 Lisp 等语言的函数式编程 (functional programming)

传统的命令式语言 (imperative language, e.g. Algol, C, Pascal) 不支持 closure,因为它们不支持 non-local 名字,只有在嵌套函数和匿名函数中才引入此概念,同时它们也不支持higher-order 函数

现代的带垃圾收集 (GC, garbage collection) 的命令式语言 (Smalltalk, C#) 和 解释性的脚本语言,很多都支持 higher-order 函数和 closure

closure 实现方法

closure 典型的实现方法,采用包含下面两者的一个数据结构:

指向函数代码的指针
函数 lexical environment 的表示结构,如 closure 创建时,函数的可用变量集合及其值

如果语言采用在堆栈上分配局部变量的运行时内存模型,那么很难实现完全的 closure,因为函数返回时会释放局部变量

closure 需要 upvalue 在 enclosing function 即外层函数执行结束后继续保持,因此 upvalue 应该一直保持的内存分配,直到最终不需要时才释放,所以很多支持 closure 的语言都使用 GC

另外,语言也可以选择接受特定使用导致的不确定行为,如 C++ 标准的 lambda 表达式实现提议,见 Lambda Expressions and Closures

Funarg problem (functional argument problem) 说明了在基于堆栈的语言,如 C/C++ 中实现 first class function 的困难

第一版 D 语言,假定程序员知道如何对待从定义作用域中返回后引用无效的 delegate 和局部变量(局部变量在堆栈上分配),此时仍能使用很多 functional pattern,但对于复杂情况需要显式地对变量进行堆分配

第二版 D 解决了这个问题,它检测哪些变量必须存储在堆上,并且执行自动分配。因为 D 的两个版本都使用 GC,所以不需要跟踪传递时变量的使用

在一些使用 immutable data 的严格函数式语言,如 Erlang 中,很容易实现自动内存管理 (GC),因为它们没有变量引用。如在 Erlang 中,所有的变量都在堆上分配,但是它们的引用存储在堆栈上,当函数返回后,引用依然有效,堆清理由增量 GC 完成

ML 中局部变量在堆栈上分配,当创建 closure 时,会将 closure 所需的变量值拷贝进 closure 的数据结构中

Scheme 是类似 ALGOL 的词法作用域系统,带有动态变量和 GC,缺少堆栈编程模型,所以也没有基于堆栈语言的限制,可以直接表达 closure。包含执行代码和环境 free variable 的 lambda 表达式,可在程序中一直保持访问,也可被其它 Scheme 表达式使用

closure 和并发计算 Actor model 中的 Actor 有紧密关系,那里函数 lexical environment 中的值称为 acquaintance。关于 closure 在并发编程 语言中的一个重要问题是:closure 中的变量是否可更新,如何同步这些更新,Actor 给出一种方案,见 Foundations of Actor Semantics

closure 示例

上面的 closure wiki 解释比较抽象,下面是一个具体的 Javascript closure 示例:

function counter() {
    var x = 0;
    var increment = function(y) {
        x += y;
        console.log("x = " + x);    // Firebug console
    }
    return increment;   // increment 是一个 closure, 包括:
                        //   1. 一个 +y 的匿名函数
                        //   2. non-local 变量 x 的引用环境 (upvalue)
}

function test_closure_1() {
    counter1_increment = counter(); // 绑定一个 closure 引用环境
    counter2_increment = counter(); // 绑定另一个 closure 引用环境

    counter1_increment(1);  // x = 1, 进入 closure
    counter1_increment(7);  // x = 8, closure 保持引用环境, upvalue 变量 x 的生存期延长并保持上次的值
    counter2_increment(1);  // x = 1, 这是不同的 closure 引用环境, 不同的 upvalue 变量 x
    counter1_increment(1);  // x = 9
}

说明:

  1. closure 会绑定 closure 函数和 upvalue,并保持 upvalue 的值
  2. 不同的 closure environment 是不同的绑定,它们的 upvalue 相互独立

[END]


原文链接:http://blog.csdn.net/breakerzy/article/details/7364450
加载中
返回顶部
顶部