Metamethod lookup through __index?
I've implemented my own class system and I'm having trouble with __tostring
; I suspect a similar issue can happen with other metamethods, but I haven't tried.
(Brief detour: each class has a __classDict
attribute, holding all methods. It is used as the class instances' __index
. At the same time, the __classDict's __index
is the superclass' __classDict
, so methods in superclasses are authomatically looked up.)
I wanted to have a "default tostring" behavior in all instances. But it didn't work: the "tostring" behavior doesn't "propagate" through subclasses correctly.
I've done this test exemplifying my issue:
mt1 = {__tostring=function(x) return x.name or "no name" end }
mt2 = {}
setmetatable(mt2, {__index=mt1})
x = {name='x'}
y = {name='y'}
setmetatable(x, mt1)
setmetatable(y, mt2)
print(x) -- prints "x"
print(mt2.__tostring(y)) -- prints "y"
print(y) -- prints "table: 0x9e84c18" !!
I'd rather have that last line print "y".
Lua's "to_String" behaviour must be using the equivalent of
rawget(instance.class.__classDict, '__tostring')
instead of doing the equivalent of
instance.class.__classDict.__tostring
I suspect the same happens with all metamethods; rawget
-equivalent operations are used.
I guess one thing I could do is copying all the metamethods when I do my subclassing (the equivalent on the above example would be doing m开发者_如何转开发t2.__tostring = mt1.__tostring
) but that is kind of inelegant.
Has anyone fought with this kind of issue? What where your solutions?
I suspect the same happens with all metamethods; rawget-equivalent operations are used.
That is correct. from the lua manual:
... should be read as
rawget(getmetatable(obj) or {}, event)
. That is, the access to a metamethod does not invoke other metamethods, and the access to objects with no metatables does not fail (it simply results in nil).
Generally each class has its own metatable, and you copy all references to functions into it.
That is, do mt2.__tostring = mt1.__tosting
Thanks to daurnimator's comments, I think I found a way to make metamethods "follow" __index
as I want them to. It's condensed on this function:
local metamethods = {
'__add', '__sub', '__mul', '__div', '__mod', '__pow', '__unm', '__concat',
'__len', '__eq', '__lt', '__le', '__call', '__gc', '__tostring', '__newindex'
}
function setindirectmetatable(t, mt)
for _,m in ipairs(metamethods) do
rawset(mt, m, rawget(mt,m) or function(...)
local supermt = getmetatable(mt) or {}
local index = supermt.__index
if(type(index)=='function') then return index(t,m)(...) end
if(type(index)=='table') then return index[m](...) end
return nil
end)
end
return setmetatable(t, mt)
end
I hope it is straightforward enough. When a new metatable is set, it initializes it with all metamethods (without replacing existing ones). These metamethods are prepared to "pass on" requests to "parent metatables".
This is the simplest solution I could find. Well, I actually found a solution that used less characters and was a bit faster, but it involved black magic (it involved metatable functions de-referencing themselves inside their own bodies) and it was much less readable than this one.
If anyone finds a shorter, simpler function that does the same, I'll gladly give him the answer.
Usage is simple: replace setmetatable
by setindirectmetatable
when you want it to "go up":
mt1 = {__tostring=function(x) return x.name or "no name" end }
mt2 = {}
setmetatable(mt2, {__index=mt1})
x = {name='x'}
y = {name='y'}
setmetatable(x, mt1)
setindirectmetatable(y, mt2) -- only change in code
print(x) -- prints "x"
print(mt2.__tostring(y)) -- prints "y"
print(y) -- prints "y"
A little word of warning: setindirectmetatable
creates metamethods on mt2. Changing that behavior so a copy is made, and mt2 remains unaltered, should be trivial. But letting them set up by default is actually better for my purposes.
From my experience with Lua 5.1, metamethods are looked up in metatables using rawget(), and that's why you must copy the reference to the function into every class table you create.
See the Inheritance Tutorial on the Lua Users Wiki.
精彩评论