I like the way Scala solves the problem with multiple inheritance as described in https://fuhm.net/super-harmful/ : multiple inheritance is allowed, but only classes (not traits) may have constructors, and a class may only have one parent class (i.e. only one parent with a constructor).
For my part I still prefer composition over inheritance and prefer to do composition "by hand" (i.e. explicit members and delegation) where necessary; I wouldn't use this style at all. But Scala's great strength (and great weakness) is that it has absolutely first-class support for traditional OO like this as well as all the functional stuff.
It means there's a diversity of Scala styles; one person's Scala won't necessarily look like someone else's. More concretely it causes a lot of problems for type inference (particularly when ADTs in Scala are modeled as traits with implementing classes, so e.g. the type of Some(x) ends up as Some rather than Option). Other ML family languages that don't have traditional OO inheritance generally have perfect type inference (at least in the absence of higher kinds) via Hindley-Milner.
Practical type inference for Hindley-Milner-style languages with higher kinds is not a problem, see e.g. [1]. The problem is adding sub-typing.
I'm also not 100% sure it's correct to say that ML-style languages have no problem with type-inference. I'm sure Robin Milner would have preferred having full, unrestricted parametric polymorphism (i.e. System F) in ML. Alas System F has undecidable type inference [2]. Hindley-Milner is a restriction of full parametric polymorphism that enjoys type inference, and is expressive enough to type many programs. BC Pierce says that the Hindley-Milner "has hit a sweetspot". I concur.
That's a good point. It's one of the things that confused me about trait stacking when I first started learning Scala. People often effectively use traits to implement composition, in the sense that the intention isn't that you would take advantage of Liskov substitution, but rather you intend to aggregate behavior from a number of sources.
It's one of many ways in Scala where a single abstract construct could have several different semantics applied to it.
I didn't intend to screw up the actual point, maybe I did but I think the actual point was still understandable, isn't it? If not please let me know the actual point where the article lost it in your case.
traits in Scala doesn't always have to be in a "is a" relationship. If it makes you understand then think of them as adding behavior/mixins. And about why the syntax confused you, the first "with" trait has to be extended, if it could replace extends with "with" in case of traits then maybe you would have liked it better.
Trait in scala represents interface (your type understands some message) or "is a" relation (your type is more specific specialization of type). In your example it is not the case (Tywin is Jamie's father, which should be solved by composition). I do not think that Tywin, Jamie etc. is separate type. In my opinion, those are instances of some type. It is not good practise to call super implementation of method either. You are mixing inheritance and overriding, which in more complex programs always cause trouble (you may break inherited method by changing super implementation). You should solve this by composition (Jamie should not inherit Tywin addToDebt but should depend on Tywin for paying his depts).
I understand what you wanted to show but your example is really confusing and definitely is not correct.
I agree to your point, but was it that difficult to decode or follow till the end? You think A, B, C, D would have been better? Or something more logical names to everyone
No, it wasn't impossible to follow, but the extra layer of indirection made me jump back to the top a few times to make sure I had everything straight.
//Legacy
abstract class Lannister
//Father
trait Tywin extends Lannister
could have just been:
abstract class Family
trait Father extends Family
Otherwise, I appreciate the article - it was informative.
Not really, at all. Order the traits differently and you can get different behavior. Inherit one subtrait vs another and you can get different behavior. You can reason about all of this, but I'm not that smart, so I prefer composition.
Composition locks away the behavior you want so that you know exactly what you're getting.
Yeah -- I'm arguing for composition because it's harder to screw up, at the cost of being less pretty. The manner and order in which composed objects are delegated to is crystal clear if you're wiring them yourself (or with your IDE). I've seen some messy, confusing code involving traits and just slapping 'with Thingy' 10 times in a row. The phrase 'emergent behavior' is how I would best describe it.
I think the point is that if the hierarchy is complex enough that you have to think about it it's simple enough to figure. The cases presented are obviously over complex to describe the behavior.
Maybe to make it more interesting, it turned out to complex as you are saying. But idea was not to use A, B, C etc. Will try to come up with something different next time.
I have explained how linearization works, if you just wanna use it, its very easy. Just goes from right to left and you don't need to remember anything else. About anti pattern, I don't think so. It might not be useful every where, but at some places it makes sense to have stackable behavior.
27 comments
[ 2.8 ms ] story [ 65.3 ms ] threadFor my part I still prefer composition over inheritance and prefer to do composition "by hand" (i.e. explicit members and delegation) where necessary; I wouldn't use this style at all. But Scala's great strength (and great weakness) is that it has absolutely first-class support for traditional OO like this as well as all the functional stuff.
I'm also not 100% sure it's correct to say that ML-style languages have no problem with type-inference. I'm sure Robin Milner would have preferred having full, unrestricted parametric polymorphism (i.e. System F) in ML. Alas System F has undecidable type inference [2]. Hindley-Milner is a restriction of full parametric polymorphism that enjoys type inference, and is expressive enough to type many programs. BC Pierce says that the Hindley-Milner "has hit a sweetspot". I concur.
[1] M. P. Jones, A system of constructor classes: overloading and implicit higher-order polymorphism. http://www.cs.tufts.edu/comp/150GIT/archive/mark-jones/fpca9...
[2] J. B. Wells, Typability and type checking in System F are equivalent and undecidable. http://www.macs.hw.ac.uk/~jbw/papers/f-undecidable-APAL.ps.g...
It's one of many ways in Scala where a single abstract construct could have several different semantics applied to it.
I understand what you wanted to show but your example is really confusing and definitely is not correct.
For those of us who don't know anything about Game of Thrones, and can't pronounce Elvish names, the example in this article reads like:
Flarg → Twipptch → Glorp → Boop → Fwyxnth → AnyRef → Any
So instead of being less boring, I have to use a decoder ring to figure out what's going on here.
https://en.wikipedia.org/wiki/Composition_over_inheritance
http://blog.ryannedolan.info/2014/03/scala-pattern-stackable...
Composition locks away the behavior you want so that you know exactly what you're getting.
Indeed, you could write a straightforward mechanical method to convert any trait stacking into a decoration.