LJ Archive

Object-Oriented Programming with Lua

Alejandro Segovia

Issue #217, May 2012

In spite of being a multiparadigm programming language, Lua provides a nontrivial object-oriented programming model that may prove cumbersome to programmers who want to apply OO software engineering practices to development. This article presents a reusable mechanism through which you can implement an object-oriented model using Lua's built-in constructs.

Lua is a dynamic scripting language and has been developed at the PUC-Rio University in Brazil since 1993. In its current 5.2 version, it is lightweight and portable, and it has become very successful for both open-source and commercial software projects, particularly in the field of video games, where it is being used by companies like EA.

Lua also is becoming increasingly important for mobile device software development and recently has been chosen by Wikipedia as its new scripting language. I first became interested in Lua when I learned about how easy it was to embed into C and C++ applications.

But, enough introduction—let's start creating some objects!

A Basic Object-Oriented Model

The first thing you should know is that Lua does not provide any special constructs for declaring object classes. Indeed, all Lua knows about are “tables”. Programming in Lua in an object-oriented fashion boils down to being able to create an object model on top of Lua tables.

Fear not, however. Here I walk-though a model that can be used (hopefully) for all your object-oriented needs. This model is based on the one presented in the book Programming in Lua, which I highly recommend reading.

Let's start by supposing you want to create a mechanism through which you can represent 2-D Point objects. In Lua, you can define a generic Point table that looks like this:

Point = {x=0, y=0}

Think of this as the template for your point objects. All of your points will have x and y attributes.

Now, you can't do much with this construct as it stands. You need a way to “create” new points. In order to do so, let's add to the Point table a new attribute called, well, “new”. It will allow you to create new points:

Point.new = function(o)
    if o then
        return o
    else
        return {x=0, y=0}
    end
end

The reason to add a branch in the code is that in case the caller doesn't supply any parameters, the function will just create a default point set to (0,0).

That wasn't very difficult! You now have a Constructor and can create new points like so:

p1 = Point.new({x=10, y=10})

However, for people coming from other programming languages, this function declaration might look a little strange and even may be hard to remember. Fortunately, Lua provides some syntactic sugar that allows you to rewrite this constructor in a more familiar fashion.

Take a look at the following snippet. The semantic value is exactly the same as before:

function Point.new(self, o)
        local p = o or {x=0, y=0}
        return p
end

It's much nicer, right? Let's stick to this style for the rest of this article.

And, that's it as far as creating or “instantiating” new points goes. Now, let's add a method to the Point class. Let's provide the points the ability to pretty-print themselves to the standard output.

First, give Points a print method:

function Point.print(p)
    print("("..p.x..","..p.y..")")
end

Now, you can print points like so:

p1 = Point.new({x=10, y=10})
Point.print(p1) --prints: "(10,10)"

In a similar fashion, you can add a range of methods like translating, scaling and rotating points. The only limitation is that you always will have to call these methods as shown above: prefixing every invocation with the class name and explicitly supplying the point on which to operate.

It is natural to ask, “can you improve this syntax?” It turns out, you can by using Lua's colon operator (:).

The colon operator can be used in a statement like p1:print(), and it will be expanded to p1.print(p1) automatically. Just like there are two semantically equivalent options for adding methods to your classes, these two expressions are semantically equivalent as well.

Now, consider this: if you could have Lua associate whatever name is at the left of the colon operator to the object class, you would be able to simulate message passing to objects. This would allow using the following syntax with your objects:

p1:print() --prints p1
p2:print() --prints p2

Thus far, however, you can't associate p1 and p2 with Point. When p1:print() is expanded to p1.print(p1), the Lua interpreter will print an error message stating that "p1" has no attribute called "print".

Who knows about “print”? Well, “Point” does. What you need to do is tell Lua that when it fails to find a given attribute or method in “p1”, it has to continue searching in “Point”. This association can be declared using the concept of metatables.

Assuming “p1” is a point like before, this snippet will set everything up so the Lua interpreter continues searching for attributes and methods missing in “p1” in your Point class:

setmetatable(p1, Point)
Point.__index = Point

After this little change, the following code will start to behave the way you want:

p1.print(p1) --prints p1

But more important, its semantically equivalent sibling will work too:

p1:print() --prints p1

What sadly will not work is trying to do this on “p2”, because “p2” has no metatable. Therefore, you must set its metatable to “Point” as well. And, not just for “p2”, but also for every single Point instance you create.

Although manually setting the metatable for every Point instance definitely is an option, it might prove to be cumbersome and error-prone. Furthermore, because this is an operation that has to be performed on every point instance, why not do it in the Point constructor? Let's do that.

This is the updated Point constructor. It first will check whether an object has been supplied and will create a default one if it hasn't. Then, it will set the object's metatable to be Point, making unrecognized messages be rerouted to the Point class for every point created:

function Point.new(self, o)
    local p = o or {x=0, y=0}
    setmetatable(p, self)
    self.__index = self
    return p
end

Note: I am passing the metatable as the first argument of the Constructor (and calling it “self”). This not only will allow you to create new points using the colon operator (as shown in the next example), but it also lets you do some advanced object-oriented tricks that I'll discuss later.

With this updated constructor, the following is now valid:

p1 = Point:new({x=10, y=10})
p2 = Point:new({x=20, y=20})

That's it as far as declaring, instantiating and using point objects goes. Try running this example using the Lua interpreter, and make sure you understand the concepts. I will build upon them in the next sections to show how to implement Polymorphism and Inheritance.

Polymorphism

In the previous section, I described a mechanism by which you can model a class “Point” by means of Lua tables. In this section, let's build on the previous concepts to support Polymorphism with your objects.

Polymorphism is one of the most powerful concepts in object-oriented programming. It allows the programmer to change the behavior of a system dynamically based on the type of the objects to which you send your messages.

Although Lua doesn't provide an out-of-the-box object model, it turns out that implementing Polymorphism in Lua is very easy due to the dynamic nature of the language.

Suppose you have a global function “printpoints()”, which receives a list of points and has them printed to the standard output. Furthermore, suppose you had two different point types in your system: Point, which models 2-D points just like you have been using so far, and Point3D, a new class that represents 3-D points that live in 3-D space.

For this example, let's imagine that the “printpoints()” function will be required to handle mixed lists of 2-D and 3-D objects.

Well, if you can have both point types present in a single list and you have a statically typed programming background, you already might be thinking that you need both points to ascribe to a common interface that allows them to be printed. The term “IPrintable” may even come to mind.

Indeed, what you require is for both points to share a set of messages they respond to. In other words, you need both types to agree to a contract that “certifies” that they can be “printed”.

In statically typed programming languages, this contract can be expressed by means of Interfaces. In Lua, however, such a construct is not necessary, as you can use the dynamic foundation of the language to ask, at runtime, whether an object responds to any given message.

In this case, the message would be “print”.

This concept of objects conforming to a common Interface that is not declared anywhere explicitly is known as an Implicit Protocol. Both point types will abide to the Implicit Protocol of knowing how to print themselves.

Let's see how this “printpoints()” function could go:

function printpoints(points)
    for _, p in ipairs(points) do
        if p.print then
            p:print()
        end
    end
end

Here, you receive the list of points to print, and independently of what the list contains, iterate over its elements. For each element, you ask whether it responds to the message “print”. If it does, you send it the “print” message.

Let's use the interactive interpreter to see how this function behaves when handling a list consisting of two 2-D Points and one 3-D Point:

$ lua
Lua 5.1.4  Copyright (C) 1994-2008 Lua.org, PUC-Rio
> require "point"
> points = { Point:new({x=10,y=10}), Point:new({x=20,y=20}), 
 ↪Point3D:new({x=30,y=30,z=1}) }


--The "points" list contains 2D and 3D points.


> printpoints(points)
(10,10)
(20,20)
(30,30,1) -- This is a 3D point!

Note how the points in the list were able to respond to the “print” message, each using its corresponding behavior, meaning 2-D points printed their (x,y) pairs, whereas the single 3-D point correctly printed its (x,y,z) tuple. This is, effectively, Polymorphism in Lua.

If you are feeling uneasy about the concept of Interfaces, remember that the true meaning of Polymorphism is not in implementing Interfaces, but rather in having objects dynamically respond to the messages received according to their type. Dynamically typed programming languages are, therefore, an ideal fit for Polymorphism.

Inheritance

The last major concept common to object-oriented programming that I'm going to tackle here is Inheritance. How can you add an Inheritance mechanism to the object-oriented model I've been discussing?

Unlike Polymorphism, when modeling type Inheritance, it is not enough to have the objects ascribe to the same Implicit Protocol and rely on the dynamism of Lua. In order to implement Inheritance, you need to describe a relationship effectively between the types of the base data type (the base class) and the derived data type (the derived class).

What you need to do is be able to “extend” an object type with a newer, “more concrete” type that adds specific logic.

The way to achieve this using Lua might seem strange at first, but if you bear with me, you will see it naturally matches the concepts used here thus far.

To continue with this example, let's add to the system the ability to represent 3-D points in homogeneous coordinates. In homogeneous coordinates, each 3-D point is represented using four values (x,y,z,w).

When converting from Cartesian coordinates to homogeneous coordinates, you just need to set the w value to 1. To convert from homogeneous coordinates back to Cartesian coordinates, you need to divide all components of the point by the w value, therefore taking (x,y,z) back to Cartesian coordinates.

The 3-D point (1,1,1) in homogeneous coordinates would be (1,1,1,1), as well as (2,2,2,2), (3,3,3,3) and so on. If you are not convinced, try dividing by w in each case and see what happens.

Points in 4-D homogeneous coordinates are 3-D points, meaning, Points in homogeneous coordinates (PointH) have an “is a” relationship with class Point3D. This is an Inheritance relationship you can represent in the object model.

Assuming class Point3D exists and is similar to Point, you start by stating that PointH (4-D homogeneous coordinate points) are 3-D Points:

PointH = Point3D:new({x=0, y=0, z=0, w=1})

What I did here, conceptually, was to declare PointH to be a new class that “Inherits” all the behavior from Point3D. In particular, its metatable will point to Point3D.

What's interesting is the fact that when you create a new PointH instance called “ph”, by using the PointH:new() method, Point3D's constructor will be called, but with the “self” object pointing to (table) PointH instead. Therefore, the “ph” instance will have its metatable pointing to PointH and not Point3D.

This achieves a “chain” of metatables. PointH instances will have PointH as their metatable, whereas class PointH will have Point3D as its metatable. This chain of metatables allows the Lua interpreter to conduct the following searches automatically when a message is sent to a PointH instance:

  • 1) First, search in the instance itself.

  • 1.1) If the method is present, call it.

  • 1.2) Otherwise, search in the PointH class.

  • 1.2.1) If the method is present, call it.

  • 1.2.2) Otherwise, search Point3D class.

  • 1.2.2.1) If present, call it.

  • 1.2.2.2) Otherwise, fail.

In this example, if you send “ph” a “print” message, “ph” doesn't know “print”, so it delegates the message to its metatable: PointH. PointH doesn't know how to “print” either, so it delegates the message to its own metatable: Point3D. Point3D knows how to “print”, so its method is used.

Now, suppose you taught PointH how to interpret the “print” message by defining the function PointH.print(p). In that case, PointH's print would have been used instead of continuing the search in Point3D.

What this means is that your Inheritance model effectively supports “overriding” a base class' methods.

This is how PointH's complete implementation would look:

PointH = Point3D:new({x=0, y=0, z=0, w=1})

function PointH.print(p)
    print("("..p.x..","..p.y..","..p.z..","..p.w..")")
end

Here, Point3D's print is overridden with PointH's, meaning that when calling print on a PointH instance, the w value will be printed along the (x,y,z) tuple.

Conclusion

This article described one of the mechanisms through which object-oriented programming can be implemented in the Lua programming language. Polymorphism and Inheritance, two of the main pillars upon which object-oriented programming is based, was discussed as well.

Hopefully, you will be able to use these ideas, or build on top of them, to bring your own object-oriented software designs to Lua.

Alejandro Segovia Azapian is a computer engineer from Uruguay. He specializes in computer graphics, and he has conducted several undergraduate courses at the Uruguayan Catholic University on the topic. At the time of this writing, he is working as a software engineer for Autodesk, Inc.

LJ Archive