闭包的本质
在 Lua 中,函数是第一类值(first-class value)。当一个函数引用了其外部作用域的局部变量时,就形成了一个闭包(closure)。闭包可以访问并修改这些外部变量(称为上值/upvalue),即使外部函数已经返回。
function counter()
local count = 0
return function()
count = count + 1
return count
end
end
local c = counter()
print(c()) -- 1
print(c()) -- 2
print(c()) -- 3
这里 count 是闭包的上值。每次调用 counter() 都会创建一个新的闭包实例,拥有独立的 count 变量。
上值的工作原理
上值是闭包捕获的外部局部变量。Lua 通过特殊的机制来管理上值:
function make_adder(x)
-- x 是这个闭包的上值
return function(y)
return x + y
end
end
local add5 = make_adder(5)
local add10 = make_adder(10)
print(add5(3)) -- 8 (5 + 3)
print(add10(3)) -- 13 (10 + 3)
当外部函数返回后,局部变量本应被销毁。但如果闭包引用了它,Lua 会将该变量从栈上迁移到堆上(这个过程称为 “closing”),使闭包可以继续访问它。
闭包共享上值
多个闭包可以共享同一个上值,这是实现私有状态的关键:
function make_account(balance)
local function deposit(amount)
balance = balance + amount
return balance
end
local function withdraw(amount)
if amount > balance then
error("余额不足")
end
balance = balance - amount
return balance
end
local function get_balance()
return balance
end
return {
deposit = deposit,
withdraw = withdraw,
balance = get_balance
}
end
local account = make_account(1000)
account.deposit(500)
print(account.balance()) -- 1500
account.withdraw(200)
print(account.balance()) -- 1300
三个闭包(deposit、withdraw、get_balance)共享同一个 balance 上值,实现了数据封装。
闭包作为迭代器
Lua 标准库中的 ipairs 和 pairs 都是闭包的典型应用:
-- 自定义迭代器:range
function range(start, stop, step)
step = step or 1
local i = start - step
return function()
i = i + step
if i <= stop then
return i
end
end
end
for v in range(1, 10, 2) do
print(v) -- 1, 3, 5, 7, 9
end
-- 自定义迭代器:zip
function zip(t1, t2)
local i = 0
return function()
i = i + 1
if t1[i] and t2[i] then
return t1[i], t2[i]
end
end
end
for a, b in zip({1,2,3}, {"a","b","c"}) do
print(a, b) -- 1 a, 2 b, 3 c
end
高阶函数
闭包使 Lua 可以实现常见的高阶函数模式:
map / filter / reduce
function map(t, func)
local result = {}
for i, v in ipairs(t) do
result[i] = func(v)
end
return result
end
function filter(t, func)
local result = {}
for _, v in ipairs(t) do
if func(v) then
result[#result + 1] = v
end
end
return result
end
function reduce(t, func, init)
local acc = init
for _, v in ipairs(t) do
acc = func(acc, v)
end
return acc
end
local numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
-- 链式调用
local result = reduce(
filter(
map(numbers, function(x) return x * x end),
function(x) return x % 2 == 0 end
),
function(acc, x) return acc + x end,
0
)
print(result) -- 220 (4+16+36+64+100)
函数组合
function compose(...)
local funcs = {...}
return function(...)
local result = {...}
for i = #funcs, 1, -1 do
result = {funcs[i](table.unpack(result))}
end
return table.unpack(result)
end
end
local double = function(x) return x * 2 end
local add_one = function(x) return x + 1 end
local square = function(x) return x * x end
local transform = compose(square, add_one, double)
print(transform(3)) -- (3*2 + 1)^2 = 49
柯里化
function curry(func)
return function(a)
return function(b)
return func(a, b)
end
end
end
local add = curry(function(a, b) return a + b end)
local add5 = add(5)
print(add5(3)) -- 8
print(add5(10)) -- 15
闭包实现记忆化
闭包是实现函数记忆化(memoization)的最佳方式:
function memoize(func)
local cache = {}
return function(...)
local key = table.concat({...}, ",")
if cache[key] == nil then
cache[key] = func(...)
end
return cache[key]
end
end
-- 记忆化斐波那契
local fib = memoize(function(n)
if n <= 1 then return n end
return fib(n - 1) + fib(n - 2)
end)
print(fib(50)) -- 12586269025 (瞬间返回)
闭包与面向对象
闭包可以替代元表实现面向对象编程,提供更强的封装性:
function new_person(name, age)
-- 私有属性
local self = {
name = name,
age = age,
logs = {}
}
-- 私有方法
local function log(msg)
table.insert(self.logs, os.date() .. ": " .. msg)
end
-- 公开接口
return {
get_name = function() return self.name end,
get_age = function() return self.age end,
set_age = function(new_age)
log(string.format("年龄从 %d 改为 %d", self.age, new_age))
self.age = new_age
end,
greet = function()
return string.format("你好,我是 %s,今年 %d 岁", self.name, self.age)
end,
get_logs = function()
return self.logs
end
}
end
local person = new_person("张三", 25)
print(person.greet()) -- 你好,我是 张三,今年 25 岁
person.set_age(26)
print(person.get_logs()[1]) -- 包含年龄修改日志
-- person.name 无法直接访问,实现了真正的私有
延迟求值和惰性序列
闭包可以实现延迟求值,构建惰性序列:
function lazy_seq(gen, init)
local state = init
local computed = false
local value
return {
head = function()
if not computed then
value, state = gen(state)
computed = true
end
return value
end,
tail = function()
if not computed then
value, state = gen(state)
computed = true
end
return lazy_seq(gen, state)
end
}
end
-- 惰性自然数序列
local function naturals_gen(n)
return n, n + 1
end
local naturals = lazy_seq(naturals_gen, 1)
print(naturals.head()) -- 1
print(naturals.tail().head()) -- 2
print(naturals.tail().tail().head()) -- 3
使用 debug 库查看上值
Lua 的 debug 库提供了检查和修改闭包上值的能力:
function make_counter()
local count = 0
return function()
count = count + 1
return count
end
end
local c = make_counter()
c()
c()
c()
-- 查看上值
local name, value = debug.getupvalue(c, 1)
print(name, value) -- count 3
-- 修改上值
debug.setupvalue(c, 1, 100)
print(c()) -- 101
-- 获取闭包的上值数量
local info = debug.getinfo(c)
print(info.nups) -- 1
注意事项
闭包的使用需要注意以下方面:
- 每个闭包实例都有独立的上值副本,不会互相干扰
- 闭包会持有上值的引用,可能影响垃圾回收,注意避免循环引用
- 在循环中创建闭包时,确保上值被正确捕获而非共享意外变量
debug.getupvalue和debug.setupvalue可用于调试,但不应用于生产代码- Lua 5.2+ 中
<close>变量提供了一种资源管理替代方案 - 大量闭包创建时注意内存占用,特别是在热路径上
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。