moonscript icon indicating copy to clipboard operation
moonscript copied to clipboard

Metamethod inheritance does not work

Open ghost opened this issue 13 years ago • 10 comments

Not entirely sure if this is a feature or a bug, but metamethods defined in a class do not work in child classes. e.g.

class Foo
  new: =>
  __tostring: =>
    return "foo!"

class Bar extends Foo
  new: =>

print tostring Foo! -- prints "foo!"
print tostring Bar! -- prints "table: 0x..."

You can get the inheritance to work just by adding

__tostring:=>super!

to the child class, but it seems a little kludgey

I looked closer at the actual class code generated and it looks like the parent class' __base is no longer added as a metatable to the child's __base -- if it were it seems like the metamethods should inherit just fine.

-- Strike that, yes it does! Still doesn't work though, and I have no idea why.

ghost avatar Nov 24 '12 09:11 ghost

This is a limitation of Lua, meta-methods are fetched with rawget, so the metatable of a metatable is not used:

local mt = {
    __tostring = function()
        return "hello"
    end
}

t = setmetatable({}, mt)

print(t) --> hello


t2 = setmetatable({}, setmetatable({}, {
    __index = mt
}))


print(t2, getmetatable(t2).__tostring) --> table: 0x1cac670    function: 0x1cac9e0

The only solution I can think of is it copy all the metamethods.

leafo avatar Nov 24 '12 17:11 leafo

Actually, copying isn't necessary. If every instance, regardless of class, had this metatable:

instance_mt = {
    __tostring = function(self) return self:__tostring() end;
    __add = function(lhs, rhs) return lhs:__add(rhs) end;
    __sub = function(lhs, rhs) return lhs:__sub(rhs) end;
    -- etc... repeat this pattern for all standard metamethods
}

everything would work exactly as expected. It might be a little inefficient, but to make it work with vanilla lua (I.E. when moonscript is used for compilation only, with no runtime access to the moonscript standard libraries) this table literal would have to be included in the source code for every compiled moonscript class.

exists-forall avatar Dec 31 '12 03:12 exists-forall

I was able to force the metamethods to be inherited simply by writing this in the base class:

    __inherited:(C)=>
        for i,v in next,@__base
            C.__base[i]or=v

Perhaps this issue was before __inherited existed?

krakow10 avatar Mar 05 '14 11:03 krakow10

Correct, this was before __inherited. There's still the question of whether it should copy the metamethods by default.

leafo avatar Mar 05 '14 17:03 leafo

+1 to the original idea of inheriting the metamethods of the parent. I am unaware as to whether this feature has been implemented as of yet, but I would like to see it added to Moonscript at some point.

Let there be a base class, let us say Vehicle, and let there be a subtype of that class, let us say Bicycle. Now, according to OOP paradigms, if an object inherits attributes from another, then it should also (predictably) able to do roughly the same thins as the parent class. (A bicycle is a type of Vehicle, so a Bicycle should be able to do everything a Vehicle can and interact as a Vehicle, too.)

By making metamethod inheritance implicit, then you make it unnecessary to use __inherited for setting up the metamethods, and thus reduce code-clutter and cut code size.

class Generic
    __tostring: (properties)=>
        out = "#{@@__name}"
        if not properties then return out
        for p,v in pairs properties
            out ..= ", #{tostring @[p]}"
        return out

class Vehicle
    new: (@name="GenericVehicle", @position=Position(0, 0, 0)) =>

    move: (amount) =>
        @position += amount

    __tostring: =>
        super.__tostring :name, :position


test_vehicle = Vehicle "Ford Model T"
print tostring test_vehicle  
-- Should print: 
-- Vehicle "Ford Model T", Position(0, 0, 0)

Idyllei avatar Jan 05 '16 19:01 Idyllei

btw __inherited is inherited, unlike the Lua metamethods. So typically you would implement the functionality in a base class and not have to duplicate it anywhere else

leafo avatar Jan 05 '16 19:01 leafo

:+1:

rosshadden avatar Sep 22 '16 17:09 rosshadden

While having a "built-in" class implementation in moonscript is nice, Lua is a lot about do-it-yourself, something I quite like. There are a couple of class implementations for Lua but mine is specifically for moonscript. So, shameless self promo, this might be interesting if the moonscript built-in doesn't do it for you: https://github.com/johnae/classy

johnae avatar Sep 22 '16 18:09 johnae

@krakow10 I know it has nothing to do with this issue, but can you please explain or link me to the next syntax you used in your code block? The lua docs didn't really explain why you would do that over, say, pairs.

Thanks!

rosshadden avatar Dec 01 '16 02:12 rosshadden

@krakow10 I know it has nothing to do with this issue, but can you please explain or link me to the next syntax you used in your code block? The lua docs didn't really explain why you would do that over, say, pairs.

Thanks!

@rosshadden

Hmm... it seems github is not that great of a social platform, I never received a notification for this that I can remember. I'm sure you've learned about it in the past 9 years, but I'll write a reply anyways.

next is a global function which returns the next element in a table. The function pairs(t) simply returns next,t,nil. You can even use this to write your own iterators. Calling v=next(t) can tell you if the table is empty if v is nil, and then calling next(t,v) gives another element distinct from v. All elements are guaranteed to be visited in this way. There is a gotcha where elements may be skipped or revisited if you mutate t in the middle of the loop, however this also applies to using pairs. I was using it so long ago because I saw it as an optimization to skip the call to pairs.

krakow10 avatar May 01 '25 01:05 krakow10