Introduction
With Lua 5.2 the recommended way to write modules changed. Instead of using module(“mymodule”, package.seeall) the recommendation now is to create a local table, put all of the module’s functions in it and return the table. The big difference is a local table does not register the module in the global namespace.
Personally, I like this change. It keeps the modules self contained and reduces name collisions. That said, I’ve found information on using this concept to be a bit lacking. Most of the information I’ve seen is either very basic or highly convoluted and trying to create a full OOP class system in Lua using Lua.
Basic Example
Typically, you’ll see examples like the following for writing Lua modules:
functions_module.lua
local M = {}
function M.func1()
print("func1")
end
function M.func2()
print("func2")
end
return M
main_functions.lua
local fm = require("functions_module")
fm.func1()
fm.func2()
Using this module:
$ lua main_functions.lua
func1
func2
This module works fine if all you want to do is provide a collection of semi-independent functions. It’s not clear how you would emulate say a C++ class.
My take on writing modules
I’ve been using the following module blueprint. It doesn’t support inheritance but it does support storing state within each instance of the module. The fact that I mostly work in C, the lack of inheritance doesn’t really bother me.
If you think about an object in C what you really have is a data pointer and functions that take/use that data pointer. Something like:
int func1(func_data_t *d, int arg1)
The basic example above is more or less this idea. The issue with it is you have to track the data used by each function. This isn’t very Lua like. Instead it would be useful to let the object store it’s data.
example_module.lua
local M = {}
local M_mt = { __index = M }
function M:new(arg1)
if self ~= M then
return nil, "First argument must be self"
end
local o = setmetatable({}, M_mt)
o._arg1 = arg1
return o
end
setmetatable(M, { __call = M.new })
function M:_private1(arg1)
print("passed arg = " .. (arg1 or "nil"))
end
function M:public1()
print("self._arg1 = " .. (self._arg1 or "nil"))
end
function M:public2(arg1)
self:_private1(arg1)
self:public1()
end
function M_mt:__tostring()
return string.format("%s", o._arg1)
end
return M
Breaking this down we have M and a metatable for M. The metatable points to M
for __index
.
The idea behind new is we create a table o
using setmetatable and using the
M_mt
as it’s metatable. We set internal data members of o
and they’re
prefixed with _
. This is a convention (think Python) to denote that the data
is considered private. New then returns the table. The returned value can be
used to call any of the M functions which can be called using :
to get a
reference to that particular table and it’s internal data members.
You’ll notice we have function M:new(arg1)
using :
instead of .
like
you’ll typically see with Lua modules. This is on purpose because it allows us
to register new to __call
via setmetatable(M, { __call = M.new })
. This
allows use to do mod()
in addition to of mod:new()
. Notice that this
setmetatable call is on M
not M_mt
. This allows us to do mod()
but still
allows us to set a __call
function on M_mt
(for the object returned by
new).
At the very top of the file we set the __index
of M_mt
directly when we
create M_mt
. This is a convenience instead of having a separate function
definition for __index
referencing M
. Other meta functions we can create
directly in the module. See function M_mt:__tostring()
for example.
Here is an example of various ways we can construct and use the module:
main_example.lua
local exm = require("example_module")
mym1 = exm:new("mym1")
print(mym1)
mym1:public1()
mym1:public2("a")
print()
mym2 = exm("mym2")
print(mym1)
mym2:public1()
mym2:public2("b")
print()
mym3 = exm.new(exm, "mym3")
print(mym1)
mym3:public1()
mym3:public2("c")
And the output when we run the example:
$ lua main_example.lua
mym1
self._arg1 = mym1
passed arg = a
self._arg1 = mym1
mym2
self._arg1 = mym2
passed arg = b
self._arg1 = mym2
mym3
self._arg1 = mym3
passed arg = c
self._arg1 = mym3
Overall I’ve found the above approach to be very easy to use when constructing
Lua modules. The only real drawback I’ve seen other than not supporting some
form of inheritance is the fact that new must be called with a :
. This is not
what most people will be used to. We could change it to M.new
instead but
we’d loose the ability to register __call
to new on M
. That said, I’ve
found this module implementation works well for me needs.