Module Definition |
|
-- 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.
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.
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.
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