We’re going to talk about working with classes in Lua. I assume you’ve read the basics of Lua and about Object-Oriented Principles. We are going to gloss over some concepts (like metatables) and instead focus on getting the basics down.
To start off, Lua is not really a truly object-oriented language. Classes can be implemented in several ways and there are several libraries and extensions which add classes. Classes can be made with either tables or closures. This tutorial is going to focus only on the most basic cases for table based classes.
Syntactic Sugar
We’re going to be making use of some features in Lua which are known as “syntactic sugar”. Syntactic sugar is just a nice way of saying a feature which doesn’t necessarily change something in the language, but just makes it easier. We’re going to make a class and go over how to make it look nice using copious syntactic sugar.
Our First Class
classexample.lua:
#!/usr/bin/lua5.1
-- 'square' definition
local square = {}
square.__index = square --this makes our class work
function square.new(side)
local self = setmetatable({}, square)
-- internal processing for our class instantiation
self.side = side
-- end of internal processing
return self
end
function square.getsideugly(self)
return self.side
end
function square:getside()
return self.side
end
function square:getarea()
return self.side * self.side
end
-- Main:
local mySquare = square.new(5)
print( "square side: " .. mySquare.getsideugly(mySquare) )
print( "square side: " .. mySquare:getsideugly() )
print( "square side: " .. mySquare:getside() )
print( "square area: " .. mySquare:getarea() )
When you run it, you will get:
$ ./class.lua
square side: 5
square side: 5
square side: 5
square area: 25
Breaking It Down
local square = {}
square.__index = square --this makes our class work
To start off we set a table to be used for our square class. We use a metamethod to set the __index of our square table. To keep it short, we’re basically telling this that anything made with the square table inherits the square class’s definitions. This lets us define our functions and actually build a class. This is a very simplified view and there is a lot more to this, but for now, just use it. If you really want to know more right now, see this.
We kind of glossed over the metamethod piece for the time being, but let’s spend a little longer on the constructor. Most languages require a new keyword for a constructor, but Lua really doesn’t care.
function square.new(side)
local self = setmetatable({}, square)
-- internal processing for our class instantiation
self.side = side
-- end of internal processing
return self
end
Here, we’re telling Lua that we have a function in our square class which is called new. We pass it a side scalar to work with for our definition.
local self = setmetatable({}, square)
This line sets us up for making a class. We make a variable called self which is our object. We then use setmetatable to set self to be an instance of our object. We process whatever we want with our object, then return self to return the object to be used.
We instantiate an instance of our object with the following:
local mySquare = square.new(5)
You don’t need it to be local, but it does help with scoping and garbage collection. We use the new definition in square to define our object and set the return to our local variable mySquare. Now we can start using our class.
If this doesn’t make that much sense, don’t worry about it. We’re using some really complicated concepts to do something which is normally pretty simple. We’re learning a common idiom which is based on some very complicated grammar and usage. Work to remember it and apply it now, and we’ll go over what makes it tick as we learn more about Lua.
The Ugly Duckling
function square.getsideugly(self)
return self.side
end
I talked about syntactic sugar earlier, and this function showcases why we want it.
If we look down below in the program, we see the following:
print( "square side: " .. mySquare.getsideugly(mySquare) )
print( "square side: " .. mySquare:getsideugly() )
These both give the exact same result, but the second is definitely much cleaner than the other. In Lua, the difference between “.” and “:” is the implication. mySquare.getsideugly(mySquare) tells it to run the function as written and we explicitly pass our class so that our function knows what to do with it. The colon tells it to silently pass this without us having to care.
If we look at the other getside function, we see the following:
function square:getside()
return self.side
end
We didn’t have to pass “self” to it. We have implied it with the colon.
print( "square side: " .. mySquare:getside() )
Doesn’t that look much nicer?
Getting Closure
class2.lua:
#!/usr/bin/lua5.1
-- 'square' definition
local square = {}
square.new = function(myside)
local self = {}
-- internal processing for our class instantiation
side = myside
self.getside = function() return side end
self.getarea = function() return side * side end
-- end of internal processing
return self
end
local mySquare = square.new(5)
mySquare.side = 100 -- this doesn't do anything!
print( "square side: " .. mySquare:getside() )
print( "square area: " .. mySquare:getarea() )
Notice how we don’t use any metatables or anything in this method. This method is arguably cleaner, but isn’t really taught from the get-go. Functions are first-class in Lua so this method works, but it can be a bit weird for people from other languages. There are pros and cons, but the important thing right now is to just try them out and work with them. One thing to note is that this method allows private variables. Try setting the side in the class with the table method and see what happens!