lua的函数为数据类型之一, 也叫first-class.
所以可以把函数赋予给变量, 这个我在前一篇BLOG中写过, 例如
function f(x) return x*2 end
和f = function(x) return x*2 end
是一样的写法.
甚至可以把函数作为返回值, 例如 :
function f(x)
return function (x) return x*2 end
end
这里其实返回了一个匿名函数,
a = f(1)
> a = f(1)
> = a
function: 0x12a0ab0
> =a(1)
2
> return a(2)
4
那么什么是closure呢?
我们来看一个函数 :
function newc()
local i = 0
return function ()
i = i+1 -- 非本地, 非全局, 函数中的函数可以访问它上层的本地变量, 并可以逃逸这个变量, 成为一个非本地, 非全局变量.
return i
end
end
这个函数返回一个函数, 在返回的匿名函数中, i其实是newc函数的本地变量, 对匿名函数来说, i变量非本地, 非全局.
那么把newc()的结果匿名函数赋予给一个变量后, i就变成了这个函数的一部分,
closure包含"函数的定义"以及"函数需要访问的非本地, 非全局变量, 这个例子是i".
我们可以理解为closure实际上包含了两部分信息, 一部分是定义区域, 一部分是函数自有的变量区域(非本地, 非全局.)
利用这种方式创建的函数, 自由变量可以用作计数器, 例如 :
> c1 = newc()
> = c1
function: 0x1291a70
> return c1() -- i这个自由变量在当前这个c1函数/closure内自增.
1
> return c1()
2
> return c1()
3
> c2 = newc()
> = c2 -- i这个自由变量在当前这个c2函数/closure内自增. 和c1/closure没有关系.
function: 0x12a2290
> return c2()
1
> return c2()
2
> return c1()
4
由于函数名只是一个closure的名称, 所以可以很方便的复写. 例如
> do
local oldsin = math.sin -- 把math.sin这个函数名指向的closure赋予给本地变量oldsin. 对外就隐藏了
local k = math.pi/180
math.sin = function(x) -- 复写math.sin
return oldsin(x*k)
end
end
那么以后调用math.sin就是新的closure了, 老的closure因为被指向为一个本地变量, 已经无法被使用了.
> = oldsin(1)
stdin:1: attempt to call global 'oldsin' (a nil value)
stack traceback:
stdin:1: in main chunk
[C]: in ?
> = math.sin(1)
0.00030461741507571
这种用法类似黑盒, 把一个closure隐藏, 对外使用新的一个closure.
还有一个例子是对文件操作加一个权限判断, 隐藏没有权限判断的io.open函数.
> do
local oldopen = io.open -- 把老的io.open存储到一个本地变量, 对外隐藏.
local access_ok = function (filename, mode) -- 权限判断函数
-- check access, return boolean
end
io.open = function (filename, mode) -- 复写io.open
if access_ok(filename, mode) then
return oldopen(filename,mode) -- 在黑盒中访问外部的本地变量oldopen, 这个新的io.open函数/closure包含了oldopen这个变量.
else
return nil, "access denied"
end
end
end
-- 那么以后调用io.open时其实调用的是复写的io.open.
小结.
1. Lua函数或closure的一个重大功能是, 对上层函数的本地变量的可访问, 以及逃逸(逃逸这个说得明白点就是closure中包含变量信息, 同时包含定义信息).
2. 支持复写, 同时结合本地变量的逃逸, 很好的达到黑盒目的.