Basic Concepts

Creating objects in Lua is very simple and clean. Take for example the code bellow that creates an object implementing a stack:

Stack = { top = 0 }
function Stack:push(value)
  local pos = self.top+1
  self[pos] = value
  self.top = pos
end
function Stack:pop()
  local pos = self.top
  if pos > 0 then
    local value = self[pos]
    self[pos] = nil
    self.top = pos-1
    return value
  end
end
.
.
.
Stack:push("Hello")
print(Stack:pop())

However, if we want to create more than one stack, we will need a factory of stacks. There are different ways to create a factory in Lua. For example, you could create a function that uses the code above to create and return a new stack. A more usual solution however is to create objects that borrow the implementation from the original stack object, which works as a prototype or class for the new objects. LOOP modules are designed to aid the later approach.

Cloning Objects

LOOP provides a module for cloning objects called loop.proto. This module basically provides the clone function. This function receives the object that will provide the members (methods and attributes) and an optional table that will become the cloned object. The clone function is equivalent to:

function clone(proto, clone)
  return setmetatable(clone or {}, {__index=proto})
end

Cloning an object with clone is different from copying it (see copy) because the cloned object does not have a copy the members of the original object. Instead, the cloned object references the original object and consults it to get its members whenever necessary. So, if the original object changes, this change will reflect on its clones. As an example we could make our stack object a prototype and clone it to create more stacks, like in the example below:

oo = require "loop.proto"
otherStack = oo.clone(Stack)
otherStack:push("Hello")
print(otherStack:pop()) --> Hello

It is worth noticing that when we set a field in the clone, this field is not set in the prototype. So when we call myStack:push("Hello") the line self.top = pos sets a value for attribute top in the clone, not the prototype. This way, next time someone consults the value of top in the cloned object, it will not consult the prototype to get its value. The clone now have a value for top of its own.

One problem with cloning is that we have to avoid changing the prototype in a way that breaks the behavior of the clones. For example, suppose we call an operation on the prototype, like Stack:push(). This call would change the value of attribute top of the prototype to 1. All clones without a value for top would automatically inherit the value 1 from the prototype. This way, all such clones would appear as if they have the same values of the prototype. This might not be what we want. For this reason, a better approach in many scenarios is to treat the prototype object in a special way, not as an ordinary object, but exclusively as a template or class.

Creating Classes

LOOP provides different modules that support OOP based on the concept of classes. A LOOP class is basicaly a metatable. Moreover the instances of a class are tables that have the class as their metatable.

LOOP classes have three minor extensions in relation to ordinary metatables. First, the default value of metamethod __index is the class itself. So all fields of the class are shared with all its instances. In fact the class works as a prototype for all its instances. Secondly, a class can be used as a function to create instances of that class thus working as a factory. Finally, a class can optionally define a special metamethod called __new to redefine how instances of that class are created. Class-based moduels provides function class to create classes, which is equivalent to:

function new(class, ...)
  local new = class.__new
  if new ~= nil then
    return new(class, ...)
  end
  return setmetatable(... or {}, class)
end
function class(metatable)
  if metatable == nil then
    metatable = {}
  end
  if metatable.__index == nil then
    metatable.__index = metatable
  end
  return setmetatable(metatable, {__call=new})
end

By default, classes are like prototypes but, unlike prototypes, classes can redefine some behavior of its instances through metamethods. Moreover, since the metamethods defined in the class only applies to the instances, the class itself might not behave like its instances. For this reason, classes usually are used only as a template or mold of a group of object and not as working objects. As an example, we could rewrite our stack implementation using a class, like in the example below:

oo = require "loop.base"

StackClass = oo.class{ top = 0 }
function StackClass:push(value)
  local pos = self.top+1
  self[pos] = value
  self.top = pos
end
function StackClass:pop()
  local pos = self.top
  if pos > 0 then
    local value = self[pos]
    self[pos] = nil
    self.top = pos-1
    return value
  end
end
function StackClass:__tostring()
  local strings = {}
  for i, value in ipairs(self) do
    strings[i] = tostring(value)
  end
  return "{"..table.concat(strings, ",").."}"
end
.
.
.
myStack = StackClass()
myStack:push("Bottom")
myStack:push("Middle")
myStack:push("Top")
print(myStack)       --> {Bottom,Middle,Top}
print(myStack:pop()) --> Top
print(myStack:pop()) --> Middle
print(myStack:pop()) --> Bottom

The __new Metamethod

By default, when a class is used as a factory to create an instance of that class, it takes the first argument and turns it into an instance of the class. This allows for an easy way to initialize the new instance attributes when necessary. For example, we could create a stack with three strings inside it by providing a table with proper contents, like in the example below:

myStack = StackClass{ "Bottom", "Middle", "Top", top = 3 }
print(myStack:pop()) --> Top
print(myStack:pop()) --> Middle
print(myStack:pop()) --> Bottom

However, the way instances are created can be redefined with a metamethod defined by field __new of the class. This metamethod is called to create instances of a class and receives the class and all the values provided to the factory. For example, suppose that we want to change the class StackClass so it can be instantiated from a sequence of values instead of a table containing its internal values. Then we can use the code provided below:

oo = require "loop.base"

StackClass = oo.class{ top = 0 }
function StackClass:__new(...)
  local count = select("#", ...)
  return oo.rawnew(self, { top = count, ... })
end
.
.
.
myStack = StackClass("Bottom", "Middle", "Top")
print(myStack:pop()) --> Top
print(myStack:pop()) --> Middle
print(myStack:pop()) --> Bottom

The function rawnew used in the code above turns a table into an instance of a class without calling the __new metamethod. One common use for the __new metamethod is to initialize a table so it can be turned into an instance of a class. For example, suppose we want to store the elements of our stack in a separate table stored in a field called elements. This way, we would have to create a new storage table for each instance of the stack class. We could use the __new metamethod to create such table at each instantiation, like in the example below:

oo = require "loop.base"

StackClass = oo.class()
function StackClass:__new(newStack)
  newStack = newStack or {}
  if newStack.elements == nil then
    newStack.elements = {}
  end
  newStack.top = #newStack.elements
  return oo.rawnew(self, newStack)
end
function StackClass:push(value)
  local pos = self.top+1
  self.elements[pos] = value
  self.top = pos
end
function StackClass:pop()
  local pos = self.top
  if pos > 0 then
    local value = self.elements[pos]
    self.elements[pos] = nil
    self.top = pos-1
    return value
  end
end

Library Organization

LOOP is organized as a set of modules. These module are designed to allow the programmer to load only the code that provides the functionality he needs. For most applications only a few of these modules are actualy required. We can separate LOOP modules in tree categories, described below:

Basic Modules

The first group of modules are designed to provide support for different styles of OOP in Lua. To use prototyping, you might requilre only the loop.proto module. However, to use classes you might requilre one of the class-based modules listed below. These class-based modules are organized as incremental modules that provides the same functionality of the previous one but introducing some new funcionality. This way, your application can load only the module that provides the funcionality it requires.

Class-based Module Introduced feature
loop.base The __new metamethod.
loop.simple Simple inheritance.
loop.multiple Multiple inheritance.
loop.cached Metamethod inheritance.
loop.scoped Member access control.

Complementary Modules

The second group contains modules with auxiliary functions that complement the functionality of the basic modules but are not necessary in most applications.

Utility Modules

Finally, LOOP provides the loop.table module with some basic operations on tables.

Copyright (C) 2004-2018 Renato Maia

This project was originally developed in Tecgraf at PUC-Rio.