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	nil

Hugo 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.pumpkin is in Hugo's table, so it's 25.
  • hugoPrices.carrot isn't. Lua looks at the table's metatable, finds __index there, and reads carrot from the table that __index refers to, marketPrices: 5.
  • corn isn't in either table, so the result is nil, 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
true

Here's what carrot:grow() does:

  1. It's short for carrot.grow(carrot), so Lua first reads carrot.grow. The carrot holds only name, days and age; there's no grow in it.
  2. The carrot's metatable has __index, so Lua looks in cropMethods and finds grow there.
  3. Lua calls that function with the carrot as self, so self.age = self.age + 1 changes the carrot, not cropMethods.

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:

true

So 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 eggs

Start programming for free

By signing up, you agree to the Terms of Service and Privacy Policy.

Or sign up with:

4/9