Module Definition

lua-users home
wiki

There are many ways to define a "module"[1] in Lua.

From a Table

-- mymodule.lua
local M = {} -- public interface

-- private
local x = 1
local function baz() print 'test' end

function M.foo() print("foo", x) end

function M.bar()
  M.foo()
  baz()
  print "bar"
end

return M

-- Example usage:
local MM = require 'mymodule'
MM.bar()

This is a common approach. It is simple, relies on no external code, avoids globals, and has few pitfalls. Externally facing variables are prefixed by "M." and clearly seen.

Using the module Function

module(..., package.seeall)  -- optionally omitting package.seeall if desired

-- private
local x = 1
local function baz() print 'test' end

function foo() print("foo", x) end

function bar()
  foo()
  baz()
  print "bar"
end

-- Example usage:
require 'mymodule'
mymodule.bar()

This is also common and shorter. It uses Lua's module function. Some other ways of using the module function are in Programming in Lua[2]. However, see LuaModuleFunctionCritiqued for criticisms of this approach.

From a Table - Using Locals Internally

local M = {}

-- private
local x = 1
local function baz() print 'test' end

local function foo() print("foo", x) end
M.foo = foo

local function bar()
  foo()
  baz()
  print "bar"
end
M.bar = bar

return M

This is similar to the table approach, but even inside the module itself it uses lexicals when referring to externally faced variables. Although this code is more verbose (repetitive), lexicals can be more efficient for performance critical code and more suitable for static analysis approaches to DetectingUndefinedVariables. Futhermore, this approach prevents changes made to M--e.g. from a client--from affecting the behavior inside the module; for example, in the regular tables approach, M.bar() internally calls M.foo(), so the behavior of M.bar() will change if M.foo() were replaced. This has some implications for SandBoxes too, and it is the reason for the extra locals in etc/strict.lua in Lua 5.1.3.

localmodule

local M = {}

local x = 1  -- private

local M_baz = 1  -- public

local function M_foo()
  M_baz = M_baz + 1
  print ("foo", x, M_baz)
end

local function M_bar()
  M_foo()
  print "bar"
end

require 'localmodule'.export(M)

return M

-- Example usage:
local MM = require 'mymodule'
MM.baz = 10
MM.bar()
MM.foo = function() print 'hello' end
MM.bar()

-- Output:
-- foo     1       11
-- bar
-- hello
-- bar

This approach is more novel. It defines all externally facing variables in the module using lexical (local) variables. It makes heavy use of lexicals. Reliance on lexicals has some advantages such as when using the static analysis methods of DetectingUndefinedVariables.

The export function uses the debug module to read the current function's local variables prefixed by M_ (debug.getlocal) and expose them (read/write) through the module table M via metafunctions. The ability to write to these variables is made possible by searching for and using (whenever possible) the upvalues located in closures (debug.getupvalue/debug.getupvalue), such as in the nested closures. This avoids the repetition seen in "From a Table - Using Locals Internally". You may selectively replace M_foo style references with M.foo style references if more dynamic behavior is desired.

The implementation of localmodule assumes that symbols have not been stripped (luac -s) and that the debug module has not been removed, so this approach does have a bit more baggage.

The localmodule module is defined as

-- localmodule.lua
-- David Manura, 2008-03, Licensed under the same terms as Lua itself (MIT License).
local M = {}

-- Creates metatable.
local getupvalue = debug.getupvalue
local setupvalue = debug.setupvalue
local function makemt(t)
  local mt = getmetatable(t)
  if not mt then
    mt = {}
    setmetatable(t, mt)
  end
  local varsf,varsi = {},{}
  function mt.__index(_,k)
    local a = varsf[k]
    if a then
      local _,val = getupvalue(a,varsi[k])
      return val
    end
  end
  function mt.__newindex(_,k,v)
    local a = varsf[k]
    if a then
      setupvalue(a,varsi[k], v)
    end
  end
  return varsf,varsi
end

-- Makes locals in caller accessible via the table P.
local function export(P)
  P = P or {}

  local varsf,varsi = makemt(P)

  -- For each local variable, attempt to locate an upvalue
  -- for it in one of the local functions.
  --
  -- TODO: This may have corner cases. For example, we might want to
  -- check that these functions are lexically nested in the current
  -- function (possibly with something like lbci).
  for i=1,math.huge do
    local name,val = debug.getlocal(2, i)
    if val == nil then break end
    if type(val) == 'function' then
      local f = val
      for j=1,math.huge do
        local name,val = debug.getupvalue(f, j)
        if val == nil then break end
        if name:find("M_") == 1 then
          name = name:sub(3)
          varsf[name] = f
          varsi[name] = j
          --print('DEBUG:upvalue', name)
        end
      end
    end
  end

  -- For each local variable, it no upvalue was found, just
  -- resort to making a copy of it instead.
  for i=1,math.huge do
    local name,val = debug.getlocal(2, i)
    if val == nil then break end
    if name:find("M_") == 1 then
      name = name:sub(3)
      if not varsf[name] then
        rawset(P, name, val)
        --print('DEBUG:copy', name)
      end
    end
  end

  return P
end
M.export = export

return M

See Also


FindPage · RecentChanges · preferences
edit · history
Last edited March 29, 2008 10:24 pm GMT (diff)