multi-level inheritance and __index

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

multi-level inheritance and __index

Dave Hayden
I've got a base class "sprite" implemented in C, a subclass "ball" defined in Lua, and an instance of the subclass. I want to synthesize properties on the base class using __index and __newindex so that they're available to all instances. What I'm seeing, though, is that the "ball" table gets passed into sprite's __index function, not the instance. Here's a Lua example that shows the same behavior:

        a = { name = 'a' }
        print('a = '..tostring(a))
        a.__index = a

        function a.__index(self, key)
                print('a.__index('..tostring(self)..', '..key..')')
       
                if key == 'myname' then
                        return self.name
                end
       
                if self ~= a then
                        return a[key]
                else
                        return nil
                end
        end

        b = { name = 'b' }
        print('b = '..tostring(b))
        b.__index = b
        setmetatable(b, a)
        print(b.myname)

        c = { name = 'c' }
        print('c = '..tostring(c))
        setmetatable(c, b)
        print(c.myname) -- prints 'b', not 'c'


Am I doing it wrong? Or is Lua's crazy OO hack not capable of this?

Thanks!
-Dave


Reply | Threaded
Open this post in threaded view
|

Re: multi-level inheritance and __index

Steven Degutis
> I want to synthesize properties on the base class using __index and __newindex so that they're available to all instances.

To add methods to "all instances" of a class, you just add methods to
the table that's the __index field on every instance's metatable, like
so:

    Sprite = {}

    sprite1 = setmetatable({x=1}, {__index = Sprite})
    sprite2 = setmetatable({x=2}, {__index = Sprite})

    Sprite.foobar = function(self) return self.x * 10 end
    -- alternatively but functionally equivalent:
    function Sprite:foobar() return self.x * 10 end

    sprite1:foobar() -- returns 10
    sprite2:foobar() -- returns 20

To turn this into multi-level inheritance, you can just do this:

    SpriteBase = {}
    Sprite = setmetatable(Sprite, {__index = SpriteBase})

Now, just add methods to SpriteBase, and both sprite1 and sprite2 have them.

Reply | Threaded
Open this post in threaded view
|

Re: multi-level inheritance and __index

Dave Hayden
On Jan 14, 2015, at 3:20 PM, Steven Degutis <[hidden email]> wrote:
>
>> I want to synthesize properties on the base class using __index and __newindex so that they're available to all instances.
>
> To add methods to "all instances" of a class, you just add methods to
> the table that's the __index field on every instance's metatable, like
> so: [...]

Okay, but if I want to define an __index function to synthesize a property, I seem to be out of luck: The metatable is passed into __index(self, key), not the instance object.

Can someone verify that Lua just doesn't work that way?

-D


Reply | Threaded
Open this post in threaded view
|

Re: multi-level inheritance and __index

Andrew Starks
On Wed, Jan 14, 2015 at 7:51 PM, Dave Hayden <[hidden email]> wrote:

> On Jan 14, 2015, at 3:20 PM, Steven Degutis <[hidden email]> wrote:
>>
>>> I want to synthesize properties on the base class using __index and __newindex so that they're available to all instances.
>>
>> To add methods to "all instances" of a class, you just add methods to
>> the table that's the __index field on every instance's metatable, like
>> so: [...]
>
> Okay, but if I want to define an __index function to synthesize a property, I seem to be out of luck: The metatable is passed into __index(self, key), not the instance object.
>
> Can someone verify that Lua just doesn't work that way?
>
> -D
>
>

No, the instance is passed in, not the metatable. You can have both, however.

```
local mt = {}

mt.__index = function(self, i)
   assert(mt ~= self and type(self) == "table")
  return mt.some_method(self, i)
end

function mt:some_method(i)
   return self
end

local obj = setmetatable({}, mt)

assert(obj.foo == obj)

```

Notice that I have access to mt inside of mt.__index, because mt is an
upvalue. Notice that the index handler returns self and self is equal
to the obj table. (I didn't test the above. let me know if it doesn't
work)

Also, note that when you assign `__index` to a table(`mt.__index =
mt`), it is not-quite only short hand for `mt.__index = function(self,
i) return mt[i] end`.

In the table assignment, it is a raw access. In the function
assignment, it's whatever you want. The second example is doing a
normal lookup and will use the an `__index` method on `mt`, if there
is one. That might be something to keep in mind when trying to get a
lookup chain going.

--Andrew

Reply | Threaded
Open this post in threaded view
|

Re: multi-level inheritance and __index

Mark Hamburg-3
While one can chain __index accesses, in my experience, it doesn't work particularly well if you want to do anything more than lookup a value. __index is more like a delegation link than it is like an inheritance link.

As a result, I tend toward having no "runtime" dependencies between subclasses and superclasses. (The last class system I built had no inheritance support though it did provide for mixins at class construction time.)

Mark


Reply | Threaded
Open this post in threaded view
|

Re: multi-level inheritance and __index

Dirk Laurie-2
2015-01-17 20:33 GMT+02:00 Mark Hamburg <[hidden email]>:

> While one can chain __index accesses, in my experience, it doesn't work particularly well if you want to do anything more than lookup a value. __index is more like a delegation link than it is like an inheritance link.

At times like this, it is useful to remember that metamethods
started out as fallbacks. A way to avoid failure in an exceptional
case rather than a routine technique.