开发者

Metamethod lookup through __index?

开发者 https://www.devze.com 2023-01-08 00:34 出处:网络
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.

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消