Lua - Chaining Metatables



Chaining metatables is a very effective technique to implment inheritance like feature in Lua and to delegate operations to different tables or objects as per the requirement. Chaining is achieved with the use of __index() metamethod

Chaining metatables can be referred as a fallback mechanism for tables. When we try to access a non-existent key in a table, lua looks for its metatable for __index() method implementation. If __index() is another table, then Lua keeps it search in the next table and continues making a chain of metatables to be searched for __index() method implementation.

Working of __index as as a table

  • Consider an object or table as table1 with a missing key.

  • A metatable, metatable1 is set on the table as setmetatable(table1, metatable1)

  • metatable1 is having __index field pointing to another table as table2.

  • Now first Lua looks for the missing key in table2 because of __index field. If key is found, value from table2 is returned.

  • If key is not found and table2 is again having metatable2 with __index set to table3, Lua will continue search in table3 and so on.

Example - Chaining Metatable using __index as table

Let's create an inheritance chain of Animal → Dog &rarr Pug.

main.lua

-- Base class: Animal
Animal = { sound = "Generic Roar" }

Animal.__index = Animal 

-- method makeSound
function Animal:makeSound()
  print(self.sound)
end

-- Subclass: Dog, inheriting from Animal Class
Dog = setmetatable({ breed = "Bulldog" }, { __index = Animal })

Dog.__index = Dog

-- method specific to Dog class
function Dog:bark()
  print("Woof!")
end

-- override parent method
function Dog:makeSound()
  print("Woof woof!")
end

-- Subclass: Pug, inheriting from Animal Class
Pug = setmetatable({ name = "PUG" }, { __index = Dog })
-- for method calls when : syntax is used.
Pug.__index = Pug 

-- method specific to Pug class
function Pug:snore()
  print("Zzz..")
end

-- Create instance of Animal
local animal = {}
setmetatable(animal, { __index = Animal })

-- Create instance of Dog
local dog = {}
setmetatable(dog, { __index = Dog })

-- Create instance of Pug
local pug = {}
setmetatable(pug, { __index = Pug })

-- Chaining process
animal:makeSound()       -- Generic Roar

dog:makeSound()          -- Woof woof! (overridden)
dog:bark()               -- Woof! (inherited)
print(dog.sound)         -- Generic Roar (inherited)
print(dog.breed)         -- Bulldog (own property)

pug:makeSound()          -- Woof woof! (inherited from Dog)
pug:bark()               -- Woof! (inherited from Dog)
pug:snore()              -- Zzz.. (own method)
print(pug.sound)         -- Generic Roar (inherited from Animal)
print(pug.breed)         -- Bulldog (inherited from Dog)
print(pug.name)          -- PUG (own property)

Output

When we run the above program, we will get the following output−

Generic Roar
Woof woof!
Woof!
Generic Roar
Bulldog
Woof woof!
Woof!
Zzz..
Generic Roar
Bulldog
PUG

Working of __index as as a function

We can use __index metamethod as a function as well. This function will return the table and now it will work in the same fashion as explained earlier,

main.lua

-- Table 1
Table1 = { value1 = "Hello" }

-- Table 2
Table2 = { value2 = "World" }
setmetatable(Table1, { __index = Table2 })

-- creating instance
local instance = {}
setmetatable(instance, {
  __index = function(table, key)
    print("Checking for missing key '" .. key .. "'")
    return Table1[key]
  end
})
-- prints Checking for missing key 'value1' \n Hello
print(instance.value1)
-- prints Checking for missing key 'value2' \n World
print(instance.value2) 
-- prints Checking for missing key value3 \n nil
print(instance.value3)

Output

When we run the above program, we will get the following output−

Checking for missing key 'value1'
Hello
Checking for missing key 'value2'
World
Checking for missing key 'value3'
nil

Advantages of Chaining Metatables

  • Inheritance − We can create hiearchies of subclasses inheriting properties from super classes.

  • Reusability − Common code can be defined in base class and then can be reused by sub classes.

  • Delegation − Certain operations can be delegated to a specialized object like logging.

  • Organization − Code can be structured by define tables, chaining them with specialized tables and so.

Advertisements