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.