dayu's profileDAYUZHIZAOPhotosBlogLists Tools Help
    July 25

    研究闭包实例

    继续讨论闭包~
    要想更好地理解闭包,研究一些实例是比不可少的,那么用什么语言来举例呢?
    我最初认识闭包是在学习SICP这本计算机经典教材的时候,SICP的教学语言是Scheme,我现在也认为用Scheme来构造一个闭包的例子的确很简洁优雅。但是根据国内的计算机教育的情况,有多少人会在意Scheme呢?
    我也曾经认为python是一个不错的选择,可是当我在构造一个实际的例子的时候突然发现闭包不是python强调的一个概念!python可以支持闭包,但是由于python在处理简单类型(如整形)对象时候的一些特点,在python中构造一个很简单的闭包反而更加复杂,不优雅。
    最近由于工作的原因,做了一些javascript方面的研究,在对javascript的传统印象(只能用来产生弹出式广告)发生改变之后,忽然发现javascript是我可以用来演示闭包的一种通俗易懂的语言。
    下面请看例子
    function bank_account(init_balance){
        var balance = init_balance;
        function deposit(amount){
            balance += amount;
            return balance;
        }
        function withdraw(amount){
            if (balance < amount)
                return "there isn't enought money, there is only " + balance + " in your account";
            else
            {
                balance -= amount;
                return balance;
            }
        }
        return [deposit, withdraw];
    }
    //create a bank account with the initial balance of 100
    acc1 = bank_account(100);
    //get deposit function
    deposit = acc1[0];
    //get withdraw function
    withdraw = acc1[1];
    //start to do some bank account operation
    deposit(100);    //returns 200
    deposit(100);    //returns 300
    withdraw(100);    //returns 200
    withdraw(100);    //returns 100
    withdraw(100);    //returns 0
    withdraw(100);    //returns "there isn't enought money, there is only 0 in your account"
    这是一个模拟银行账户的例子,bank_account会创建一个帐户,在bank_account内部定义了存钱(deposit)和取钱( withdraw)函数,然后把这两个函数返回,在外部用同名的deposit和withdraw来对应。
    对deposit和withdraw的调用过程展示了这样一个现象,虽然每次调用deposit和withdraw的参数都是相同的,但每次的结果都不同。这是因为bank_account返回了内部函数deposit和withdraw,而deposit和withdraw的功能对bank_account内部的变量balance的引用构成了闭包,构成了一个隐含的保存特定状态(balance)的对象。对deposit和withdraw的每次调用都会改变这个闭包的状态,因此尽管参数相同,但是返回值依然不同。
    这个例子的原型来自SICP的3.1节Assignment and Local State,前一段时间我曾想过用python去重写SICP中的一些例子,结果现在却用更简单的javascript实现了,看来简单的东西功能却不一定简单,这也是当时学习SICP时通过Scheme懂得的一个道理~
    July 23

    闭包夜话

    对于闭包,很早以前就知道了,但一直以来都是一知半解。
    最近静下心来,好好把这个概念琢磨了一番,还真品尝出了一些趣味。

    wiki中对于闭包(closure)的定义是我在调研过程中找到的最详细最系统的,这里比较学术的说法是,一个闭包是一个函数,这个函数和一个环境(environment )绑定在一起,这个环境包含若干变量,当该函数执行的时候,可以访问这些变量。原文是,In computer science, a closure is a function that is evaluated in an environment containing one or more bound variables. When called, the function can access these variables.
    学术的东西一般比较抽象,难以令人理解,这段话也是如此,在你理解了闭包的含义之后看起来头头是道,但在理解之前,会让自己更加迷惑。我把它用更通俗的话又翻译了一下,一个闭包是一个函数,这个函数和一块内存绑定在一起,函数执行的时候可以访问这块内存中保存的变量。
    这个定义更容易理解,但是除了闭包是函数很明确以外,还有很多不清楚的地方,因为就这个定义本身很难把闭包和普通的函数区分开来,因为无论什么语言的函数,执行的时候至少可以访问自己的局部变量,这和环境或者被绑定的内存中的变量有什么区别。

    wiki对这个问题作了进一步的补充,简单的说就是,和闭包关联的这些变量,在闭包(函数)每一次执行之后的状态都会被保存下来,影响闭包的下一次执行。原文是来自A closure can be used to associate a function with a set of "private" variables, which persist over several invocations of the function.
    这次描述的很清楚了,原来闭包这种函数是有状态的,闭包的状态就是和闭包关联的变量,闭包的每次执行都会改变这些变量,闭包的下次执行基于上一次执行改变了的变量。习惯C/C++,java编程的人对这个现象会感到一些迷惑,在C/C++,java中,只有对象才有状态,函数不是对象,函数是没有状态的。这个问题算是说到点子上了,的确只有对象才有状态,而闭包这种函数就是对象,所以有状态。因此支持闭包的语言必须可以把函数作为对象处理,否则是无法在语言层面上提供对闭包的支持的(C++可以通过运算符的重载来模拟函数对象,但是这不是语言层面上的支持,而且也无法100%的模拟)
    这个结论引出了一点新的话题,到底什么是对象,是不是只有类的实例才是对象?当然不是,在我看来,任何包含了一块内存的编程模型都可以视为对象。由于闭包和一块内存绑定,自然闭包也是对象。

    认识到闭包是函数,同时闭包也是对象之后,闭包的概念也便不再神秘了,可能唯一还有点麻烦的是和闭包关联的那些变量在语言层面上看上去并没有被包含在一个特定的结构中(像class一样的结构),这使得在阅读代码的时候还需要花点脑筋来想想它们是包含在一个特定的结构中密不可分的一个整体。