Help request: class, inheritance and elegance

@Jmv38: I have indeed spent a lot of time playing with various mechanisms in Lua looking for a nice, sugary way of doing this. Believe it or not, I consider @toffer’s initial solution to be the best in most cases. Just a reminder of the code:

Button = class(IconBox)

function Button:updateState(data)
    IconBox.updateState(self,data)
    ...
end

As far as elegance goes, pragmatically speaking I think this is the most elegant solution I’ve come across so far, when performance and simplicity are considered. When you remember that the : operator is just syntactic sugar, and visualize the code without it, it becomes much more clear what is actually going on:

Button = class(IconBox)

Button.updateState = function(self, data)
    IconBox.updateState(self,data)
   ...
end

If you just can’t stand having to qualify the base class name, you could always do something like:

local super = IconBox
Button = class(super)

function Button:updateState(data)
    super.updateState(self, data)
    ...
end

This way at least the code is less brittle, in that you only have to update 1 line of code if you change the superclass name.

That’s probably about as good as it gets in Lua, for just one extra line of code per class definition.

Actually, I like being explicit with the class name because, of course, in Lua you don’t have to use the base class when making this kind of call. You can inherit from class A, then pass self to a method from class B. It’s not as neat as real multiple inheritance, but then (as anyone who has written large applications in C++ will testify) it’s also not as confoudingly confusing as multiple inheritance.

Being explicit about the base class you are calling is a good thing. It’s clarity.

Thanks a lot @Toffer and @Toadkick.
I think i’ll go for last proposal of Toadkick.
But i think i tried it already and ran into problems. I have to work on it to show exactly where the problem was (maybe it was just a bug from me).

@Mark: I tend to agree with you. Normally it sort of feels like a violation of the DRY principle to explicitly specify the class name in every overridden method (because it can be factored out), but I’ve been bitten by it exactly 0 times so practically speaking I think it’s the simplicity is worth it.

Hello guys. Here is my final test. I was confused because test1() works at first sublevel but explodes at second sublevel. Test1 provides the super:copy(x) syntax which was desirable. But it does not work So i will use test2 method, which is extremely compact. thanks for your help!


-- test class heritage


-- Use this function to perform your initial setup
function setup()
    -- this works
    test2() 

    -- this generates a stack overflow on c
    test1() 

end

function test2()
    print("method without ':'")
    A = class()
    function A:init() end
    function A:copy(x) return x end
    
    B = class(A)
    function B:init() end
    local super = A
    function B:copy(x) y = super.copy(self,x) return y end
    
    C = class(B)
    function C:init() end
    local super = B
    function C:copy(x) y = super.copy(self,x) return y end
    
    a = A()
    print("a:copy(5) = ",a:copy(5))
    b = B()
    print("b:copy(5) = ",b:copy(5))
    c = C()
    print("c:copy(5) = ",c:copy(5))
end

function test1()
    print("method with ':'")
    A = class()
    function A:init() end
    function A:copy(x) return x end
    
    B = class(A)
    function B:init() end
    B.super_copy = A.copy
    function B:copy(x) y = self:super_copy(x) return y end
    
    C = class(B)
    function C:init() end
    C.super_copy = B.copy
    function C:copy(x) y = self:super_copy(x) return y end
    
    a = A()
    print("a:copy(5) = ",a:copy(5))
    b = B()
    print("b:copy(5) = ",b:copy(5))
    c = C()
    print("c:copy(5) = ",c:copy(5))
end

@Jmv38 - thank you (that nice blue color is totally essential) :wink:

@ignatz i was sure you would be sarcastic about that :wink:

@ignatz the results are: @Toffer has proven it is possible to modify the class() definition so that in the following code:

MyClass2 = class(MyClass1) 
myObject = MyClass2()
Function MyClass2:func()
    A = self.super:func()
    B = MyClass1.func(self)
End

Then A does exacly the same as B. The interest of A vs B is to

  • improve the functions progressively,
  • while keeping the same function name,
  • using a generic keyword (super) to access base class,
  • having the pleasant syntax self.XXX:func() instead of XXX.func(self),
  • and having the nice blue color in the editor for A.
    BUT:
    The construct that enable that is very slow, so the best way to do it it to stick with the syntax proposed by @Toadkick:
MyClass2 = class(MyClass1) 
Local super = MyClass1
myObject = MyClass2()
Function MyClass2:func()
    A = super.func(self)
    B = MyClass1.func(self)
End

Then A does exacly the same as B. The interest of A vs B is to

  • improve the functions progressively,
  • while keeping the same function name,
  • using a generic keyword (super) to access base class,
    But we dont have:
  • the pleasant syntax self.XXX:func() instead of XXX.func(self).
  • the nice blue color in the editor for A.

Is that more clear now?

@Jmv38 - don’t forget to write up the results in a way we can all understand - please! :-??

I looked at how classes were implented. Your inelegant solution is the best since classes were implemented as a shallow copy of super rather than passing unfound method calls to super. I’m assuming this was done for speed. You could always just implement your own class creation method to implement super capabilities and use it instead.

@Toffer and @Toadkick just a status after one month of usage of super:
Well THANK YOU SO MUCH for pointing out this concept of super to me! It has changed my life as a programmer in Codea: i have the feeling it was the missing link to break through the complexity wall i talked several times about.
Before that, i was struggling with names and subnames and getting lost. Now i can start with a small set of names in a class and progressively enrich it with the same function names. That make a real difference.
This is so important i think this inheritance mechanism should be explained in he wiki, in the class section.
My dream would be if it was included inside the class function itself, so instead of doing:

Class2 = class(Class1)
Super = Class1
Function Class2:myfunc() 
    super.myfunc(self)
    New functionnalities....
End

i could write

Class2 = class(Class1)
Function Class2:myfunc() 
    Self.super:myfunc()
    New functionnalities....
End

I know it is against the rules, because Self.super:myfunc() would then be a shortcut for Self.super.myfunc(self) and not Self.super.myfunc(Self.super), but that would be great.
I would really like to implement that and test it, but i am not familiar with metatables…

@Jmv38: FWIW, I’ve written so many different experimental class() implementations that I’ve forgotten everything I’ve tried, but if I recall correctly, the closest I’ve ever come is a construct like this:

function Class2:myfunc()
    self.class.super.myfunc(self)
end

Frankly, I don’t see that as an improvement. self.class.super.myfunc(self) is so much more verbose than Class1.myfunc(self) or super.myfunc(self), and besides, having to express “self” twice is very annoying to me.

Also, I can’t wait until you learn metatables. It’s going to blow your mind :smiley:

@Toadkick but we could certainly modify the class() function itself so that:

  • it adds a super field to the object: self.super. (this one is for sure).
  • it interprets self.super:myfunc() as self.super.myfunc(self). (super should not be the superclass, but an instance of superclass with same self as the instanciated class self… Or the opposite :wink: )
    Cant we?

@Jmv38:

  • Question 1:
    Yes, it’s easy enough to add a super field to the created instance. I don’t exactly like that though because it’s confusing: self.super sort of implies that you are accessing a superinstance, not a superclass. self.class.super would be more orthogonal (though currently there is not a self.class either). However, this is mainly a nitpick. I think it would be a net win to be able to access the superclass, whether through self.class.super or through self.super.

  • Question 2:
    No, this is not possible. There is no way within Lua to change the meaning of the : operator. self.super:myfunc() will always evaluate to self.super.myfunc(self.super), and that can only be changed by modifying Lua itself (and IMO, it wouldn’t be worth it).

Q2: so we have to define self.super in such a way that self.super.myfunc(super) returns class1.myfunc(self)

Well, I guess what I’m saying is, that’s not possible. There is no way in Lua to do that. Period.

Dont dare me! :-?

I wish it was a dare! Unfortunately, it’s a fact :frowning:

I think the heart of it is, the construct you are trying to achieve doesn’t actually make sense, which would also explain why I’m having such a hard time explaining technically why it’s impossible :smiley:

"Q2: so we have to define self.super in such a way that self.super.myfunc(super) returns class1.myfunc(self)"

So: We can make self.super return self’s superclass; if self is an instance of Class2, which inherits from Class1, then the expression self.super would evaluate identically to the expression Class1. In other words, self.super.myfunc(self) is now functionally identical to Class1.myfunc(self).

But, you can’t change the meaning of :. It always translates from:

t:func()

to

t.func(t)

That is to say, the table directly in front of the : will always be the table that gets passed into the function, and there is no way to change that.

So, self.super:myfunc() will always evaluate to self.super.myfunc(self.super), and that is clearly not correct (we want self to be passed into myfunc, not self.super).

Hopefully that makes sense :slight_smile: If it doesn’t, then I apologize that I’m often terrible at explaining things :slight_smile:

@Toadkick thanks a lot for taking the time to explain. I agree. But i feel there is a way to get the result i want, maybe by changing a few things. I am not sure of course, especially because you as an expert has not succeded to, but i love challenges, so i am currently studying metatables to see if there is really no way. Since i am not used to it, it may take some time before i agree with you or show something that works (which will not be what you describe above, but slightly different, but i dont know what yet). Thank you anyway for your great help!

@Jmv38: Well, don’t let me discourage you! You may indeed find a better way to handle this, and even if you don’t, learning metatables along the way will still be very valuable and rewarding in itself :slight_smile: