Classes And Methods With Eventtable

lua-users home
wiki


[!] VersionNotice: The below code pertains to an older Lua version, Lua 4. It does not run as is under Lua 5. This page is obsolete now that Lua 5.0 is finished. See LuaClassesWithMetatable.

The difference between the Lua4.0 and Lua4.1 (work4) is like the difference between AWK and Perl 5. The new metatables and lexical scoping are really great for making classes in Lua, but some understanding of tag methods is required.

Note: in Lua4.1 (work4) the metatable() command has been altered to metatable() -- Dominik Wagner

Note: I changed the article to reflect this change. -- JamesHearn

To understand tag methods first set the gettable and settable events to be the print function. That way you can see what arguments are passed to the event function when a table access happens.

$ lua
Lua 4.1 (work4)  Copyright (C) 1994-2001 TeCGraf, PUC-Rio
> t = { 11,22,33 , one = "a", two = "b", three = "c" }
> foreach(t,print)
1       11
2       22
3       33
one     a
three   c
two     b
> mt = { gettable = print, settable = print }
> metatable( t, mt )
> x = t[2]
table: 0x80631d0        2
> = t
table: 0x80631d0
> x = t.three
table: 0x80631d0        three
> x = t["three"]
table: 0x80631d0        three
> t.one = 'rat'
table: 0x80631d0        one     rat
> t[1] = 99
table: 0x80631d0        1       99
> = x
nil
>
From this we can see that when you try to look up a value in table 't', the gettable method is passed both the table 't' and the index as arguments. We can also see that when you try to set a table member to a value, the settable method is passed the table 't', the index and the new value.

settable and gettable events are straight forward, but the index event is much more useful. The index event is called when you try to get a value from a table that the table doesn't have. You can do something special for these cases like look in another table or whatever you want.

> foreach(t,print)
1       11
2       22
3       33
one     a
three   c
two     b
> mt = { index = print }
> metatable( t, mt )
> x = t.fish
table: 0x80631d0        fish
> = t
table: 0x80631d0
> x = t[44]
table: 0x80631d0        44
> x = t['zzz']
table: 0x80631d0        zzz
> = x
nil
>
We can see that the index event gets the table 't' and the index just like the gettable event did, but think of the index event as a special gettable for undefined table values. Beware that if you use the gettable event, then the index event will never get called.

Lua 4.1 (work4) lets you set these tag methods to either a function or some other table to access instead.

> foreach(t,print)
1       11
2       22
3       33
one     a
three   c
two     b
> u = { cow = 'big' }
> mt.index = u
> foreach(u,print)
cow     big
> = t.cow
big
>

The following is all you need to make classes like you would in Perl.

An object of a class is just a table whose metatable is set to another table which is the class.

You can put all the class methods in the class table and set the index method to be the class table. Notice below how my function 'class' sets it's index member to itself.

Then when you do a method call like t:fun(4), which is shorthand for t.fun(t,4), if table 't' doesn't have the function 'fun' it will use the index method to look in the class table.

------------------------------------------------------------------------------
--
function class( t )
  t = t or {}
  t.index = t
  t.new = new
  t.copy = copy
  return t
end

------------------------------------------------------------------------------
function new( class, init )
  init = init or {}
  return metatable( init, class )
end

------------------------------------------------------------------------------
function classof( x )
  return type(x)=='table' and metatable(x)
end

------------------------------------------------------------------------------
function copy( obj )
  local newobj = classof( obj ):new()
  for n,v in obj do newobj[n] = v end
  return newobj
end

------------------------------------------------------------------------------
------------------------------------------------------------------------------

B = class{
  name = 'Bob',
}

function B:who()
  print(self.name)
end

function B:hypotenuse()
  local x,y = self.x, self.y
  return sqrt( x*x + y*y )
end

a = B:new{
  x = 4,
  y = 99,
}

b = B:new{
  x = 5,
  y = 12,
}

print( b:hypotenuse() )

------------------------------------------------------------------------------
------------------------------------------------------------------------------
Here the class 'B' is the metatable or class table of methods for objects 'a' and 'b'. Notice how 'B.index' is equal to 'B'.
$ lua std.lua -i
> foreach(B,print)
copy    function: 0x8066038
index   table: 0x8066a08
who     function: 0x8065d40
hypotenuse      function: 0x80660c0
name    Bob
new     function: 0x80659b8
> foreach(a,print)
y       99
x       4
> foreach(b,print)
y       12
x       5
> print( b:hypotenuse() )
13
> = B
table: 0x8066a08
>
You can override the default 'new' method for the class 'B' as follows:
>
> function B:new( x,y )
>> local obj = new( self, { x=x, y=y } )
>> return obj
>> end
>
> c = B:new(1,sqrt(3))
> foreach(c,print)
y       1.732050807568877
x       1
> = c:hypotenuse()
2
>
A good trick is to use the 'debug' function to stop and print out values inside functions and then just enter 'cont' to continue.
> function B:something(x)
>> y = self
>> print(x,y)
>> debug()
>> print(x,y)
>> print"finish"
>> return x
>> end
>
> b:something(1234)
1234    table: 0x8066548
lua_debug> print(b)
table: 0x8066548
lua_debug> y=22
lua_debug> cont
1234    22
finish
>
One last note. Beware of using tag methods on the table of globals. Because all of Lua's standard functions are just global values, unless you make a local copy in a local variable, the tag method will go into an infinite loop when you try to call any of these functions. That is why you'll sometimes see things like the following:
local rawget, metatable, getglobal, gsub
    = rawget, metatable, getglobal, gsub

Is it essential that the event table peform double-duty by also holding class members? In Python classes, for example, special members are named with underbars (e.g., __index__) so as to not conflict with user fields. The same clashing problem exists when the Lua designers decide to add some new field to the event table specification.

No, it is just a convenience which Peter employed. You can use a different table for the index tag method; it just means juggling more tables. There are some oddities in Peter's code (like the fact that b:new() is defined but what it does is not 100% obvious) which result from the decision to merge the index table with the event table. I would personally suggest using a different table even though it complicates the design a bit, both for this reason and because of John's objection about name clashes. __foo__ is an ugly workaround, not a solution: separation of namespaces is the solution. -- RiciLake

Roberto was the first to suggest using the metatable to hold the methods for the class. I like it because then you can say:

function some_class:some_method(some_params) does_something end

If that's a recommended use then the system's event table fields should definitely be named to avoid clashes.

The following should fix the b:new() problem because obj.index always gives the class table whether new is passed a class table or an object of the class.

function class( t )
  t = t or {}
  t.index = t
  t.new = new
  t.copy = copy
  return t
end

function new( obj, init )
  init = init or {}
  return metatable( init, obj.index )
end

function copy( obj, init )
  local newobj = obj:new(init)
  for n,v in obj do newobj[n] = v end
  return newobj
end
$ lua -v std2.lua -i
Lua 4.1 (work4)  Copyright (C) 1994-2001 TeCGraf, PUC-Rio
> foreach(B,print)
copy    function: 0x8066fb0
index   table: 0x8067b40
who     function: 0x8066cb8
hypotenuse      function: 0x8067038
name    Bob
new     function: 0x8066b18
> foreach(b,print)
y       12
x       5
> w = b:new()
> foreach(w,print)
> =B
table: 0x8067b40
> =b.index
table: 0x8067b40
> =w.index
table: 0x8067b40
> z = b:new{ x=1,y=sqrt(3) } 
> = z:hypotenuse()
2
> foreach(z,print)
y       1.732050807568877
x       1
> zz = z:copy()
> foreach(zz,print)
y       1.732050807568877
x       1
> =z
table: 0x8068488
> =zz
table: 0x80686d0
> 
> b:who() 
Bob
> c=b:copy{ z=3 }
> foreach(c,print)
y       12
x       5
z       3
> c:who()
Bob
> 


RecentChanges · preferences
edit · history
Last edited December 31, 2006 12:13 am GMT (diff)