Andrey Listopadov

Tiny OOP in Lua

@programming lua ~5 minutes read

Recently I was participating in a conversation about popular programming languages and I brought up Lua. I was met with a response like this:

Lua? Can you even do anything with this language?

The conversation then shifted towards discussing modern dynamic languages, mostly Python, and how it gives you everything Java does without being as complicated as Java.

I’m not a Python fan - in fact, I think Python is a badly designed language and should be avoided. But people tend to like it for some reason. I also think that Lua is a much better language and it’s worth considering it before jumping to Python or alternatives.

I do agree that Lua is much smaller than Python, and it can do less by itself, as there are no batteries included in the language. But it also means that you can bring in only what you need for the project.

And Lua is extremely flexible too. When the conversation shifted towards discussing Python, one particular thing was discussed the most - Python is object-oriented. Lua isn’t, as there are no classes, no inheritance, or such. I brought up Lua again in the conversation, saying that it’s quite easy to add object orientation to Lua if you need it for your project. People were skeptical so I decided to write this post.

Most of the code here I wrote on my phone while guys were arguing about Python, so I wasn’t participating in the conversation much. After I was done, I showed them the following example, but the only response I got was a yet again skeptical “neat”.

Neat.

So here’s a basic OOP-style code in Lua.

First, we need a class:

local Shape = {}

It’s just a table, but now let’s add a new method to it and do some metatable trickery:

function Shape:new(obj)
    self.__index = self
    return setmetatable(obj or {}, self)
end

Now we can create an object like this:

local s = Shape:new{}

It’s still a table, but it has Shape as it’s metatable. It means that we can add methods directly to a class, and all objects would share them:

function Shape:setOrigin(x, y)
    self.x, self.y = x, y
end

function Shape:getOrigin()
    return self.x, self.y
end

We can set the shape’s origin, and then obtain it with these methods:

s:setOrigin(1, 1)
print(s:getOrigin()) -- 1, 1

Note on self: in Lua self is an ordinary variable name, that is implicitly declared when we use method declaration syntax. In other words, these two function definitions are the same thing:

function Shape:getOrigin() -- the colon after Shape
    return self.x, self.y
end

function Shape.getOrigin(self) -- the dot after Shape
    return self.x, self.y
end

It’s just a bit more convenient to write Class:method(...) than Class.method(self, ...) when defining a function. And when we’re calling a method with : instead of . it automatically passes the object as the first argument. So again, these are the same:

s:setOrigin(1, 1)

s.setOrigin(s, 1, 1)

But what makes objects special is the ability to build upon objects using inheritance. So let’s create a new class that inherits all of the methods of Shape and provides some new on its own:

local Rectangle = Shape:new{}

function Rectangle:setWidth(width)
    self.width = width
end

function Rectangle:setHeight(height)
    self.height = height
end

function Rectangle:area()
    return self.width * self.height
end

We’ve defined a new class Rectangle that inherits all of the Shape methods:

local rect = Rectangle:new{width = 2, height = 4}

rect:setOrigin(3, 4)

print(rect:getOrigin()) -- 3, 4
print(rect:area())      -- 8

Here’s how this works:

Rectangle inherits new from Shape like any other method. This time, however, when new executes, the self parameter will refer to Rectangle. Therefore, the metatable of rect will be Rectangle, whose value at __index is also Rectangle. So, rect inherits from Rectangle, which inherits from Shape.

We can go even further, defining a class that would inherit from Rectangle but first, let’s add two helper functions:

local function implements(obj, class)
    local mt = getmetatable(obj)
    if nil ~= mt then
        if class == mt.__index then
            return true
        else
            return implements(mt.__index, class)
    end end
    return false
end

local function super (class)
    return (getmetatable(class) or {}).__index
end

The first function implements is a predicate that checks if a given object implements a given class:

print(implements(rect, Shape))     -- true
print(implements(rect, Rectangle)) -- true

The second function super allows us to access a superclass of a class. For example, to call a superclass method, like a constructor.

We can create a new class that inherits from Rectangle and use it to alter the inherited constructor:

local Square = Rectangle:new{}

function Square:new(obj)
    self.__index = self
    local obj = super(self):new{width = obj.width, height = obj.width}
    return setmetatable(obj, self)
end

You can see that Square implements all of the methods:

local square = Square:new{width = 10}

print(implements(square, Shape))     -- true
print(implements(square, Rectangle)) -- true
print(implements(square, Square))    -- true

square:setOrigin(5, 6)
print(square:getOrigin()) -- 5, 6
print(square:area()) -- 100

This isn’t a new kind of trick though. All of this was greatly described in the Programming In Lua book. In fact, I straight up took the explanation from this page and adjusted class names in it to represent the code on this page.

Now, of course, this leaves out a lot of stuff like encapsulation, and multiple inheritance, but you can build on this simple concept further if you really need to. Moreover, there are multiple ways of achieving object orientation in Lua. You can define the object’s methods in five different ways. But in my practice even what I did here is way more than I ever needed.

What I wanted to show here is the flexibility that Lua provides you with. If you need something that’s missing in pure Lua, the same pure Lua probably has a way to add that to the language. Over the years I’ve created lazy sequences, immutable data structures, and asynchronous channels all in pure Lua. With C modules you can add even crazier stuff.

Lua is a very capable language. The community made a lot of libraries. If you want so-called “batteries” there’s Penlight. If you’re making games, you have LÖVE2D that gives you its own batteries and a plethora of small Lua libraries for game-making are available too.

If you’re not a fan of the syntax or want more features in the base language you can choose amongst other languages compile to Lua. For example, there’s MoonScript which is whitespace-based like Python. Or there are Fennel and Urn if you’re into lisps. And probably other languages.

Lua is fast, has a small footprint, is easily embeddable, and is very flexible. It’s a shame it’s not taken seriously most of the time.