Learn Lua from JavaScript, part 3: Object-oriented behavior

JavaScript’s prototype-based classes compared to a Lua class pattern.

By Tyler Neylon
June 21, 2016
Moon rocks Moon rocks (source: dave_7 via Flickr (CC BY 2.0))

This post explains how Lua enables object-oriented behavior by implementing a small number of low-level language features. The key mechanic is the overloading of Lua’s key-lookup operator, which may be seen as similar to JavaScript’s prototypes. Since this overloading is the foundation, this post begins by covering operator overloading. After that I’ll take a look at JavaScript’s conceptual model in order to compare it to a class-like pattern in Lua, finishing up with an example of subclassing in Lua.

Operator overloading

Every Lua table can potentially have a metatable. A metatable is simply a Lua table that provides extra functionality for the original table, such as operator overloading. For example, two tables can be added together if their metatable has the special key __add and the value for this key is a function that accepts the two tables as input. In that case, the expression table1 + table2 acts the same as the function call aMetatable.__add(table1, table2), where aMetatable is the metatable of table1 and table2.

Learn faster. Dig deeper. See farther.

Join the O'Reilly online learning platform. Get a free trial today and find answers on the fly, or master something new and useful.

Learn more

The example below shows how we can use Lua tables to represent rational numbers that can be added together. It uses the setmetatable function, which sets its 2nd argument as the metatable of its 1st argument, and then returns the 1st argument:

-- Lua

-- The fraction a/b will be represented by a table with
-- keys 'a' and 'b' holding the numerator and denominator.

fractionMetatable = {}

fractionMetatable.__add = function (f1, f2)
  local a, b = f1.a * f2.b + f1.b * f2.a, f1.b * f2.b
  -- The gcd function, defined in the previous post, returns
  -- the greatest common divisor of a and b.
  local d = gcd(a, b)
  return setmetatable({a = a / d, b = b / d}, fractionMetatable)
end

fractionMetatable.__tostring = function (f)
  return f.a .. '/' .. f.b  -- The token `..` indicates string concatenation.
end

frac1 = setmetatable({a = 1, b = 2}, fractionMetatable)  -- 1/2
frac2 = setmetatable({a = 1, b = 6}, fractionMetatable)  -- 1/6

-- The following call implicitly calls __add and then __tostring.
print(frac1 + frac2)  --> 2/3

Almost all of Lua’s operators can be overloaded by implementing a custom function stored in a metatable. The official list of overloadable operators, along with their corresponding special keys, can be found in Lua’s reference manual.Methods that overload operators in Lua are called metamethods.

Lua’s metatables may remind you of JavaScript’s prototypes, but they’re not quite the same. The properties of a JavaScript prototype can be seen from the inheriting object, whereas in Lua, data stored in a metatable is not visible from the base table by default. This example illustrates the difference:

// JavaScript

a = {keyOfA: 'valueOfA'}
b = Object.create(a)        // Now a is the prototype of b.
b.keyOfB = 'valueOfB'
console.log(b.keyOfA)       // Prints out 'valueOfA'.

-- Lua

a = {keyOfA = 'valueOfA'}
b = setmetatable({}, a)     -- Now a is the metatable of b.
b.keyOfB = 'valueOfB'
print(b.keyOfA)             -- Prints out 'nil'; the lookup has failed.

Class-like behavior

Lua and JavaScript are both amenable to prototype-based programming, in which class interfaces are defined using the same language type as the instances of the class itself. I consider JavaScript’s class mechanics to be non-obvious, so I’ll review those first, and then dive into the analogous workings of Lua.

Classes in JavaScript

Here’s the traditional way of defining a class in JavaScript:

// JavaScript, pre-ES6

var Dog = function(sound) {
  this.sound = sound;
}

Dog.prototype.sayHi = function() {
  console.log(this.sound + '!');
}

Let’s see a usage example for this class, and then review how it works. The Dog class can be instantiated with JavaScript’s new operator:

// JavaScript

var rex = new Dog('woof');
rex.sayHi();  // Prints 'woof!'.

JavaScript’s new operator begins by creating a new object whose prototype is the same as Dog’s prototype; then it calls the Dog function with this new object set as the value of this. Finally, the new object is given as the return value from the new operator. The end result is a new object called rex with the key/value pair { sound: 'woof' } and the same prototype as Dog.

As shown in the diagram below, both Dog and rex share the same prototype object. This is how JavaScript class instances are able to call methods assigned to their constructor’s prototype.

When rex.sayHi() is called, the key 'sayHi' is found to be missing on rex itself, but the key is found to exist in rex’s prototype; this function in the prototype is called. Because sayHi was called from the rex object—basically, because rex was the prefix of the string rex.sayHi() used to make the call—the sayHi function body is executed with this = rex. (If you’d like to understand JavaScript’s prototype-based object model in more detail, this Mozilla developer network page is a good place to start.)

ES6 introduced some fancy new notation for defining classes:

// JavaScript, ES6

class Dog {
  constructor(sound) {
    this.sound = sound;
  }

  sayHi() {
    console.log(this.sound);
  }
}

That code is semantically identical to the traditional way of defining a class that was shown earlier.

Classes in Lua

We’ve seen that JavaScript classes are based on prototypes, which are objects themselves. Lua is similar in that both a class interface and a class instance are viewed by the language as having the same type—specifically, both are tables.

The key mechanism used to connect class instances to interfaces is the overloading of the __index operator via metatables. This operator is used whenever an index is dereferenced on a table. For example, the line

a = myTable.myKey

is dereferencing the key myKey on the table myTable. If the __index operator is overloaded and if myTable does not directly have a myKey key, then the __index overloading function is called, and can effectively provide fallback values for keys. This is analogous to the way rex.sayHi() worked in JavaScript even though rex did not directly have a sayHi key.

Let’s take a look at a typical class definition and usage in Lua, and then examine why it works:

-- Lua

-- Define the Dog class.

Dog = {}

function Dog:new(sound)
  local newDog = {sound = sound}
  self.__index = self
  return setmetatable(newDog, self)
end

function Dog:sayHi()
  print(self.sound .. '!')
end

-- Use the Dog class.

kepler = Dog:new('rarf')
kepler:sayHi()  -- Prints 'rarf!'.

The Dog table is a standard Lua table. In the definition of the constructor, the colon used in the syntax Dog:new(sound) is syntactic sugar for Dog.new(self, sound). In other words, the colon-based function definition inserts a first parameter called self. When the new function is called, a colon is used again, as in Dog:new('rarf'). Again, the use of the colon is syntactic sugar, this time for Dog.new(Dog, 'rarf'). In other words, a colon-based function call inserts the object immediately before the colon as the first argument. The special variable name self in Lua thus plays the role that this plays in JavaScript. Whereas JavaScript binds this implicitly, once you understand the syntactic sugar behind Lua’s colon notation, it expresses a more explicit form of binding values to self.

The constructor Dog:new performs essentially the same actions as JavaScript’s new operator. Specifically:

  1. It creates a new table, internally called newDog, and assigns the value of sound to the key ‘sound‘.
  2. It sets self.__index = self. In our example, self = Dog, and Dog will be the metatable of the new instance. This line makes sure that any failed key lookups on the new instance fall back to key lookups on Dog itself.
  3. It sets newDog’s metatable to self, which is the same as Dog, and returns the new instance.

The resulting table relationship is shown in the diagram below. Dog is the metatable of the instance kepler. Because the key ‘__index‘ in Dog has the value Dog, this means that any keys not found in kepler will be looked for in Dog.

When the function kepler:sayHi() is called, the colon syntax is effectively the same as kepler.sayHi(kepler). There is no sayHi key directly in the kepler table, but kepler has a metatable with an ‘__index‘ key, so that is used to find the sayHi key in Dog. The end result is the same as the function call Dog.sayHi(kepler), where the parameter kepler is assigned to the variable self inside that function.

I’m not going to dive into the full details of class inheritance, but I’ll demonstrate that inheritance code in Lua can be simple. This example defines and uses a subclass of Dog called BarkyDog for which the sayHi method is different, although the constructor and the sound field are the same:

-- Define BarkyDog which inherits behavior from the Dog class.

BarkyDog = Dog:new()

function BarkyDog:sayHi()
  print(self.sound .. ', ' .. self.sound .. '!')
end

-- Use the BarkyDog subclass.

benedict = BarkyDog:new('woof')
benedict:sayHi()  -- Prints 'woof, woof!'

It may seem like magic that this works. Technically, nothing is happening here that hasn’t been explained, but it took me some thinking to fully grok this code pattern. The main tools here are the flexibility of self in the Dog constructor, along with Lua’s ability to chain lookups so that any key reference on benedict first looks in benedict itself, then in BarkyDog, and finally in Dog. This pattern is explained here in more detail.

Those are the fundamental mechanics of Lua classes. At first glance, it may seem more involved than its JavaScript counterpart. The design of Lua consistently employs smaller individual building blocks; for example, ES6 has 55% more keywords than Lua 5.3, despite offering a similar set of features. The result of finer-grained language design is greater flexibility and transparency into how the system is working. In fact, there are numerous ways to set up your object-oriented interfaces in Lua, and what I’ve covered in this post is simply one common approach.

Where to go from here

This post, along with the first and second posts in this series, has covered the essentials of Lua—but there’s much more to the language. Lua has a small but versatile set of standard libraries with operations on strings, tables, and access to files. Lua has a C API that supports extending the language with Lua-callable functions implemented in C, C++, or Objective-C. The C API also makes it easy to call Lua scripts from one of these C-family languages. LuaRocks is a package manager that can help you easily find and install Lua modules that provide a wide variety of functionality not built into the language itself.

If you’re interested in taking the next step toward Lua mastery, I humbly recommend my own quick-reference style post Learn Lua in 15 Minutes. If you enjoy long-form content with greater depth, I highly recommend Roberto Ierusalimschy’s Programming in Lua.

Post topics: Software Engineering
Share: