Multiple inheritance alternatives

Monkey Forums/Monkey Programming/Multiple inheritance alternatives

arpie(Posted 2016) [#1]
Hello all,

Multiple inheritance is generally considered evil, probably for good reason. But occasionally I come across a problem that would be very well solved by MI. Various languages have alternative offerings (I particularly like PHP's mixins). I was wondering how people would implement the following in Monkey.

For the sake of this example, lets consider a (massively oversimplified) GUI implementation :
- I have a Container class - basically a wrapper around Stack or Set.
- I have a Widget class - in essence a class that provides fields x,y,width,height and method Render().

The Container class is all-sorts-of useful outside of the GUI so deserves a dedicated reusable implementation.
The Widget class is unique to this GUI and provides the core functionality of a GUI element - it stores position (and speed, location, parent, origin...) and provides methods for manipulation (MoveTo(), Hide(), Click(), Highlight()...). It can be extended to create all sorts of specific widgets (Button, Text, Slider...)

A GUI consists of Widget objects contained in Frames. Frames can also contain Frames.

How do I define a Frame class, which clearly requires attributes from both Container and Widget?

Interface IWidget
  Method Render
End Interface

Interface IContainer<T>
  Method Add(t:<T>)
  Method Remove(t:<T>)
End Interface

Class Widget Implements IWidget
  Field x:Float, y:Float, w:Float, h:Float
  Method Render()
    DrawRectangle(x, y, w, h)
  End Method
  Method MoveTo(x, y)
    Self.x = x; Self.y = y
  End Method

' ...etc...

End Class

Class Container<T> Implements IContainer<T>
  Field items := New Stack<T>
  Method Add(t:T)
    items.Push(t)
  End Method
  Method Remove(t:T)
    items.RemoveEach(t)
  End Method
  Method All:Stack<T>()
    Return items
  End Method
End Class


So far so good... but what about the frame class? My Container and Widget classes are massively simplified, but even with a lot more functionality the Frame class clearly shares a lot of both Class's fields and methods. This could (theoretically) be achieved with plain old multiple inheritance, with no evil side-effects. But...in my mental model, the Frame should derive from Widget (but has Container-like properties). My ideal solution would be this :

Class Frame Extends Widget Mixin Container<Widget>
  Method Render
    For w := Eachin All
      w.Render()
    Next
  End Method
End Class


Again, this is highly simplified but basically the entire difference between a Frame and a Widget is its ability to Contain other widgets. So I want to be able to call myFrame.Add(myWidget) and the like. Which means it is a Container. As well as a Widget. PHP-style Mixins make this super-easy. But this is not PHP.

Am I missing something obvious? Without going the route of duplicating code by doing
Class Frame Extends Widget Implements IContainer


Then rewriting the Add, Remove, etc. Methods within Frame, is there an elegant solution that a more Monkey-savvy coder might employ? If I decide all my containers should use Set instead of Stack, I would have to change them all, not just the Container Class.

Thanks in advance for all suggestions.


ImmutableOctet(SKNG)(Posted 2016) [#2]
CRTP (Well, not quite, but it's the same concept), my old friend, we meet again! It's the static way of dealing with multiple-inheritance. If you want something more dynamic, you'll probably need multiple objects, or to simply change the structure of your API.

As a side note, multiple inheritance is fine for some situations, but there's usually better methods. Honestly, I wish Monkey had "mixins" like other languages have (D), that would make things simpler.



Not exactly perfect, there's some ordering issues you need to solve (Usually with a common derivative class you base off of), but it works like "mixins". The real problems come from access, rather than implementation. In something like a game, idioms like CRTP shine, because you can keep details far away from each other. Again, having a simplified intermediate class and/or an interface will help. Just keep compatibility in mind when using this strategy.

EDIT 01: I should also bring up generic interfaces, as they do work, even if they're not documented. Also, using 'Object' for 'SuperType' should technically work, if you want to go that route.

EDIT 02: I should mention that despite it making logical sense, Monkey 1 doesn't allow self-referencing in class arguments. Supposedly, Monkey 2 is fixing this.


ziggy(Posted 2016) [#3]
I would make an interface oriented design so a widget is any class that implements the widget interface, then a container is any class that implements the container interface. No harm in implementing both on any class.

That said, I would considere taking into account a design that enforces composition over inheritance.


ziggy(Posted 2016) [#4]
double post


arpie(Posted 2016) [#5]
Thank you both for your replies.

@ziggy : From a global design standpoint I think you are spot on, and this is an ideal example of where composition would be useful. But I have never liked using composition as it creates an extra level of abstraction by having a whole load of function wrappers (method forwarding) in the derived classes (If I add a new method to a component class, I have to add a wrapper to all of the derived classes. Admittedly this would happen rarely and direct derivatives should be few in number so maybe I'm just lazy). Anyway, composition always strikes me as inefficient, although I can already hear people shouting "You shouldn't be prematurely optimising your code - do some real world testing!". Now if Monkey had Inline functions, like in C, then I'd be happier :)

@Immutable Octet : I hesitated to mention D as I assumed it wasn't very well known. It is currently my favourite language but I have yet to find a project where I can actually use it (D compiler for android anybody?!?).

Anyway, thank you for showing me something brand new. I thought I had seen most OO concepts at least in passing but I have never even heard of CRTP! What a fantastic concept! Unfortunately, as you point out in Edit02, it doesn't seem to work directly in MonkeyX. If I use the general form example from the wikipedia article you linked to, Monkey complains about the cyclic reference in the Derived Class declaration:
Class Base<T>
End Class

Class Derived Extends Base<Derived>
End Class


I am struggling to see how your example gets round this issue. Surely in your example, the Element class would have to define forwarding methods to all of the Container class's methods? In which case I don't see the advantage of your example over simply using classic inheritance and writing
Class Widget
...
End Class

Class Container
...
End Class

Class Element Extends Widget Implements IContainer
  Field container := New Container<Element>
  ' Widget's methods are inherited here but we must
  ' Declare methods that wrap all of containers methods, forwarding them to the container field
End Class


I presume I have misunderstood? Any chance you can shed some more light on your example?

Thanks in advance


arpie(Posted 2016) [#6]
Got it!

As I suspected, @immutableoctet : your code was spot on and it was me who hadn't quite understood your example. Although, as far as I can see you would have been fine just using Container<T>, without the SuperType (or even Stack<T>). I simplified it and changed the name SuperType to Mixin (which I find more intuitive, although perhaps less accurate) and this works perfectly:
'Empty class for when you don't need a mixin to make things clearer
Class NoMixin
End Class

Interface IWidget
  Method Render()
End Interface

Class Widget<TMixin> Extends TMixin Implements IWidget
  Method Render()
    Print("Here I Am")
  End Method
End Class

Class Button Extends Widget<NoMixin> Implements IWidget
End Class

Class Frame Extends Widget<Stack<IWidget>> Implements IWidget
End Class


For the benefit of anybody else reading this and wondering what is going on, the magic is in the following line :
Class X<Mixin> Extends Mixin
End Class


That allows you to simultaneously declare a class and specify its parent class. What a useful trick!