Classes And Methods With Eventtable |
|
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 >
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 >
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() ) ------------------------------------------------------------------------------ ------------------------------------------------------------------------------
$ 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 >
> > 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 >
> 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 >
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 >