|Portada|Blog|Wiki|
In this entry I'll be talking about how to achieve OOP (polymorphism) in a
language only supporting prototypical inheritance, or more specifically in
lua.  We will be describing those terms here.

------------------------------------------------------------------------
1. INTRODUCTION

But first, let's start by defining a use case.  Suppose we have an in-memory
filesystem-like tree composed of File instances, where they can either be
Directory or RegularFile.

Diagram:
    image

Let's then begin by writing some traditional non-OOP code so that we can
familiarize with the syntax, starting by making two RegularFile instances
and a Directory:

  local foo = {name='foo.txt', content='hello world'}
  local bar = {name='bar.txt', content='good bye'}

  local dir = {name='stuff', children={foo, bar}}

Note that we just created a few tables with some keys, ie: name and content,
which we can display with:

  print('name:', foo.name)
  print('content:', bar.content)

At this point foo, bar and dir are simple tables, associations of keys and
their values.  If we need to know if one of those objects is a RegularFile or
a Directory we can for instance see if they have content or children defined.

As an example let's do a function to search for a specific file name returning
true if it's found or false if not.  Both RegularFile and Directory have names:

  function search(file, filename)
    if file.name == filename then
      -- this is the file we were looking for!
      return true
    end
    -- if the file is a directory we should search among their children:
    if file.children then
      -- with ipairs we can go through the children:
      for i, child in ipairs(file.children) do
        -- maybe child (or one of its children if applicable) has the filename
        -- we are looking for, so let's look inside it:
        if search(child, filename) then -- found, let's return:
          return true
        end
      end
    end
    -- not found:
    return false
  end

Now, how would we write this in a traditional OOP language? something like
this (this is pseudocode):

  abstract class File {
    String name;

    bool search(String filename) {
      return name.equals(filename);
    }
  }

  class RegularFile extends File {
    String content;
  }

  class Directory extends File {
    Array<File> children;

    bool search(String filename) {
      // we reuse the code for checking against the file's name by
      // placing that code in the File class, and calling it from here:
      if (super.search(filename)) {
        return true;
      }
      for (File child : children) {
        if (child.search(filename)) return true;
      }
      return false;
    }
  }

So, if this code is not obvious to you, what's going on here is that we are
associating the functions to the classes, so that once we call:
  myfile.search("xyz");
we are going to call a different implementation of that search method
depending on myfile's dynamic type (the real type of the object when the code
is running).  If myfile was a Directory then the function at class Directory
would be called; and since RegularFile does not have a specific implementation,
the one from its parent class (File) would be used for them.

Polymorphism via inheritance in OOP is exactly what we've shown here, to be
able to call different method implementations depending on the dynamic type.
Other programming languages use other techniques to achieve the same, like
"type classes" in haskell.

While most OOP languages have specific syntax for classes hardcoded in them,
that's not the case in lua, so when we want to write OOP code we need a deep
understanding of what's really going on, in order to avoid common pitfalls,
especially when doing bugfixing.

Then in order to do that, we must understand the following concepts:

  * the colon syntax sugar for method calling: object:method(...),
  * metatables, and
  * the __index metamethod.

------------------------------------------------------------------------
2. COLON SYNTAX SUGAR

Doing a call with obj:foo('xyz', 42) is equivalent to: obj.foo(obj, 'xyz', 42),
that is, a reference to the object whose method is being called is passed as
an initial first parameter.  This is useful because it allows the method
implementation to know on which specific instance is being called without
requiring us to repeat it.

This syntax sugar allows us to write the very common pattern:
  local res
  do
    local tmp = foo:m1('xyz')
    res = tmp:m2(42)
  end

which is used a lot in patterns like the builder pattern, as the following:
  local res = foo:m1('xyz'):m2(42)

Finally, the colon syntax sugar can be used when defining a function to store
the first parameter into a hidden argument called self,
  function foo:m1(s)
    do_something(self, s)
  end

is the same as:
  function foo.m1(self, s)
    do_something(self, s)
  end

With this in mind remember that when doing x:y(...) you're evaluating x once,
so no, (x()):y(...) is NOT the same as x().y(x(), ...)! as the intermediate
result of x() gets "stored" into a hidden temporary variable.

Bonus Sugar:  You can write the function name after the function keyword to
obtain the same behavior as assigning the function to the variable:
  function x.y.z()...end  ---->   x.y.z = function() ... end
  function x:z(w)...end   ---->   x.z = function(self, w)...end.

------------------------------------------------------------------------
3. METATABLES

The best way to understand metatables is to try to write the previous OOP code
without using them, it will not look nice:

local function new_file(name)
  return {
    name = name,
    search = function(self, filename)
      return self.name == filename
    end,
  }
end

function new_regularfile(name, content)
  local file = new_file(name)

  file.content = content
  return file
end

function new_directory(name, children)
  local file = new_file(name)
  local super_search = file.search

  file.children = children
  file.search = function(self, filename)
    if super_search(self, filename) then
      return true
    end
    for i, child in ipairs(self.children) do
      if child:search(filename) then
        return true
      end
    end
    return false
  end
  return file
end

-- after this file:search('foobar') works perfectly

This means that for each object we create we have to populate its table with
all its methods.  We can avoid this by using metatables.

So... What's a metatable? you might ask.  It's a table we can associate to our
objects so that we can override their behavior by defining metamethods inside
it.

Let's redefine the '+' operator:

  local complexmetatable = {}

  local new_complex(real, imag)
    local res = {real = real, imag = imag or 0}
    setmetatable(res, complexmetatable)
    return res

    -- all these 3 lines could have been summarized into:
    -- return setmetatable({real = real, imag = imag or 0}, complexmetatable)
  end

  complexmetatable.__add = function(z1, z2)
    return new_complex(z1.real + z2.real, z1.imag + z2.imag)
  end

  local z = new_complex(1) + new_complex(1, -1)

  -- we can also override tostring for them:
  function complexmetatable:__tostring()
    if self.imag == 0 then
      return tostring(self.real)
    end

    local imagsign = self.imag < 0 and '-' or '+'
    local imagabs = math.abs(self.imag)

    if imagabs == 1 then
      return string.format('%s%si', self.real, imagsign)
    else
      return string.format('%s%s%si', self.real, imagsign, imagabs)
    end
  end

  print(z)  -- now works

There are many metamethods if you ever feel like you need to override them,
and you could even define your owns:

  https://www.lua.org/manual/5.1/manual.html#2.8

------------------------------------------------------------------------
4. __INDEX METAMETHOD

This is by far the most useful metamethod, but the documentation even though
as a reference manual should precisely define its behavior, it fails to do
justice to the impressively powerful patterns it allows.

In lua x.y is syntactic sugar for x['y'], and that's the behavior that the
__index metamethod overrides:

When x[y] is evaluated:
  1. if x does not have a metatable or its metatable does not have the __index
     metamethod defined, then the y key is searched directly in the table x
     and returned.
  2. if getmetatable(x).__index is a function,
     then the result of getmetatable(x).__index(x, y) is used.
  3. otherwise getmetatable(x).__index[y] is used.

Or in a more understandable version:

  function gettable_event(x, y)
    local mt = getmetatable(x)

    if not mt or not mt.__index then
      return rawget(x, y)
    elseif type(mt.__index) == 'function' then
      return mt.__index(x, y)
    else
      return mt.__index[y]  -- note that this is a recursion!
    end
  end

A very interesting use case is when __index point to a table, as it can be
used to define proxies, even overriding a set of keys!

  local back = {a = 1, b = 2}
  local mt = {__index = back}
  local front = setmetatable({b = 200, c = 300}, mt)

  print(front.a, front.b, front.c)   --->   1   200   300

This could generate an infinite loop, however loops are detected when querying.
  local x = {}
  setmetatable(x, {__index = x})
  print(x.foo)  -- this will throw an error because: x.foo -> x['foo'] ->
                --     -> (getmetatable(x).__index)['foo'] -> x['foo']

But wait, why are we talking about __index here? isn't this blog post supposed
to talk about OOP? And to that I can answer you this:

  MyClass = {}

  function MyClass:new(name)
    return setmetatable({name=name}, {__index=self})
  end

  function MyClass:hello()
    print('Hello ' .. self.name .. '!')
  end

  MyClass:new('Francisco'):hello()  -->  Hello Francisco!

But wait, what is going on here?  Let's desugarize and rename variables!

  function MyClass.new(cls, name)
    local mt = {__index = cls}
    local instance = {name = name}
    setmetatable(instance, mt)
    return instance
  end

  function MyClass.hello(instance)
    print('Hello ' .. instance.name .. '!')
  end

  local mytmpinstance = MyClass.new(MyClass, 'Francisco')
  mytmpinstance.hello(mytmpinstance)

(mind_blown.gif)

You may wonder why should you pass the class as the first argument to the new
function! Well, there's a very strong reason we'll see here:

  MySubClass = setmetatable({}, {__index = MyClass})

  function MySubClass:hello()
    -- if we wanted to call the supercalss we can do it by using either:
    --   MyClass.hello(self)
    --   getmetatable(MySubClass).__index.hello(self)
    print('Hola ' .. self.name .. '!')
  end

  MySubClass:new('Francisco'):hello()

Voilà! purely OOP code with tons of support for inheritance!

Final notes:

Some people are a bit stingy and like to reuse the metatables instead of
creating a new one for every single object like we were doing in the example
above.  How would you do that? by using this:

  MyClass = {}
  MyClass.__index = MyClass  -- note that there's not an infinite loop here
    -- because we are not storing MyClass in MyClass' metatable.__index, but
    -- in MyClass.__index instead.

  function MyClass:new(name)
    return setmetatable({name=name}, self)
  end

  function MyClass:method() return 1 end

  MySubClass = setmetatable({}, MyClass)
  MySubClass.__index = MySubClass

  function MySubClass:method() return self.name .. '!' end

  print(MySubClass:new('oh'):method())  -- prints oh!

So in the end here we only created 3 tables: MyClass, MySubClass and the
instance itself.  Same number of functions as before.

Should you use your classes as metatables?
  ✅ You only need a single table per object.
  ❌ Your objects will end up with __index defined, and that's dirty.
  ✅ If you need to override other metamethods you can do it in the same class.
  ❌ By sharing them you won't be able to subclass using the not-shared syntax.

In the end there are pros and cons to both shared and non-shared metaclasses.
I'm personally used to sharing the same object for classes and metaclasses
but I don't have a strong opinion against doing otherwise; specially when the
objects are heavyweight and we can ignore the cost of a slim metatable.

If anything, given a large project, we could even spend time writing a base
class containing a constructor with a cache for the metatable singleton given
a class, so that at least we would end up with a very small number of extra
tables.

In other words do whatever you feel comfortable with!

That said, at least being conscious of the consequences of your decisions lets
you be able to choose the best alternative depending on the characteristics of
the project you are working on.

PS: Hope this post helps someone on the process of starting with lua.  And it
could even help intermediate users who are only used to class frameworks.