modules as classes in lua

suppose we have something like this:

local value = 1
local function get()
	return value
end
local function double()
	value = value * 2
end
return {
	get = get,
	double = double,
}

this is a pretty simple example of a "thing with methods and some state", and could be used like this:

local doubler = require "doubler"
doubler.double()
print(doubler.get()) --> 2
doubler.double()
doubler.double()
print(doubler.get()) --> 8

but what if we wanted multiple instances of this doubler thing? a common approach is to make it a class:

local methods = {}
local function new()
	return setmetatable({
		value = 1,
	}, {
		__index = methods,
	})
end
		
function methods.get(self)
	return self.value
end
function methods.double(self)
	self.value = self.value * 2
end
return {
	new = new,
}

which could then be used like so:

local doubler = require "doubler"
local d1 = doubler.new()
local d2 = doubler.new()
d1:double() 
d1:double() 
print(d1:get()) --> 4
print(d2:get()) --> 1

that required a quite a lot of changes to the original module. for instance, every field access now requires a preceeding self.. but today it occurred to me something like this would also work:

-- unchanged from the original
local value = 1
local function get()
	return value
end
local function double()
	value = value * 2
end
return {
	get = get,
	double = double,
}
local doubler = loadfile "doubler.lua"
local d1 = doubler()
local d2 = doubler()
-- then can be used like the class case
-- though with '.' instead of ':'
d1.double() 
d1.double() 
print(d1.get()) --> 4
print(d2.get()) --> 1

simply by changing require to loadfile, we get basically the same behaviour as the class-based approach, but with no refactoring necessary. this works because load and loadfile, unlike require, don't evaluate the loaded module immediately: they instead return a function that evaluates the module when run. in our case, every time doubler() is called, a new local value is created, and two closures are returned with that new local bound as an upvalue.

sometimes people say things like "closures are just a poor man's objects", or "objects are just a poor man's closures". i don't know what poor men have to do with anything, but this demonstration shows that the two do have an overlap of usecases.

but is it good?

pros:

cons:

overall i'm not sure i'd ever use this in a real project, even though it is kind of cool. also, considering how simple it is, i'm surprised i've never seen it before.


  1. actually in our case using : happens to be fine, but if any of the methods were taking arguments then it would break things ↩︎