Metamethods
Metamethods let you customize how instances behave with Lua operators and built-in functions.
Using the meta Modifier
Mark metamethods with the meta modifier:
Supported Metamethods
| Metamethod | Triggered by |
|---|---|
__tostring |
tostring(obj) or print(obj) |
__call |
obj() |
__index |
obj.unknownKey |
__newindex |
obj.unknownKey = value |
__concat |
obj .. other |
__unm |
-obj |
__add |
obj + other |
__sub |
obj - other |
__mul |
obj * other |
__div |
obj / other |
__mod |
obj % other |
__pow |
obj ^ other |
__eq |
obj == other |
__lt |
obj < other |
__le |
obj <= other |
__tostring
Customize string representation:
__call
Make instances callable like functions:
local double = Multiplier.new(2)
local triple = Multiplier.new(3)
print(double(5)) -- 10
print(triple(5)) -- 15
__construct vs __call
The first call to an instance uses __construct. Subsequent calls use __call:
Arithmetic Operators
Implement mathematical operations:
class "Complex" {
real = 0;
imag = 0;
__construct = function(self, r, i)
self.real = r or 0
self.imag = i or 0
end;
meta {
__add = function(self, other)
return Complex.new(
self.real + other.real,
self.imag + other.imag
)
end;
__sub = function(self, other)
return Complex.new(
self.real - other.real,
self.imag - other.imag
)
end;
__mul = function(self, other)
return Complex.new(
self.real * other.real - self.imag * other.imag,
self.real * other.imag + self.imag * other.real
)
end;
__unm = function(self)
return Complex.new(-self.real, -self.imag)
end;
__tostring = function(self)
if self.imag >= 0 then
return self.real .. "+" .. self.imag .. "i"
else
return self.real .. self.imag .. "i"
end
end;
};
}
local a = Complex.new(3, 2)
local b = Complex.new(1, 4)
print(a + b) -- 4+6i
print(a - b) -- 2-2i
print(a * b) -- -5+14i
print(-a) -- -3-2i
Comparison Operators
class "Version" {
major = 0;
minor = 0;
patch = 0;
__construct = function(self, maj, min, pat)
self.major = maj or 0
self.minor = min or 0
self.patch = pat or 0
end;
meta {
__eq = function(self, other)
return self.major == other.major
and self.minor == other.minor
and self.patch == other.patch
end;
__lt = function(self, other)
if self.major ~= other.major then
return self.major < other.major
end
if self.minor ~= other.minor then
return self.minor < other.minor
end
return self.patch < other.patch
end;
__le = function(self, other)
return self == other or self < other
end;
__tostring = function(self)
return self.major .. "." .. self.minor .. "." .. self.patch
end;
};
}
local v1 = Version.new(1, 0, 0)
local v2 = Version.new(1, 2, 0)
local v3 = Version.new(1, 2, 0)
print(v1 < v2) -- true
print(v2 == v3) -- true
print(v2 <= v3) -- true
__index and __newindex
Handle access to undefined members:
class "FlexibleObject" {
data = {};
meta {
__index = function(self, key)
return self.data[key]
end;
__newindex = function(self, key, value)
print("Setting " .. key .. " = " .. tostring(value))
self.data[key] = value
end;
};
}
local obj = FlexibleObject.new()
obj.foo = 42 -- Setting foo = 42
obj.bar = "hello" -- Setting bar = hello
print(obj.foo) -- 42
print(obj.bar) -- hello
__concat
Implement string concatenation:
class "StringBuilder" {
parts = {};
add = function(self, str)
table.insert(self.parts, str)
return self
end;
meta {
__concat = function(self, other)
local new = StringBuilder.new()
for _, p in ipairs(self.parts) do
table.insert(new.parts, p)
end
if type(other) == "string" then
table.insert(new.parts, other)
else
for _, p in ipairs(other.parts) do
table.insert(new.parts, p)
end
end
return new
end;
__tostring = function(self)
return table.concat(self.parts)
end;
};
}
local a = StringBuilder.new():add("Hello"):add(" ")
local b = StringBuilder.new():add("World"):add("!")
print(a .. b) -- Hello World!
Multiple Metamethods Example
class "Money" {
cents = 0;
__construct = function(self, dollars, cents)
self.cents = (dollars or 0) * 100 + (cents or 0)
end;
meta {
__add = function(self, other)
local m = Money.new()
m.cents = self.cents + other.cents
return m
end;
__sub = function(self, other)
local m = Money.new()
m.cents = self.cents - other.cents
return m
end;
__mul = function(self, factor)
local m = Money.new()
m.cents = math.floor(self.cents * factor)
return m
end;
__eq = function(self, other)
return self.cents == other.cents
end;
__lt = function(self, other)
return self.cents < other.cents
end;
__tostring = function(self)
local dollars = math.floor(self.cents / 100)
local cents = self.cents % 100
return string.format("$%d.%02d", dollars, cents)
end;
};
}
local price = Money.new(10, 50) -- $10.50
local tax = Money.new(0, 84) -- $0.84
local total = price + tax -- $11.34
local discounted = total * 0.9 -- $10.20
print(price) -- $10.50
print(total) -- $11.34
print(discounted) -- $10.20
print(price < total) -- true