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 effcetive 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 http://joshuawise.com/hooks.lua . 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: Module System with Public/Private Namespaces

As noted in Programming in Lua, 2nd edition p.144, when using the Lua 5.1 module system with the package.seeall option (or the equivalent setmetatable(M, {__index = _G}) trick), there is a peculiarity in that global variables are accessible through the module table. For example, if you have a module named complex defined as such:

-- complex.lua
module("complex", package.seeall)
-- ...

then doing

require "complex"
print(complex.math.sqrt(2))

prints the square root of 2 because math is a global variable. Furthermore, if a global variable with name complex already exists (possibly defined in some unrelated file), then the require will fail:

-- put this in the main program:
complex = 123
-- then deep in some module do this:
local c = require "complex"
--> fails with "name conflict for module 'complex'"

This is a type of namespace pollution and possibly a source of errors.

The problem as I see it is that the environment used internally by the module is the same as the table exposed to the client of the module. We can make these two separate tables as given in the below solution:

-- cleanmodule.lua

-- Declare module cleanly.
-- Create both public and private namespaces for module.
-- Global assignments inside module get placed in both
-- public and private namespaces.
function cleanmodule(modname)
  local pub = {}     -- public namespace for module
  local priv = {}  -- private namespace for module
  local privmt = {}
  privmt.__index = _G
  privmt.__newindex = function(priv, k, v)
    --print("DEBUG:add",k,v)
    rawset(pub, k, v)
    rawset(priv, k, v)
  end
  setmetatable(priv, privmt)
  setfenv(2, priv)

  package.loaded[modname] = pub
end

-- Require module, but store module only in
-- private namespace of caller (not public namespace).
function cleanrequire(name)
  local result = require(name)
  rawset(getfenv(2), name, result)
  return result
end

Example usage:

-- test.lua
require "cleanmodule"

m2 = 123  -- variable that happens to have same name as a module

cleanrequire "m1"

m1.test()

assert(m1)
assert(not m1.m2)  -- works correctly!
assert(m1.test)
assert(m1.helper)

assert(m2 == 123)  -- works correctly!

print("done")

-- m1.lua
cleanmodule(...)

cleanrequire "m2"

function helper()
  print("123")
end

function test()
  helper()
  m2.test2()
end

assert(not m1)
assert(test)
assert(helper)

assert(m2)
assert(m2.test2)
assert(not m2.m1)
assert(not m2.m2)

-- m2.lua
cleanmodule(...)

function test2()
  print(234)
end

Output:

123
234
done

Take #2 - Here is the latest refinement of the previous code. This version only replaces module not require.

-- cleanmodule.lua

-- Helper function added to modules defined by cleanmodule
-- to support importing module symbols into client namespace.
-- Usage:
--   local mm = require "mymodule"  -- only local exported
--   require "mymodule" ()          -- export module table to environment
--   require "mymodule" ":all"      -- export also all functions
--                                     to environment.
--   require "mymodule" (target,":all")  -- export instead to given table
local function import(public, ...)
  -- Extract arguments.
  local target, options = ...
  if type(target) ~= "table" then
    target, options = nil, target
  end
  target = target or getfenv(2)

  -- Export symbols.
  if options == ":all" then
    for k,v in pairs(public) do target[k] = v end
  end

  -- Build public module tables in caller.
  local prevtable, prevprevtable, prevatom = target, nil, nil
  public._NAME:gsub("[^%.]+", function(atom)
    local table = rawget(prevtable, atom)
    if table == nil then
      table = {}; rawset(prevtable, atom, table)
    elseif type(table) ~= 'table' then
      error('name conflict for module ' .. public._NAME, 4)
    end
    prevatom = atom; prevprevtable = prevtable; prevtable = table
  end)
  rawset(prevprevtable, prevatom, public)

  return public
end

-- Declare module cleanly.
-- Create both public and private namespaces for module.
-- Global assignments inside module get placed in both
-- public and private namespaces.
function cleanmodule(modname)
  local pubmt = {__call = import}
  local pub = {import = import, _NAME = modname} -- public namespace for module
  local priv = {_PUBLIC = pub, _PRIVATE = priv,
                _NAME = modname} -- private namespace for module
  local privmt = {
    __index = _G,
    __newindex = function(priv, k, v)
      rawset(pub, k, v)
      rawset(priv, k, v)
    end
  }
  setmetatable(pub, pubmt)
  setmetatable(priv, privmt)
  setfenv(2, priv)

  pub:import(priv)

  package.loaded[modname] = pub
end

This is typically used in this way:

-- somemodule.lua
require "cleanmodule"
cleanmodule(...)

local om = require "othermodule"

om.hello()

require "othermodule" ()

othermodule.hello()

require "othermodule" ":all"

hello()

The caller has full control in deciding how it wants to the called module to modify the caller's (private) namespace.

One small problem you might run into is when setting a global twice:

cleanmodule(...)
local enable_spanish = true
function test() print("hello") end
if enable_spanish then test = function() print("hola") end end

Here, the metamethod only activates on the first set, so the public namespace will incorrectly contain the first function defined above. The work around is to explicitly set to nil:

cleanmodule(...)
local enable_spanish = true
function test() print("hello") end
if enable_spanish then test = nil; test = function() print("hola") end end

--DavidManura, 200703

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


FindPage · RecentChanges · preferences
edit · history
Last edited February 2, 2008 6:18 pm GMT (diff)