Metatables
Every table can have a metatable: a second, ordinary table with instructions for Lua on what to do with the first table in special situations. You attach one with setmetatable(table, metatable).
The instructions are stored under special keys that start with two underscores. The most useful one is __index. It tells Lua where to look when you read a key that the table doesn't have:
local marketPrices = {carrot = 5, potato = 8, pumpkin = 20}
local hugoPrices = {pumpkin = 25}
setmetatable(hugoPrices, {__index = marketPrices})
print(hugoPrices.pumpkin, hugoPrices.carrot, hugoPrices.corn)This prints:
25 5 nilHugo the trader pays the usual market prices, except for pumpkins, which he pays more for. His table holds just that one price, and Lua looks for the others elsewhere:
hugoPrices.pumpkinis in Hugo's table, so it's25.hugoPrices.carrotisn't. Lua looks at the table's metatable, finds__indexthere, and readscarrotfrom the table that__indexrefers to,marketPrices:5.cornisn't in either table, so the result isnil, as for any missing key.
__index is used only when you read a missing key. When you write a key, it goes into the table itself, and the other table doesn't change: after hugoPrices.carrot = 6, Hugo pays 6 for carrots, and marketPrices.carrot is still 5.
What a metatable holds under such a special key is called a metamethod. __index is the first one you know, and you'll meet more of them in this lesson. getmetatable(t) gives you the metatable of t, or nil if it has none.
Shared methods. This is how objects can share their methods. Put the methods into one table, and give each object a metatable whose __index is that table:
local cropMethods = {}
function cropMethods:grow()
self.age = self.age + 1
end
function cropMethods:describe()
print(self.name .. ": day " .. self.age .. " of " .. self.days)
end
local function newCrop(name, days)
local crop = {name = name, days = days, age = 0}
setmetatable(crop, {__index = cropMethods})
return crop
end
local carrot = newCrop("Carrot", 3)
local pumpkin = newCrop("Pumpkin", 7)
carrot:grow()
carrot:describe()
pumpkin:describe()
print(carrot.grow == pumpkin.grow)This prints:
Carrot: day 1 of 3
Pumpkin: day 0 of 7
trueHere's what carrot:grow() does:
- It's short for
carrot.grow(carrot), so Lua first readscarrot.grow. The carrot holds onlyname,daysandage; there's nogrowin it. - The carrot's metatable has
__index, so Lua looks incropMethodsand findsgrowthere. - Lua calls that function with the carrot as
self, soself.age = self.age + 1changes the carrot, notcropMethods.
Each crop now holds only its own data, and each method exists just once: carrot.grow == pumpkin.grow is true. Lua looks for a method at the moment you call it, so a method that you add to cropMethods later works for crops that already exist, too.
The secret of s:upper(). Texts have a metatable too, one that all texts share, and its __index is the table string:
print(getmetatable("carrot").__index == string)This prints:
trueSo s:upper() is s.upper(s). A text has no keys of its own, so Lua looks in its __index, which is string, and finds string.upper. It's the same mechanism as with the crops.
Instructions
Make the hens share their methods. Above newHen, create a table henMethods and move both methods into it (function henMethods:lay(count), …). In newHen, give each new hen a metatable whose __index is henMethods.
Run the program: the last line now prints true, because both hens use the same function. Add one more method, henMethods:collect(), which returns the hen's eggs and sets them back to 0. You can write it below the line that prints true: the hens look for their methods only when you call them. At the end of the program, collect the eggs of both hens and print how many there are together:
Goldie laid 2 eggs
Pearl laid 4 eggs
true
Collected: 6 eggsStart programming for free
4/9