Troubleshooters.Com and Code Corner Present

Litt's Lua Laboratory:
Lua OOP
(With Snippets)

Copyright (C) 2011 by Steve Litt


Debug like a Ninja

Contents

Introduction

Does Lua have OOP?

I guarantee you no matter whether you answer that question "yes" or "no", you will get yelled at by Lua devotees.

"No!", exclaim some purists. "Lua isn't locked into a single paradigm like object orientation. Lua has no syntax for classes or objects." they continue. The purists then go on to say Lua gives you ways that *you* can construct things that act like OOP in other languages.

The purists are right.

"Yes!" exclaim some pragmatists. "Every major OOP thing you can do in other languages you can do in Lua. Encapsulation, polymorphism, inheritance, Lua does it all. The only difference is in Lua you can do it in many different ways, but you can do object oriented programming in Lua, so Lua is OOP." The pragmatists then go on to say Lua gives you ways that *you* can construct things that act like OOP in other languages.

The pragmatists are right.

And then there are the guys in the middle to whom whether or not Lua is OOP is just a marketing label anyway. We stay out of that particular argument. We just use Lua to do the OOP idioms we've done in other languages, and probably do it easier. And because there's no specific Lua OOP syntax, we construct our own objects out of tables, closures and metatables. And because we so construct them, we truly understand what's in them, kind of like when you build a computer from parts you understand what's in it.

And you know what that understanding means? It means you'll never forget the syntax for your classes and objects, because you constructed them from basic Lua constructs.

There are many ways to construct and use objects in Lua. Many are too advanced for Litt's Lua Laboratory, at least on this page. They use metatables, and we haven't covered those yet.

So on this page I'll show you one method using closures and tables but not metatables. The method I'll show you has encapsulation but not polymorphism nor inheritance. This page's method has "private" properties accessed by setters, getters and other "methods". I put quotes around "private" and "methods" because these are other languages' terms for these things, not Lua's.

So kick back, relax, and learn how we do OOP or Pseudo-OOP in Lua...

Dots and Colons

On this page you'll see OOP methods run like this:
objectname.methodname(args)
And yet in a lot of other code you'll see it like this:
objectname:methodname(args)
Notice one has a dot and one has a colon. Why the difference?

It becomes obvious when you consider that objectname:methodname(args)
is just syntactic sugar for objectname.methodname(objectname, args). Most people write Lua classes and objects such that you need to actually pass the object as the first argument to the method. Since it's both time consuming and amateurish to write the object name twice, the colon syntax was developed so you wouldn't have to.

If you write code like I did, using closures not requiring the object name as an argument, be sure to argument your code so other programmers know why you used a dot instead of a colon.

Hello World: The Point Class

A point is a thing with an x measurement and a y measurement. You need to set each and get each. In GUI applications points have methods to draw and undraw themselves, but what we're doing isn't GUI. We'll make a "method" called show() that simply prints out the x and y values. The "constructor" sets x and y, or if either doesn't exist it's 0. Here's a review of the methods for the Point class we'll be developing:
As you look at this code, remember the class is made with a closure, and that functions inside of other functions can see local variables of the outer function. The outer function is Point.new(). One of that outer function's local variable is a table called self, which is what is eventually returned by Point.new(). That returned local variable, self, is the object returned by the class's constructor.

Here's the code:
#!/usr/bin/lua 

Point = {} -- THE CLASS

Point.new = function(x, y)

-- #PRIVATE VARIABLES
local self = {} -- Object to return
x = x or 0 -- Default if nil
y = y or 0 -- Default if nil

-- #GETTERS
self.getx = function() return x end
self.gety = function() return y end

-- #SETTERS
self.setx = function(arg) x = arg end
self.sety = function(arg) y = arg end

-- #OTHER METHODS
self.show = function(msg)
print(string.format("%s (x,y)=(%d,%d)",
msg, x, y))
end
return self --VERY IMPORTANT, RETURN ALL THE METHODS!
end

mypoint = Point.new(2,5)
mypoint.show("Before tweaking:")

--NOW DOUBLE BOTH X AND Y
mypoint.setx(2 * mypoint.getx())
mypoint.sety(2 * mypoint.gety())

mypoint.show("After doubling:")
The preceding program produces the following output:
slitt@mydesk:~$ ./test.lua
Before tweaking: (x,y)=(2,5)
After doubling: (x,y)=(4,10)
slitt@mydesk:~$
Take five minutes to examine and run the code. Don't examine it for understanding -- we're going to discuss it line by line. Examine it for its clean simplicity. Say goodbye to Perl's silly bless command. I've been doing Perl since 1995 and I still don't understand the bless command. Say goodbye to the syntactical typing nightmare of C++.

Maybe you consider type checking a good thing. OK fine, use assert() functions against the types of x and y in the setters and the constructor. No big deal. You'll probably want to do that if you're writing classes for other people to use. But personally, speaking for myself, I'm glad typechecking is optional, so if I'm writing something quick and dirty, and I know how to use it, typechecking doesn't get in my way.

Now let's examine the code line by line...

Line by Line Analysis


CODE
EXPLANATION
Point = {}       -- THE CLASS
This is a table that will contain the new() function so that it can be called Point.new(). and resemble the class/object relationship in other languages. Actually, if you called the constructor Pointnew() instead of Point.new(), you wouldn't even need this table. But we use it to get that familiar OOP look.
Point.new = function(x, y)
Constructor function, just what you'd expect. You pass in x and y to give the point a location. The purpose of a constructor is to pass back an object, and that's just what Point.new() does. Read on...
local self = {}       -- Object to return
      
This is a little tricky. In OOP speak this is the object that will be returned by the constructor. But from a Lua perspective this is just a table to contain all the functions inside Point.new(). The functions and NOT the variables. You know why? The variables x and y are local variables of the outer function, Point.new()-- they are not in self. But the functions are in self, and since an inner function can see its outer function's local variables (this is the basis of closures), the functions inside self can retrieve and manipulate the local variables for Point.new().

So from a Lua point of view, self is a bag into which to put all the functions declared by Point.new().
x = x or 0            -- Default if nil
y = y or 0 -- Default if nil
There's a lot of Lua in these two simple lines. First of all, a widely used Lua idiom for defaulting a variable looks like this:
a = a or a_default
This is short circuit logic. If a exists then it's set to itself. But if it doesn't exist (perhaps no argument passed into the function), then it's set to a_default.

But even beyond this, remember that in Lua, a functions arguments function EXACTLY like local variables within that function. Therefore, x and y are visible and manipulable to all functions inside of Point.new().
self.getx = function() return x end
self.gety = function() return y end
Getter functions. Put inside table self. Remember that x and y are local to the outer enclosing function (because they're arguments of that function), and therefore are available to the inner function. This is how closures work.
self.setx = function(arg) x = arg end
self.sety = function(arg) y = arg end
Setter function. Put inside table self. Same explanation of x and y and closures.
self.show = function(msg)
print(string.format("%s (x,y)=(%d,%d)",
msg, x, y))
end
Pretty print's x and y with a message passed in as an argument. Put inside table self. Same explanation of x and y and closures.
return self
By the end of Point.new() , self is a bag of functions containing getx, gety, setx, sety and show. By passing this back, the calling program can execute all these functions. If you forget to return self then you'll get an error message something like this:
slitt@mydesk:~$ ./test.lua
/usr/bin/lua: ./test.lua:29: attempt to index global 'mypoint' (a nil value)
stack traceback:
./test.lua:29: in main chunk
[C]: ?
slitt@mydesk:~$
end
This ends Point.new(), the constructor.
-- BELOW HERE IS THE MAIN PGM

mypoint = Point.new(2,5)
OOP viewpoint: Use class Point to instantiate object mypoint.
Lua viewpoint: Use maker Point.new() to deliver a table of functions to variable mypoint, with state variables held by the closure enclosed by Point.new().
mypoint.show("Before tweaking:")
Pretty-print x and y with a message.
mypoint.setx(2 * mypoint.getx())
mypoint.sety(2 * mypoint.gety())
Set x to double the value returned by getx(). Same with y.
mypoint.show("After doubling:")
Pretty print x and y with message, proving that each has been doubled.

Review this entire article, especially the line by line table of explanations, until you understand just what happened here and why. See if you can appreciate the simplicity and logic of the way Lua has been used to implement what in other languages would be called OOP, and notice that in the main program the syntax looks like most other languages' OOP implementations.

One Class, Multiple Independent Objects

The preceding article instantiated only one object. You might be thinking that if you instantiate and then manipulate multiple objects, the objects would interfere with each other. They don't, as this article proves.

From the Point Class article, leave the Point class code as is, but replace its main routine with the following:
point1 = Point.new(1, 2)
point2 = Point.new(3, 4)
point1.show("Point 1 before tweaking:")
point2.show("Point 2 before tweaking:")
point1.setx(8)
point1.sety(9)
point1.show("Point 1 after setting point 1 to (8,9):")
point2.show("Point 2 after setting point 1 to (8,9):")
point2.setx(5)
point2.sety(6)
point1.show("Point 1 after setting point 2 to (5,6):")
point2.show("Point 2 after setting point 2 to (5,6):")
What was done was two points were instantiated and shown. Then point 1 was tweaked and the show statements should indicate that point 1 has changed but point 2 remains the same. Then point 2 was tweaked and the show statements should indicate that point 1 remains the same as it was after you tweaked it.

Now let's look at the output:
slitt@mydesk:~$ ./test.lua
Point 1 before tweaking: (x,y)=(1,2)
Point 2 before tweaking: (x,y)=(3,4)
Point 1 after setting point 1 to (8,9): (x,y)=(8,9)
Point 2 after setting point 1 to (8,9): (x,y)=(3,4)
Point 1 after setting point 2 to (5,6): (x,y)=(8,9)
Point 2 after setting point 2 to (5,6): (x,y)=(5,6)
slitt@mydesk:~$
Bang! Modifying one does nothing to the other. They're independent, just like objects should be.

About Class Variables

Some OOP implementations have "Class Variables", which are shared across all objects of a given class. Seems like asking for trouble to me, but if you really need it I'm sure there's a way to do it within Lua.

Example: Distance Between Points

We can find distance between two points with the addition of one more function. Call it distance_to(), which takes another Point object as an argument and returns a table with the following keys:
All the preceding are from the point of reference of the point running the function, not the point used as an argument. Therefore if the original point is (5,5) and the argument point is (0,0), then xto would be -5, yto would be -5, absdistance would be 5 * sqrt(2), and angle would be 225.

In this case typechecking is more important, because you want to make sure it's passed a real point and not something else. So what you'll do first is check that the argument is of type table (remember, the objects returned by the constructor functions on this page were tables. But beyond that, we'll check that the argument has elements called distance_to, new, getx, gety, setx and sety. If all those check out, it's very probably another point object.

So to the Point class in the Point Class article, add the following method:
	self.distance_to = function(pnt)
if type(pnt) == "table" and
pnt.getx and
pnt.gety and
pnt.setx and
pnt.sety and
pnt.distance_to then
else
io.stderr:write("ERROR: Argument to Point.distance_to() must be another Point.\n")
io.stderr:write("Aborting...\n\n")
os.exit(1)
end

local rtrn = {} -- Table to be returned
rtrn.xto = pnt.getx() - x
rtrn.yto = pnt.gety() - y
rtrn.absdistance = math.sqrt(rtrn.xto * rtrn.xto + rtrn.yto * rtrn.yto)
rtrn.angle = math.atan2(rtrn.yto, rtrn.xto) * 180/math.pi
return rtrn
end
The first if/else tests that the argument is truly a Point object. The next few statements build a table of quantities representing the distance from the current point to the argument Point, and then you return that table. Then write the following main-routine code to exercise the new method:
local point1 = Point.new(0, 0)
local point2 = Point.new(4, 4)
local d = point1.distance_to(point2)
print(string.format("d.xto = %f", d.xto))
print(string.format("d.yto = %f", d.yto))
print(string.format("d.absdistance = %f", d.absdistance))
print(string.format("d.angle = %f\n", d.angle))

local point1 = Point.new(0, 3)
local point2 = Point.new(4, 0)
local d = point1.distance_to(point2)
print(string.format("d.xto = %f", d.xto))
print(string.format("d.yto = %f", d.yto))
print(string.format("d.absdistance = %f", d.absdistance))
print(string.format("d.angle = %f\n", d.angle))
The preceding code puts point1 at the origin and point2 at (4,4), so it's obvious to get from point1 to point2 you'd need to advance +4 in each direction, that absolute distance  is 4*sqrt(2),  and the angle is 45 degrees. Then it runs again with point 1 at (0,3) and point 2 at (4, 0), obviously a 3-4-5 right triangle. Starting at point 1 you'd have to advance +4 to get to point 2, and you'd have to advance -3 to get to point 2. Since it's a 3-4-5 right triangle the absolute distance should be 5, and if you visualize the line going from (0,3) to (4,0) that line is somewhere between 0 and -45 degrees. Run the code and you get the following output:
slitt@mydesk:~$ ./test.lua
d.xto = 4.000000
d.yto = 4.000000
d.absdistance = 5.656854
d.angle = 45.000000

d.xto = 4.000000
d.yto = -3.000000
d.absdistance = 5.000000
d.angle = -36.869898

slitt@mydesk:~$
Precisely what you expected.

Preventing Direct Property Manipulation

The preceding several articles are how I write my Lua objects, and it works just fine for me. But there's a potential for mistakes because an applications programmer naive to the exact way you wrote your objects might think he could manipulate x by directly setting point1.x rather than using point1.setx(). Lua would go merrily along letting him do that, but if he used both, the results would be inconsistent because they manipulate two different variables. If you remember, the Point class from the last few articles returns a table of functions that can be used as an object. If an applications programmer sets point1.x, he simply adds a new element to the table, an element called x. As you remember, point1.setx() really sets a closure variable, not an element of a table. Two different things.

If you really, really really want to prevent all access to anything within point1 except its methods, you can lock it down with the following code inserted just before returning self in Point.new():
	--#### PREVENT READ AND WRITE ACCESS TO THE RETURNED TABLE
local mt = self

-- PREVENT WRITE ACCESS AND ABORT APPROPRIATELY
mt.__newindex = function(table, key, value)
local msg = string.format("%s %s %s %s %s",
"Attempt to illegally set Point object key",
tostring(key),
"to value",
tostring(value),
", aborting...\n\n"
)
io.stderr:write(msg)
os.exit(1)
end

-- PREVENT READ ACCESS AND ABORT APPROPRIATELY
mt.__index = function(table, key)
if type(key) ~= "function" then
io.stderr:write("Attempt to illegally read attribute " ..
tostring(key) .. " from Point object, aborting...\n\n")
os.exit(1)
end
end

-- WRITE NEW __index AND __newindex TO METATABLE
setmetatable(self, mt)
The preceding uses metatables, which I promised not to get into, and as far as I'm concerned it's over the top overkill. But if you for some reason want to program Lua with the same anal retentive encapsulation enforcement as C++, now you have a way to do it.

Discussion: Polymorphism and Inheritance

When Philippe Kahn of Borland Software taught me OOP in a video featuring himself, his flute and his car, he taught me that OOP had three properties:
The Point class described on this web page, especially if  augmented with the __index and __newindex mode in the Preventing Direct Property Manipulation article, enforces encapsulation about as far as a reasonable person would want to enforce it.

Polymorphism is where a function acts differently depending on what it's acting on. Due to Lua's loose type checking, a lot of the polymorphism you need in C++ or Java just to get around their ubiquitous type checking is unnecessary. The rest can be done with metatables and is beyond the scope of this web page.

Inheritance is where you define a new class as being just like an existing class except that it also has this and that and the other, where you define this, that and the other. Anything not defined as being a difference is run just like the old class (the base class). In Lua you can achieve this with metatables, but once again it's beyond the scope of this web page.

Discussion: Many Ways to Do OOP

You know it's funny. In C++ or Java there's basically one way to do OOP. It's built solidly into the language. It's cast in concrete.

Not so with Lua. Lua wasn't built with OOP in mind, it was built with atomic computer programming features such as closures, iterators, tables, metatables, tail recursion. The programmer can then easily use those atomic features to create OOP, along with whole bunches of other programming paradigms. The programmer can build OOP many different ways.

It's funny. My biggest disappointment with Perl was Larry Wall's "there are many ways" philosophy. And yet with Lua I accept that same philosophy. Except it's not really the same. Unlike Perl, Lua has a *very* small set of syntax. And a *very* consistent set of syntax. It's just that you can use that syntax to do all sorts of things.

For the person who believes to the bottom of his soul that OOP is the be all and end all of computer programming, Lua is probably not the right language. C++, Java, Ruby or Smalltalk would probably be better for such a person. But for the person who believes OOP is one tool of many powerful tools a programming language can and should offer, Lua's the choice.


 [ Troubleshooters.com| Code Corner | Email Steve Litt ]

Copyright (C) 2011 by Steve Litt --Legal