Tutorial :Metamethod lookup through __index?



Question:

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 mt2.__tostring = mt1.__tostring) but that is kind of inelegant.

Has anyone fought with this kind of issue? What where your solutions?


Solution:1

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


Solution:2

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.


Solution:3

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.


Solution:4

See the Inheritance Tutorial on the Lua Users Wiki.


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »