Lua ——闭包问题

当一个函数被写入另一个函数时,它可以完全访问函数中的局部变量; 这个特征被称为词法范围。虽然这听起来很明显,但事实并非如此。在编程语言中,词法范围界定和一流功能是一个强大的概念,但很少有语言支持该概念。

让我们从一个简单的例子开始。假设你有一个学生姓名列表和一个将姓名与成绩联系起来的表格; 你想根据他们的成绩(首先是更高的成绩)对名单进行排序。您可以按如下方式完成此任务:

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

//Now, suppose you want to create a function to do this task:
    function sortbygrade (names, grades)
      table.sort(names, function (n1, n2)
        return grades[n1] > grades[n2]    -- compare the grades
      end)
    end

该示例中有趣的一点是,为了sort 访问参数grades而提供的匿名函数,该参数对于封闭函数是本地的sortbygrade。在这个匿名函数中, grades既不是全局变量也不是局部变量。我们称之为外部局部变量,或者称为upvalue。(“upvalue”这个术语有点误导性,因为它grades是一个变量,而不是一个值,但是这个术语在Lua中有历史根源,并且比“外部局部变量”短)。
为什么这么有趣?因为函数是一流的值。考虑下面的代码:

function newCounter ()
      local i = 0
      return function ()   -- anonymous function
               i = i + 1
               return i
             end
    end

    c1 = newCounter()
    print(c1())  --> 1
    print(c1())  --> 2

现在,匿名函数使用upvalue, i来保留其计数器。但是,当我们调用匿名函数时, i已经超出了范围,因为创建该变量(newCounter)的函数已经返回。尽管如此,Lua使用闭包的概念正确地处理了这种情况。简而言之,闭包是一个函数,加上所有需要正确访问它的upvalues。如果我们newCounter再次调用它,它将创建一个新的局部变量i,所以我们将得到一个新的闭包,对这个新变量进行操作:

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

所以,c1和c2是在同一功能不同封口并且在局部变量的独立实例化每个行为 i。从技术上讲,Lua中的价值是闭包,而不是函数。函数本身只是闭包的原型。不过,只要不存在混淆的可能性,我们将继续使用术语“功能”来指代关闭。
在许多情况下,闭包提供了一个有价值的工具。正如我们所看到的,它们可以用作高级函数的参数,如sort。像我们的newCounter例子一样,闭包对于构建其他功能的函数也很有价值; 这个机制允许Lua程序在功能世界中融入奇特的编程技术。闭包对于回调函数也很有用。在典型的GUI工具包中创建按钮时,会出现典型的示例。每个按钮都有一个回调函数,当用户按下按钮时会被调用; 你需要不同的按钮来按下时做些微不同的事情。例如,数字计算器需要十个类似的按钮,每个数字一个。您可以使用下一个功能创建它们中的每一个:

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

在这个例子中,我们假设这Button是一个创建新按钮的工具包函数; label是按钮标签; 并且action是按下按钮时要调用的回调函数。(它实际上是一个闭包,因为它可以访问upvalue: digit。)回调函数可以在digitButton任务完成后和局部变量digit超出范围之后调用很长时间,但仍可以访问该变量。
在完全不同的情况下,闭包也很有价值。因为函数存储在常规变量中,所以我们可以轻松地重新定义Lua中的函数,甚至可以预定义函数。这个设施是Lua非常灵活的原因之一。然而,通常情况下,当您重新定义一个函数时,您需要在新实现中使用原始函数。例如,假设你想重新定义函数sin以度为单位而不是弧度。这个新函数必须转换它的参数,然后调用原始sin函数来做真正的工作。你的代码可能看起来像

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

更简单的方法如下:

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

现在,我们将旧版本保存在一个私有变量中; 访问它的唯一方法是通过新版本。
您可以使用此相同功能来创建安全环境,也称为沙箱。运行不受信任的代码(如通过服务器通过Internet接收的代码)时,安全环境至关重要。例如,要限制程序可以访问的文件,我们可以使用闭包重新定义open函数(从io库中):

   do
      local oldOpen = io.open
      io.open = function (filename, mode)
        if access_OK(filename, mode) then
          return oldOpen(filename, mode)
        else
          return nil, "access denied"
        end
      end
    end

这个例子的好处在于,在重新定义之后open,除了通过新的限制版本之外,程序无法调用不受限制的方法。它将不安全的版本保存为闭包中的私有变量,从外部不可访问。有了这个工具,您可以在Lua中构建Lua沙箱,并具有通常的优势:灵活性。Lua为您提供了一种元机制,而不是一个通用的解决方案,因此您可以根据特定的安全需求量身定制您的环境。

猜你喜欢

转载自blog.csdn.net/Pg_dog/article/details/79457456