Pseudo-OOP Syntax and Scope

Lua supports purely procedural programming as well as OOP-like concepts. This page explains how the self concept works in Lua. See Learning Lua for more resources.

Automatic Receiver Passing

Suppose you're using a programming language that isn't really object-oriented. (That's Lua.) Suppose you don't have "objects", but instead you have "tables"; all they do is associate keys with values, in a nice convenient bundle. (Hint: also Lua.)

Suppose you are working on a program that describes people. You want to bundle all the information about a person into a single, logical place. We'll use a table for that.

function makePerson( inFirstName, inLastName, inAge )
   -- Create a table associating the values with keys
   local thePersonTable = {
      firstName = inFirstName,
      lastName  = inLastName,
      age       = inAge
   }

   return thePersonTable
end

local theGK = makePerson( 'Gavin', 'Kistner', 33 )

Now, suppose you create a couple functions that work on person tables:

function getFullName( inTable )
   return inTable.firstName .. " " .. inTable.lastName
end

function makeOlder( inTable, inYearsOlder )
   inTable.age = inTable.age + inYearsOlder
end

print( getFullName( theGK ) )
--> "Gavin Kistner"

makeOlder( theGK, 2 )
print( theGK.age )
--> 35

By passing in a table to a function, you are essentially giving it a 'scope' to work on.

Now, you say to yourself, "Hey, I hate having a bunch of global functions. What if I also wanted a makeOlder function that worked on bacteria and took microseconds instead of years?"

You decide instead to wrap all the person-related functions into their own table. A library, if you will.

-- First, an empty table
PersonLibrary = { }

-- Now add some functions to it
PersonLibrary.getFullName = function( inTable )
   return inTable.firstName .. " " .. inTable.lastName
end

PersonLibrary.makeOlder = function( inTable, inYearsOlder )
   inTable.age = inTable.age + inYearsOlder
end

print( PersonLibrary.getFullName( theGK ) )
--> "Gavin Kistner"

That's nice and all, but it's getting sort of verbose. So you decide to use the sweet __index feature of Lua that lets one table use the values from another. That way, theGK will magically appear to have all the functions defined in the PersonLibrary.

-- If theGK doesn't have a value for a particular key, ask PersonLibrary for it
setmetatable( theGK, { __index = PersonLibrary } )

print( theGK.getFullName )
--> function: 0032CF20

print( theGK.getFullName( theGK ) )
--> "Gavin Kistner"

theGK.makeOlder( theGK, 1 )

Sweet! That's starting to look like object-oriented code! Except...it seems stupid to have to write "theGK" twice in each call. Wouldn't it be nice if you could ask the programming language to type that for you?

In Lua, if you have a function as a property of a table, and you use a colon (:) instead of a period (.) to reference the function, it will automatically pass the table in as the first argument!

print( theGK:getFullName( ) )
--> "Gavin Kistner"

theGK:makeOlder( 1 )

Automatic 'self' Definition

Above, I showed one way to add a function as a value in a table:

PersonLibrary.getFullName = function( inTable )
   return inTable.firstName .. " " .. inTable.lastName
end

Lua also lets you write this (exact same code) another way:

function PersonLibrary.getFullName( inTable )
   return inTable.firstName .. " " .. inTable.lastName
end

When you're going to be calling functions and having a table magically passed in for you, it seems sort of annoying to have to write out "By the way, I'm going to receive a table as my first parameter, and I'll call it '___'." In JavaScript, you have a this keyword that refers to the current scope. Wouldn't it be nice to have something like that already set up for you?

Just like Lua uses the colon to conveniently call a function, it also uses the colon syntax to conveniently define a function.

function PersonLibrary:getFullName( )
   return self.firstName .. " " .. self.lastName
end

When you use the colon notation to define a function, all it does is say "Hey Lua, I was too lazy to actually write out that the first argument to the function will be named 'self'...do that for me, would you please?"

To be 100% clear (and redundant), the above is 100% identical to:

function PersonLibrary.getFullName( self )
   return self.firstName .. " " .. self.lastName
end

self is not a keyword. It's not magical. It's not managed by the language. It's just the name of the first argument passed to your function.

When used along with the colon notation for calling a function, it makes it look like self refers to the owning table, the scope calling the function:

function theGK:resetAge( )
   self.age = 0
end

theGK:resetAge( )

However, you can always choose to NOT use the colon notation, and pass in a different table as the 'scope' for the function to act upon:

local theBob = makePerson( "Bob", "Smith", 17 )
print( theBob.age )
--> 17

theGK.age = 33
theGK.resetAge( theBob )

print( theGK.age )
--> 33

print( theBob.age )
--> 0

Finally, you should note that the colon syntax for defining and calling functions works with additional arguments. The 'special' automatic argument is always the first in the list, before all others.

-- Identical definitions
function someTable:myFunction( a, b, c ) end
function someTable.myFunction( self, a, b, c ) end

-- Identical calls
someTable:myFunction( 1, 2, 3 )
someTable.myFunction( someTable, 1, 2, 3 )