Lua Classes With Metatable


This page shows how to implement classes in Lua using metatables. The examples below work in both Lua 5.0 and 5.1.

Lua has matured from an application extension language into a wonderfully flexible scripting language. Lua 5.0 is not an object oriented language like Java or Ruby. Instead, Lua gives you the ability to implement classes however you wish. This is both a bonus and a bane. Power users love the freedom, but newbies are sometimes baffled.

Metatables are Lua's way of adding magic to tables. Let us assume that t is a regular table like so:

!Lua
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio
> t = { 11, 22, 33, you='one', me='two' }
> table.foreach(t,print)
1 11
2 22
3 33
me two
you one
> 
> = t[2]
22
> = t.me
two
> = t.fred
nil

Indexing the table with a valid index returns the value stored at that index.

Indexing the table with an undefined index returns nil. If we add some magic, we can try indexing another table instead of returning nil. We can even provide our own function to handle the undefined indexes however we wish.

These functions that customize certain Lua behaviour were called fallbacks in the first versions of Lua. In Lua 4.0 they were called tag methods. Now in Lua 5.0 (thanks largely to Edgar Toernig) these function are called metamethods and they are stored in tables called metatables.

The behaviours that we can customize have special names and are referred to as events. Adding a new index to a table is called the newindex event. Attempting to read an undefined index from a table is called the index event.

To see what happens when we access an undefined index, lets print out the arguments which are passed to the metamethod for the index event.

!Lua
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio
> t = { 11, 22, 33, you='one', me='two' }
> mt = { __index = print }
> = t.you 
one
> = t.fred
nil
> setmetatable(t, mt)
> x = t.fred
table: 0x8075e80 fred
> = x
nil
> = t
table: 0x8075e80
> 

Notice that the first argument is the table t and the second argument is the index fred.

If we do the same for the newindex event, we see that there is a third argument which is the new value to be stored at the index.

!Lua
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio
> t = { 11, 22, 33, you='one', me='two' }
> mt = { __newindex = print }
> setmetatable(t, mt)
> t[4] = 'rat'
table: 0x8075e80 4 rat
> 

As mentioned earlier, we can specify a table instead of a function, and that table will be accessed instead.

!Lua
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio
> t = { 11, 22, 33, you='one', me='two' }
> s = { }
> mt = { __newindex = s, __index = _G }
> setmetatable(t, mt)
> = t.you
one
> x = 'wow'
> = t.x
wow
> t[5] = 99
> table.foreach(s, print)
5 99
> 

The following shows how to implement a class of vectors. We have one table for the methods, and one metatable. There is an additional table for each object. All object share the same table of methods and the same metatable.

Remember that v1:mag() is like v1.mag(v1), so Lua tries to lookup mag in v1, which will trigger the index event, which then lookups mag in the table Vector.

!Lua
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio
> Vector = {}
> Vector_mt = { __index = Vector }
> 
> function Vector:new(x,y)
>> return setmetatable( {x=x, y=y}, Vector_mt)
>> end
> 
> function Vector:mag()
>> return math.sqrt(self:dot(self))
>> end
> 
> function Vector:dot(v)
>> return self.x * v.x + self.y * v.y
>> end
> 
> v1 = Vector:new(3,4)
> table.foreach(v1,print)
y 4
x 3
> = v1:mag()
5
> v2 = Vector:new(2,1)
> = v2:dot(v1)
10
> 
> = Vector
table: 0x8076028
> table.foreach(Vector,print)
mag function: 0x8078008
dot function: 0x8078b58
new function: 0x80773e8
> = v1, v2
table: 0x8079110 table: 0x8079a80
> = Vector_mt, getmetatable(v1), getmetatable(v2)
table: 0x80763b8 table: 0x80763b8 table: 0x80763b8
> table.foreach(Vector_mt,print)
__index table: 0x8076028
> 

If you want a default constructor and a copy constructor, you can create a file called Class.lua as follows:

!Lua
function Class(members)
 members = members or {}
 local mt = {
 __metatable = members;
 __index = members;
 }
 local function new(_, init)
 return setmetatable(init or {}, mt)
 end
 local function copy(obj, ...)
 local newobj = obj:new(unpack(arg))
 for n,v in pairs(obj) do newobj[n] = v end
 return newobj
 end
 members.new = members.new or new
 members.copy = members.copy or copy
 return mt
end

Then put our Vector class in a file called Vec.lua:

!Lua
require'Class'
Vector = {}
local Vector_mt = Class(Vector)
function Vector:new(x,y)
 return setmetatable( {x=x, y=y}, Vector_mt)
end
function Vector:mag()
 return math.sqrt(self:dot(self))
end
function Vector:dot(v)
 return self.x * v.x + self.y * v.y
end

Then test it as follows:

!Lua
$ lua -lVec -i -v
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio
> v1 = Vector:new(3,4)
> table.foreach(v1,print)
y 4
x 3
> = v1:mag()
5
> v2 = Vector:new(2,1)
> = v2:dot(v1)
10
>
> table.foreach(Vector,print)
copy function: 0x80692c0
dot function: 0x8069300
mag function: 0x80692e0
new function: 0x8069398
>
> v3 = v1:copy()
> = v1, v2, v3
table: 0x80779d0 table: 0x8078428 table: 0x807a050
> table.foreach(v1,print)
y 4
x 3
> table.foreach(v3,print)
y 4
x 3
> 

If we apply the Class function to Lua's table lib, we can create table objects.

!Lua
require'Class'
Class(table)
function table:push(x)
 assert( x ~= nil, 'will not push nil into table')
 self:insert(x)
 return self, x
end
function table:map(func, ...)
 local R = table:new{}
 for name,value in pairs(self) do func(R,name,value,unpack(arg)) end
 return R
end
function table:imap(func, ...)
 local R = table:new{}
 for index,elem in ipairs(self) do func(R,index,elem,unpack(arg)) end
 return R
end

Then you no longer have to type table.foreach or table.getn(t).

!Lua
$ lua -lTable -i -v
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio
> t = table:new{ 11, 22, 33, you='one', me='two' }
> = t:getn()
3
> t:foreach(print)
1 11
2 22
3 33
me two
you one
>
> = t:concat','
11,22,33
> = table
table: 0x8067808
> = getmetatable(t)
table: 0x8067808
>
> s = t:copy()
> s:foreach(print)
1 11
2 22
3 33
me two
you one
> = s, t
table: 0x8079a58 table: 0x8077bb8
> 

See Also


FindPage · RecentChanges · preferences
edit · history
Last edited October 18, 2008 5:32 pm GMT (diff)

AltStyle によって変換されたページ (->オリジナル) /