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 第 2 版] 的第 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
的作用域,则用 do
块将 sounds
和 soundit
包围起来。
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
的名称被重复,代码进一步缩进/难看,看起来不像函数定义。此外,sounds
将被初始化,无论 soundit
是否被调用(从而造成加载时开销)。以下方法将 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
<模式描述>(在此添加更多模式)