Lua Design Patterns |
|
nil
(or NAN=0/0
keys) in a table.
...
.
function() return ... end
Traditionally, if we want a define a table of variables, or key-value pairs, we use table construction syntax {...}
:
-- 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 }
However, by appropriate definition of a function collect
, we may alternately construct as follows:
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)
Note one potential benefit is that statements of code can be interspersed with and build the key-value definitions. It does impose a bit more overhead though.
collect
is defined as follows:
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
Test suite:
assert(v.x == 50) assert(#v.squares == 10) assert(v.hello() == "hello?") assert(v.print == nil) -- ensure _G not included. print("done")
This type of mechanism is used in the Lua 5.1 module system, where the collecting function is given in a file and the require
/module
functions implement the collect mechanism (as well as other things). See Chapter 15 of [Programming in Lua 2nd Edition].
module("mymodule") x = 50 color = "blue" function hello() return "hello?" end
--DavidManura, 2006-10, Lua 5.1
There are a bunch of interesting ways to execute a series of actions on a given event. One way that I've seen that was somewhat less than efficient looked like this:
for _,v in pairs(files_in_directory) do dofile(v) if action then action(args) end action = nil endwhere a file in the directory might look like this:
function action(something) print(something) endThis is inefficient; it requires everything to be reparsed each call, and it smashes the global called "action". It doen't provide for effective weighting, either. In naim, we use a hook system that's done in C that creates a bunch of chains, to which we can register C and Lua actions.
I wrote a system that allows one to create their own hook chains in Lua that can be executed like functions. The syntax seems fairly logical to me:
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")
Running this would produce output like:
-- Test 2 Hello World Test 1 Hello World Test 3 Hello World -- Test 2 Hello World Test 3 Hello World
The code that drives this is available at [1]. Still to do: support for writable arguments. This is necessary in naim if one wishes to modify a string that gets passed through; i.e., a filter module might want to substitute all instances of "lol" to "<grin>" in the input string, and then pass the modified string through to further hooks in the chain. Patches thoughtfully accepted.
-- JoshuaWise, 2007-02-01
One sometimes runs into a conflict where a variable should be lexically scoped to a particular function but should also have a lifetime longer than the function call. In the below case, the sounds
table is used only by the function soundit
, which would suggest bringing it inside the soundit
function, but it would be wasteful to reconstruct sounds
on each function call, so often the programmer will keep sounds
outside:
local sounds = { a = "ay", b = "bee", c = "see", .... } local function soundit(x) assert(type(x) == "string") return sounds[x] end
In the C language, we might make sounds
a static variable inside soundit
. In Lua, the usual suggestion here, if one wants to limit the scope of sounds
, is to surround sounds
and soundit
with a do
block:
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.
One complaint is that now the implementation of the function is spread outside the function, the name soundit
is duplicated, and the code is further indented/ugly, appearing less like a function definition. Furthermore, sounds
will get initialized regardless whether soundit
ever gets called (thereby imposing a load-time overhead). The following approach keeps sounds
outside the function but moves its initialization inside the function. Due to the short-circuiting behavior of or
, it will generally impose little additional overhead at call-time:
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
In fact, we may just give up perfection and let the lexical scope spill over for enhanced readability:
local sounds local function soundit(x) sounds = sounds or { a = "ay", b = "bee", c = "see", .... } assert(type(x) == "string") return sounds[x] end
Here are two variations involving the construction of closures. These are a bit more tidy than the do
block approach but do impose the load-time overhead of at least constructing a temporary function.
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> (Add more patterns here)