Lua Design Patterns

lua-users home
wiki

This page is intended for Lua design patterns. See [Wikipedia: design pattern] for background.

Design Patterns on Other Pages

Pattern: Global Collector

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

Pattern: Function chains / hooks

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
end
where a file in the directory might look like this:
function action(something)
  print(something)
end
This 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

Pattern: Static Local Variables

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: <Pattern Name>

<Pattern description> (Add more patterns here)

See Also


RecentChanges · preferences
edit · history
Last edited August 2, 2017 9:58 am GMT (diff)