闭包
在Lua语言中,函数是严格遵循词法定界(lexicalscoping)的第一类值(first-classvalue)。
“第一类值”意味着Lua语言中的函数与其他常见类型的值(例如数值和字符串)具有同等权限:一个程序可以将某个函数保存到变量中(全局变量和局部变量均可)或表中,也可以将某个函数作为参数传递给其他函数,还可以将某个函数作为其他函数的返回值返回。
“词法定界”意味着Lua语言中的函数可以访问包含其自身的外部函数中的变量(也意味着Lua语言完全支持Lambda演算)。
函数是第一类值
赋值语句右边的表达式(function(x)body end)就是函数构造器,与表构造器{}相似。因此,函数定义实际上就是创建类型为"function"的值并把它赋值给一个变量的语句。
在Lua语言中,所有的函数都是匿名的(anonymous)
像其他所有的值一样,函数并没有名字。当讨论函数名时,比如print,实际上指的是保存该函数的变量。虽然我们通常会把函数赋值给全局变量,从而看似给函数起了一个名字,但在很多场景下仍然会保留函数的匿名性
匿名函数在这条语句中显示出了很好的便利性
像函数sort这样以另一个函数为参数的函数,我们称之为高阶函数(higher-order function)。高阶函数是一种强大的编程机制,而利用匿名函数作为参数正是其灵活性的主要来源
非全局函数
函数不仅可以被存储在全局变量中,还可以被存储在表字段和局部变量中。
将函数存储在表字段中,大部分Lua语言的库就采用了这种机制,下列的示例创建了这种函数
在表字段中存储函数是Lua语言中实现面向对象编程的关键要素。
当把一个函数存储到局部变量时,就得到了一个局部函数(local function),即一个被限定在指定作用域中使用的函数。局部函数对于包(package)而言尤其有用:由于Lua语言将每个程序段(chunk)作为一个函数处理,所以在一段程序中声明的函数就是局部函数,这些局部函数只在该程序段中可见。词法定界保证了程序段中的其他函数可以使用这些局部函数。
在定义局部递归函数(recursive local function)时,由于原来的方法不适用,所以有一点是极易出错的
当Lua语言编译函数体中的fact(n-1)调用时,局部的fact尚未定义。因此,这个表达式会尝试调用全局的fact而非局部的fact。我们可以通过先定义局部变量再定义函数的方式来解决这个问题:
所以使用这种定义方式更合适
在间接递归的情况下,必须使用与明确的前向声明(explicit forward declaration)等价的形式:
请注意,不能在最后一个函数定义前加上local。否则,Lua语言会创建一个全新的局部变量f,从而使得先前声明的f(函数g中使用的那个)变为未定义状态。
词法定界
当编写一个被其他函数B包含的函数A时,被包含的函数A可以访问包含其的函数B的所有局部变量,我们将这种特性称为词法定界
---[[
function newCounter( )
local count = 0
return function ( )
count = count + 1
return count
end
end
c1 = newCounter()
print(c1())
print(c1())
print(c1())
c2 = newCounter()
print(c2())
--]]
好怪啊,重复调用的时候那个count还在里面,c1的count是c1的,c2的是c2的,互不干涉
c1和c2是不同的闭包。它们建立在相同的函数之上,但是各自拥有局部变量count的独立实例。
从技术上讲,Lua语言中只有闭包而没有函数。函数本身只是闭包的一种原型
由于函数可以被保存在普通变量中,因此在Lua语言中可以轻松地重新定义函数,甚至是预定义函数。
当重新定义一个函数的时候,我们需要在新的实现中调用原来的那个函数
上述代码使用了do代码段来限制局部变量oldSin的作用范围;根据可见性规则,局部变量oldSin只在这部分代码段中有效。因此,只有新版本的函数sin才能访问原来的sin函数,其他部分的代码则访问不了。
我们可以使用同样的技巧来创建安全的运行时环境(secure environment),即所谓的沙盒(sandbox)。
我们可以通过使用闭包重定义函数io.open来限制一个程序能够访问的文件: