26. Methods and self — Homework solutions

The .lua solution files are in exercises/26/homework/solutions/.

Problem 1 — Counter object

Problem. A counter table with inc, get, and reset methods.

How to think about it. The table holds the state in count; each method reads or changes self.count. The colon definition and call do the wiring.

Worked solution.

local counter = { count = 0 }

function counter:inc()
    self.count = self.count + 1
end

function counter:get()
    return self.count
end

function counter:reset()
    self.count = 0
end

counter:inc()
counter:inc()
counter:inc()
print(counter:get())   -- 3

counter:reset()
print(counter:get())   -- 0

Common mistakes.

  • Writing function counter.inc(self) — the expanded form works, but reads as if you forgot the colon. Pick one style.
  • Calling counter.inc() instead of counter:inc(). Without the colon, self is nil and the assignment fails.

Problem 2 — Dog with bark

Problem. Two dog tables with their own names, each calling its own :bark().

How to think about it. Two tables, each with a name field, can share the same method, or each can define its own bark. Both work; the second wastes memory and is rarely worth it.

Worked solution.

local function makeDog(name)
    local dog = { name = name }
    function dog:bark()
        print(self.name .. ": Woof!")
    end
    return dog
end

local rex = makeDog("Rex")
local lassie = makeDog("Lassie")

rex:bark()      -- Rex: Woof!
lassie:bark()   -- Lassie: Woof!

A simpler version without the helper function:

local rex = { name = "Rex" }
function rex:bark()
    print(self.name .. ": Woof!")
end

local lassie = { name = "Lassie" }
function lassie:bark()
    print(self.name .. ": Woof!")
end

rex:bark()
lassie:bark()

Both give the same output. Chapter 27 generalises the first into a proper class.

Common mistakes.

  • Sharing one bark method by writing lassie.bark = rex.bark. That works — it prints Lassie: Woof! because self.name reads from whichever table the call started on — and is a valid optimisation. Just know what you are doing.

Problem 3 — Calculator object

Problem. A calc table with arithmetic methods that update an internal value.

Worked solution.

local calc = { value = 0 }

function calc:add(n)
    self.value = self.value + n
end

function calc:sub(n)
    self.value = self.value - n
end

function calc:mul(n)
    self.value = self.value * n
end

function calc:show()
    print("Value: " .. self.value)
end

calc:add(10)
calc:show()    -- Value: 10
calc:mul(3)
calc:show()    -- Value: 30
calc:sub(5)
calc:show()    -- Value: 25

Common mistakes.

  • Returning the new value from each method instead of updating self.value. Both are fine, but the spec asks for in-place updates, so the methods modify self.value.

Challenge — Stack

Problem. A LIFO stack with push, pop, peek, size.

Worked solution.

local stack = { items = {} }

function stack:push(v)
    table.insert(self.items, v)
end

function stack:pop()
    return table.remove(self.items)
end

function stack:peek()
    return self.items[#self.items]
end

function stack:size()
    return #self.items
end

stack:push("a")
stack:push("b")
stack:push("c")
print(stack:size())    -- 3
print(stack:peek())    -- c
print(stack:pop())     -- c
print(stack:pop())     -- b
print(stack:size())    -- 1

Without a position, table.insert and table.remove work at the end of the list — the right place for stack operations.

Common mistakes.

  • Using table.insert(self.items, 1, v) and table.remove(self.items, 1). That inserts at the front — a queue, not a stack — and costs more because every other item shifts.
  • Forgetting to return from pop and peek. The default return is nil, which silently breaks the test.

Done?

The next chapter takes this pattern further. With metatables, you stop writing the same boilerplate for every "class" and share method tables across many instances.