Lua 设计模式 |
|
nil(或 NAN=0/0 键)。...。function() return ... end 更简洁地表达匿名函数
传统上,如果我们想定义一个变量表或键值对,我们会使用表构造语法 {...} 。
-- traditional method. local squares = {}; for n=1,10 do squares[n] = n^2 end local v = { x = 50, squares = squares, hello = function() print("hello?") end }
然而,通过适当定义一个 collect 函数,我们可以改为如下方式构造:
local v = collect(function() if true then x = 50 end squares = {}; for n=1,10 do squares[n] = n^2 end function hello() return "hello?" end end)
请注意,一个潜在的好处是可以将代码语句穿插其中并构建键值对定义。但这会增加一些开销。
collect 的定义如下:
function collect(f) -- This collects globals defined in the given function. local collector = setmetatable({}, {__index = _G}) -- Call function in collector environment setfenv(f, collector)() -- Extract collected variables. local result = {}; for k,v in pairs(collector) do result[k] = v end return result end
测试套件
assert(v.x == 50) assert(#v.squares == 10) assert(v.hello() == "hello?") assert(v.print == nil) -- ensure _G not included. print("done")
这种机制在 Lua 5.1 模块系统中有所使用,其中收集函数在文件中给出,而 require/module 函数实现了收集机制(以及其他功能)。请参阅 [Programming in Lua 2nd Edition] 的第 15 章。
module("mymodule") x = 50 color = "blue" function hello() return "hello?" end
--DavidManura, 2006-10, Lua 5.1
有很多有趣的方式可以在给定事件上执行一系列操作。我见过一种效率不太高的方式是这样的:
for _,v in pairs(files_in_directory) do dofile(v) if action then action(args) end action = nil end其中目录中的一个文件可能看起来像这样:
function action(something) print(something) end这是低效的;它需要在每次调用时重新解析所有内容,并且会覆盖一个名为“action”的全局变量。它也不能有效地处理权重。在 naim 中,我们使用一个用 C 实现的钩子系统,该系统创建了一堆链,我们可以向其中注册 C 和 Lua 操作。
我编写了一个系统,允许用户在 Lua 中创建自己的钩子链,这些链可以像函数一样执行。对我来说,语法似乎相当合乎逻辑。
require"hooks" myhooks = {} myhooks.test = hooks:new() myhooks.ref1 = myhooks.test:insert(function (foo, bar) print("Test 1", foo, bar) end, 100) myhooks.ref2 = myhooks.test:insert(function (foo, bar) print("Test 2", foo, bar) end, 50) myhooks.ref3 = myhooks.test:insert(function (foo, bar) print("Test 3", foo, bar) end, 150) print"--" myhooks.test("Hello", "world") myhooks.test:remove(myhooks.ref1) print"--" myhooks.test("Hello", "world")
运行这段代码会产生类似以下的输出:
-- Test 2 Hello World Test 1 Hello World Test 3 Hello World -- Test 2 Hello World Test 3 Hello World
驱动此功能的代码可在 [1] 找到。待办事项:支持可写参数。这在 naim 中是必需的,如果希望修改通过的字符串;例如,一个过滤器模块可能希望将输入字符串中的所有“lol”实例替换为“<grin>”,然后将修改后的字符串传递给链中的其他钩子。欢迎贡献补丁。
-- JoshuaWise, 2007-02-01
有时会遇到一个冲突:一个变量应该被词法作用域限制在特定函数内,但其生命周期也应该比函数调用更长。在下面的例子中,sounds 表仅由 soundit 函数使用,这表明可以将其移入 soundit 函数内部,但每次函数调用时重建 sounds 会很浪费,所以程序员通常会将 sounds 放在外面。
local sounds = { a = "ay", b = "bee", c = "see", .... } local function soundit(x) assert(type(x) == "string") return sounds[x] end
在 C 语言中,我们可以将 sounds 设为 soundit 内部的一个静态变量。在 Lua 中,如果想限制 sounds 的作用域,通常的建议是将 sounds 和 soundit 包围在一个 do 块中:
local soundit; do local sounds = { a = "ay", b = "bee", c = "see", .... } function soundit(x) assert(type(x) == "string") return sounds[x] end end -- note: sounds not visible outside the do-block.
一个抱怨是,现在函数的实现分散在函数外部,soundit 这个名字被重复了,并且代码进一步缩进/变得丑陋,看起来不太像函数定义。此外,无论 soundit 是否被调用,sounds 都会被初始化(从而带来加载时的开销)。以下方法将 sounds 放在函数外部,但将其初始化移到函数内部。由于 or 的短路行为,它通常在调用时带来的额外开销很小:
local soundit; do local sounds; function soundit(x) sounds = sounds or { a = "ay", b = "bee", c = "see", .... } assert(type(x) == "string") return sounds[x] end end
事实上,我们可以放弃完美,让词法作用域溢出以增强可读性。
local sounds local function soundit(x) sounds = sounds or { a = "ay", b = "bee", c = "see", .... } assert(type(x) == "string") return sounds[x] end
这里有两个涉及闭包构造的变体。它们比 do 块方法更整洁一些,但会带来至少构造一个临时函数的加载时开销。
local soundit = (function() local sounds = { a = "ay", b = "bee", c = "see", .... } return function(x) assert(type(x) == "string") return sounds[x] end end)()
local soundit = (function() local sounds return function(x) sounds = sounds or { a = "ay", b = "bee", c = "see", .... } assert(type(x) == "string") return sounds[x] end end)()
--DavidManura, 2007-03
<Pattern description> (在此处添加更多模式)