Classes

In the previous exercise, crops needed two tables: cropMethods with the methods, and a metatable {__index = cropMethods}, a new one for every crop. Lua programmers usually let one table do both jobs: it holds the methods, it's the metatable of every crop, and its __index points to itself. Such a table is called a class:

local Crop = {}
Crop.__index = Crop

function Crop.new(name, days)
    local crop = {name = name, days = days, age = 0}
    setmetatable(crop, Crop)
    return crop
end

function Crop:grow()
    self.age = self.age + 1
end

function Crop:isReady()
    return self.age >= self.days
end

local wheat = Crop.new("Wheat", 2)
local carrot = Crop.new("Carrot", 3)
wheat:grow()
wheat:grow()
carrot:grow()
print(wheat.name, wheat:isReady())
print(carrot.name, carrot:isReady())

This prints:

Wheat	true
Carrot	false

Line by line:

  • local Crop = {} creates the class, an ordinary table. Class names start with a capital letter: Crop is the class, wheat and carrot are crops.
  • Crop.__index = Crop is the trick. Crop will be the metatable of every crop, and its __index is Crop itself. So when a crop doesn't have a key, Lua looks for it in Crop, where the methods are.
  • Crop.new is the constructor. It creates a table with the crop's own data, sets Crop as its metatable, and returns it. new is the usual name for a constructor in Lua. Call it with a dot, Crop.new("Wheat", 2): you call it on the class, before any crop exists, so there's no self.
  • function Crop:grow() defines a method on the class with a colon, and the crops call it with a colon: wheat:grow().

An object made by a class is called an instance of that class: wheat and carrot are instances of Crop. Each holds only its own data; the methods are in Crop, once. Unlike many other languages, Lua has no special keyword for classes. A class is this pattern of a table, __index and a constructor, and you'll find it in almost every bigger Lua program.

setmetatable returns the table it gets, so you'll often see a shorter constructor: local crop = setmetatable({name = name, days = days, age = 0}, Crop). It does the same thing.

The classic mistake: forgetting Crop.__index = Crop. Without that line, everything looks fine until you call a method:

local Crop = {}

function Crop.new(name, days)
    local crop = {name = name, days = days, age = 0}
    setmetatable(crop, Crop)
    return crop
end

function Crop:grow()
    self.age = self.age + 1
end

local wheat = Crop.new("Wheat", 2)
wheat:grow()

The program ends with an error:

main.lua:14: attempt to call a nil value (method 'grow')

Crop is the wheat's metatable, but without __index, Lua doesn't know that it should look for missing keys there. So wheat.grow is nil, and calling nil is an error. (method 'grow') means that a method wasn't found: check __index, and check the method's name for a typo.

The mistakes from the previous exercises can happen here too. Without return crop, Crop.new returns nil (attempt to index a nil value (local 'wheat')), and wheat.grow() with a dot leaves self empty (attempt to index a nil value (local 'self')).

Instructions

Run the program: it ends with attempt to call a nil value (method 'lay'). Fix the class, so that the program prints:

Goldie: 2 eggs

Add a method Hen:report() that prints the hen's line the way the last line of the program does now. Then replace that last line with goldie:report(). The program prints the same.

Create two more hens with Hen.new: Pearl, who lays 3 eggs, and Rosie, who lays 4. Print their reports below Goldie's, and then the number of eggs of all three hens together:

Goldie: 2 eggs
Pearl: 3 eggs
Rosie: 4 eggs
Total: 9 eggs

Start programming for free

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

Or sign up with:

5/9