Simple Lua Classes |
|
Out of the box, Lua does not have a class system, but its powerful metaprogramming facilities makes defining classic objects straightforward. In fact, there's a number of ways of doing this; together with the unfamiliar notation, this makes object-orientation in Lua seem a little intimidating at first.
The method described here is the most common and flexible method, using metatables. A table's behavior can be customized by giving it a metatable. For instance, if the metatable has an __index
function, then any failed attempt to look up something in the table will be passed to __index
. If __index
is itself a table, the symbol will be looked up in that table. (Please see the excellent discussion in PiL [1]) Here is the basic idea:
Account = {} Account.__index = Account function Account.create(balance) local acnt = {} -- our new object setmetatable(acnt,Account) -- make Account handle lookup acnt.balance = balance -- initialize our object return acnt end function Account:withdraw(amount) self.balance = self.balance - amount end -- create and use an Account acc = Account.create(1000) acc:withdraw(100)
Here, Account objects are represented by tables, which contain precisely one field, the balance. Lua tries to look up withdraw
in acc, and cannot find it. Because acc has a metatable that defines __index
, it will then look up withdraw
in that metatable. So acc:withdraw(100)
is actually the call Account.withdraw(acc,100)
. We could have actually put withdraw
directly into acc
, but this would be wasteful and inflexible - adding a new method would require a change to the create
function, etc.
I'll define a function class
which does all this (and more) transparently.
Account = class(function(acc,balance) acc.balance = balance end) function Account:withdraw(amount) self.balance = self.balance - amount end -- can create an Account using call notation! acc = Account(1000) acc:withdraw(100)
In this scheme, one supplies an initialization function to the new class, and a 'constructor' is automatically generated.
Simple inheritance is supported. For example, here a base class Animal
is defined, and several specific kinds of animals are declared. All classes made using class
have a is_a
method, which you can use to find out the actual class at runtime:
-- animal.lua require 'class' Animal = class(function(a,name) a.name = name end) function Animal:__tostring() return self.name..': '..self:speak() end Dog = class(Animal) function Dog:speak() return 'bark' end Cat = class(Animal, function(c,name,breed) Animal.init(c,name) -- must init base! c.breed = breed end) function Cat:speak() return 'meow' end Lion = class(Cat) function Lion:speak() return 'roar' end fido = Dog('Fido') felix = Cat('Felix','Tabby') leo = Lion('Leo','African')
D:\Downloads\func>lua -i animal.lua > = fido,felix,leo Fido: bark Felix: meow Leo: roar > = leo:is_a(Animal) true > = leo:is_a(Dog) false > = leo:is_a(Cat) true
All Animal
does is define __tostring
, which Lua will use whenever a string representation is needed of the object. In turn, this relies on speak
, which is not defined. So it's what C++ people would call an abstract base class; the specific derived classes like Dog
define speak
. Please note that if derived classes have their own initialization functions, they must explicitly call init
for their base class.
class()
uses two tricks. It allows you to construct a class using the call notation (like Dog('fido')
above) by giving the class itself a metatable which defines __call
. It handles inheritance by copying the fields of the base class into the derived class. This isn't the only way of doing inheritance; we could make __index
a function which explicitly tries to look a function up in the base class(es). But this method will give better performance, at a cost of making the class objects somewhat fatter. Each derived class does keep a field _base
that contains the base class, but this is to implement is_a
.
Note that modification of a base class at runtime will not affect its subclasses.
-- class.lua -- Compatible with Lua 5.1 (not 5.0). function class(base, init) local c = {} -- a new class instance if not init and type(base) == 'function' then init = base base = nil elseif type(base) == 'table' then -- our new class is a shallow copy of the base class! for i,v in pairs(base) do c[i] = v end c._base = base end -- the class will be the metatable for all its objects, -- and they will look up their methods in it. c.__index = c -- expose a constructor which can be called by <classname>(<args>) local mt = {} mt.__call = function(class_tbl, ...) local obj = {} setmetatable(obj,c) if init then init(obj,...) else -- make sure that any stuff from the base class is initialized! if base and base.init then base.init(obj, ...) end end return obj end c.init = init c.is_a = function(self, klass) local m = getmetatable(self) while m do if m == klass then return true end m = m._base end return false end setmetatable(c, mt) return c end
--- class_orig.lua 2009-07-24 20:53:25.218750000 -0400 +++ class.lua 2009-07-24 20:53:49.734375000 -0400 @@ -21,8 +21,8 @@ mt.__call = function(class_tbl,...) local obj = {} setmetatable(obj,c) - if ctor then - ctor(obj,...) + if class_tbl.init then + class_tbl.init(obj,...) else -- make sure that any stuff from the base class is initialized! if base and base.init then
A = class() function A:init(x) self.x = x end function A:test() print(self.x) end B = class(A) function B:init(x,y) A.init(self,x) self.y = y end
function A:__add(b) return A(self.x + b.x) end
c.init = ctor. I changed this argument's name to 'init'. --DeniSpir
the nested constructor function is ugly, but otherwise this is helpful