zoukankan      html  css  js  c++  java
  • Lua中的closure(闭合函数)

    词法域:若将一个函数写在另一个函数之内,那么这个位于内部的函数便可以访问外部函数中的局部变量,这项特征称之为“词法域”。

    例:假设有一个学生姓名的列表和一个对应于没个姓名的年级列表,需要根据每个学生的年级来对他们的姓名进行排序(由高到低)。可以这么做:

    names = {"Peter", "Paul", "Mary"}
    grades = {Mary = 10, Paul = 7, Peter = 8}
    table.sort(names, function (n1, n2)
            return grades[n1] > grades[n2]      -- 比较年级
        end)

    现在假设单独创建一个函数来做这项工作:

    function sortbygrade (names, grades)
        table.sort(names, function (n1, n2)
            return grades[n1] > grades[n2]        -- 比较年级
         end)
    end

    上例中有一点很有趣,传递给sort的匿名函数可以访问参数grades,而grades是外部函数sortbygrade的局部变量。在这个匿名函数内部,grades既不是全局变量也不是局部变量,将其称为一个“非局部的变量(non-local variable)”。

    为什么在Lua中允许这种访问呢?运因在与函数是“第一类值”。考虑一下代码:

    function newCounter()
        local i = 0
        return function ()      -- 匿名函数
            i = i + 1
            return i
        end
    end
    
    c1 = newCounter()
    print(c1())     --> 1
    print(c1())     --> 2

    在这段代码中,匿名函数访问了一个“非局部的变量”i,改变两用于保持一个计数器。出刊上去,由于创建变量i的函数(newCounter)已经返回,所以之后每次调用匿名函数时,i都应该是已超出作用范围的。但其实不然,Lua会以closure的概念来正确地处理这种情况。简单地说,一个closure就是一个函数加上该函数所需访问的所有“非局部的变量”。如果再次调用newCounter,那么它会创建一个新的局部变量i,从而也将得到一个新的closure:

    c2 = newCOunter()
    print(c2())    --> 1
    print(c1())    --> 3
    print(c2())    --> 2

    因此c1和c2是同一个函数所创建的两个不同的closure,它们各自拥有局部变量i的独立实例。

    从技术上讲,Lua中只有closure,而不存在“函数”。因为,函数本身就是一种特殊的closure。不过只要不会引起混淆,仍将采用属于“函数”来指代closure。

    在很多场合中closure都是一种很有价值的工具。就像只前所看到的,它们可作为sort这类高阶函数的参数。closure对于那些创建其他函数的函数也很有价值,例如前例中的newCounter。这种机制使Lua程序可以混合那些在函数式百年成世界中久经考验的编程技术。另外,closure对于回调函数也很有用。这里有一个典型的例子,假设有一个传统的GUI工具包可以创建按钮,每个按钮都有一个回调函数,每当用户按下按钮时GUI工具包都会调用这些回调函数。再假设,基于此要做一个十进制计算器,其中需要10个数字按钮。会发现这些按钮之间的区别其实并不大,仅需在按下不同按钮时做一些稍微不同的操作就可以了。那么可以使用以下函数来创建这些按钮:

    function digitButton (digit)
        return Button{ label = tostring(digit),
                       action = function ()
                                    add_to_display(digit)
                                end
                        }
    end

    closure在另一种情况中也非常有用。例如在Lua中函数是存储在普通变量中的,因此可以轻易地重新定义某些函数,甚至是重新定义那些预定以的函数。这正是Lua相当灵活的原因之一。通常当重新定义一个函数的时候,需要在新的视线中调用原来的那个函数。举例来说,假设要重新定义函数sin,使其参数能使用角度来替换原先的弧度。那么这个心寒数就必须得转换他的实参,并调用原来的sin函数完成真正的计算。这段代码可能是这样的:

    oldSin = math.sin
    math.sin = function (x)
        return oldSin(x*math.pi/180)
    end

    还有一种更彻底的做法是这样的:

    do
        local oldSin = math.sin
        local k = math.pi/180
        math.sin = function (x)
            return oldSin(x*k)
        end
    end

    将老版本的sin保存到了一个私有变量中,现在只有通过新版本的sin才能访问它了。
    可以使用同样的技术来创建一个安全地运行环境,即所谓的“沙盒(sandbox)”。当执行一些未受信任的代码时就需要一个安全地运行环境,例如在服务器中执行那些从Internet上接收到的代码。举例来说,如果要限制一个程序访问文件的话,只需使用closure来重定义函数io.open就可以了。

    do
        local oldOpen = io.open
        local access_OK = function (filename, mode)
            <检查访问权限>
        end
        io.open = function (filename, mode)
            if access_OK(filename, mode) then
                return oldOpen(filename, mode)
            else
                return nil, "access denied"
            end
        end
    end

    这个示例的精彩之处在于,经过重新定义后,一个程序就只能呢该通过新的受限版本来调用原来哪个未受限的open函数了。示例将原来不安全的版本保存到closure的一个私有变量中,从而使得外部再也无法直接访问到原来的版本了。通过这种技术,可以在Lua的语言层面上就构建除一个安全地运行环境,且不是简易性了灵活性。相对于提供一套大而全的解决方案,Lua提供的则是一套“元机制(meta-mechanism)”,因此可以根据特定的安全需要来创建一个安全的运行环境。

  • 相关阅读:
    hdu 2647 Reward
    hdu 2094 产生冠军
    hdu 3342 Legal or Not
    hdu 1285 确定比赛名次
    hdu 3006 The Number of set
    hdu 1429 胜利大逃亡(续)
    UVA 146 ID Codes
    UVA 131 The Psychic Poker Player
    洛谷 P2491消防 解题报告
    洛谷 P2587 [ZJOI2008]泡泡堂 解题报告
  • 原文地址:https://www.cnblogs.com/moonlightpoet/p/5684850.html
Copyright © 2011-2022 走看看