lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Hi,

> I am currently writing an object system for lua (yes, another one). I
> want to be able to define the metamethods like __add in my classes as
> methods. This works pretty well the way I do it now (define all
> metamethods in a metatable, and make them see wether the class defines
> a method for that), but considering that the presence of metamethods
> is checked for when evaluating expressions, I'd much rather have
> another metatable for the metatable, which defines an __index method
> that does the right thing[tm]. However, this does not seem to work.
> So my question is, can a table that is used as a metatable have its
> own metatable, and is that used?

A metatable is just a table and hence can have a metatable itself.  However,
this is not always being used as you might expect.  Suppose you a access a
non existing key in a table.  Then the __index method of its metatable is
considered.  However, _only_ the metatable itself is searched for this
__index method, Lua does not recurse to find it.  On the other hand the
value of the __index metakey can be either a function or a table.  In the
latter case the missing index will be accessed in the table provided by
__index.  This lookup _does_ use the metatable of the index table.  Don't
worry, here's an example:

-- prepare a metatable construct...

local tab1 = {
    -- here __index is a function
    __index = function(_, index) return string.format("accessed index %q",
index) end
}

local tab2 = {
    -- here it is a table
    __index = {}
}

-- provide a nested metatable
setmetatable(tab2.__index, tab1)

-- our test table...
local x = {
    weather = "fine"
}

-- see what happens with an __index function ...
setmetatable(x, tab1)

print(x.weather)
print(x.greeting)

-- ... and with an __index table
setmetatable(x, tab2)

print(x.weather)
print(x.greeting)


If all went well, both sets of "print" statements should produce identical
output.

Now about "instance members" in general.  Metatables are typically shared
among several "instances."  So promoting a table to some class is done by
simply setting its (unique) metatable.  If you want to provide shared
members (be it data or methods, which is all the same in Lua) you can do it
in a nice way as follows.

Suppose you want to implement a "class" myclass.  Define a "prototable" that
will hold all shared members (let's call it myclass_proto) and a metatable
to wire some functionality to instances (let's call it myclass_meta.)  I
prefer seperate proto- and metatables so your prototable can even hold the
"protected" keys like __index, __add, etc...

local myclass_proto = {
    greeting = "hello!",
    report_weather = function(self) print("the weather is " .. (self.weather
or "unknown")) end,
    -- etc...
}

local myclass_meta = {
    __index = function(_, index) return myclass_proto[index] end,
    -- etc. (e.g. an __add function, ...)
}

-- a myclass "constructor"
function myclass()
    local m = {}
    setmetatable(m, myclass_meta)
    return m
end

-- test it!

local instance = myclass()

instance.weather = "fine!"
instance:report_weather()
print(instance.greeting)
-- etc...


I hope this helps!

Wim