6

So, I was trying to make a finagle server, talk to sentry (not important), and stumbled upon a case, where I needed to inherit from two classes (not traits) at the same time, let's call them class SentryHandler extends Handler and class TwitterHandler extends Handler, and assume, that I need to create MyHandler, that inherits from both of them.

After a moment of stupidity, when I thought it was impossible without using a dreaded "delegation pattern", I found a solution:

trait SentryTrait extends SentryHandler
class MyHandler extends TwitterHandler with SentryTrait

Now, this got me thinking: what is the purpose of having the notion of "trait" to being with? If the idea was to enforce that you can inherit from multiple traits but only a single class, it seems awfully easy to get around. It kinda sounds like class is supposed to be the "main" line of inheritance (that you "extend a class with traits", but that isn't true either: you can extend a trait with (or without) a bunch of other traits, and no class at all.

You cannot instantiate a trait, but the same holds for an abstract class ...

The only real difference I can think of is that a trait cannot have constructor parameters. But what is the significance of that? I mean, why not? What would the problem with something like this?

class Foo(bar: String, baz: String) extends Bar(bar) with Baz(baz) 
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
Dima
  • 39,570
  • 6
  • 44
  • 70
  • Main idea is to enhance classical interfaces with implementation you can extend class with. Don't see much to add .. – Pavel Mar 18 '16 at 11:10
  • @PavelOliynyk well, that's the point: there is already a term for an "interface enhanced with implementation" - it's called a "class" :). So, the question is why create a new term rather than using an existing one. – Dima Mar 18 '16 at 11:12
  • 3
    if your hierarchy looks like this: `trait Handler; class SentryHandler extends Handler; class TwitterHandler extends Handler; trait SentryTrait extends SentryHandler; class MyHandler extends TwitterHandler with SentryTrait` this doesn't compile on the REPL. – Ende Neu Mar 18 '16 at 11:13
  • Game of words :) I would say that something everyone have to accept. – Pavel Mar 18 '16 at 11:13
  • A `trait` and an `abstract class` posses two different relationships in relation to a entity. The former is a "has-a" relationship, while the latter has an "is-a" relationship. Doesn't matter if a `trait` holds a default implementation or not, IMO. Other than that, a `trait` with a default implemention isn't fully compatible with Java, while an `abstract class` is. Also, [this question](http://stackoverflow.com/questions/1991042/what-is-the-advantage-of-using-abstract-classes-instead-of-traits) covers most questions. – Yuval Itzchakov Mar 18 '16 at 11:38
  • 1
    @YuvalItzchakov in theory, yes, but in practice ... :-/ For example, `Either` is a class, but `Future` is a trait (while "has a Future" doesn't even make any sense). `Option` is a class, but `Map` is a trait ("has-a Map"???). `List` is a class, but `Seq` is a trait ... etc. Clearly, the decision of whether the type you are designing should be a class or a trait is based on a plethora of considerations other than "is-a" vs. "has-a" dilemma, which is rather philosophical (in a bad sense, as in having no practical significance). – Dima Mar 18 '16 at 13:24
  • I tend to agree with @Dima here. The real culprit has already been explained by Ende Neu: your "solution" does not compile, so any conclusion you derived from that code is invalid. The reality is that no, you cannot extend two unrelated classes in scala, but you can mix two unrelated traits. – Régis Jean-Gilles Mar 18 '16 at 13:54
  • @RégisJean-Gilles yes, that's a bummer (not sure, if you saw my comment to the answer below, basically, I spoke too soon, because my IDE lied to me, and tricked me into believing it would work). So, it is clear now _how_ traits are different from classes. The question, that still remains though, is _why_ it has to be this way. – Dima Mar 18 '16 at 13:58
  • What's so dreaded about delegation? – Mifeet May 18 '16 at 12:17

4 Answers4

7

Your solution (if I understood correctly) - doesn't work. You cannot multiinherit classes in scala:

scala> class Handler
defined class Handler

scala> class SentryHandler extends Handler
defined class SentryHandler

scala> class TwitterHandler extends Handler
defined class TwitterHandler

scala> trait SentryTrait extends SentryHandler
defined trait SentryTrait

scala> class MyHandler extends TwitterHandler with SentryTrait
<console>:11: error: illegal inheritance; superclass TwitterHandler
 is not a subclass of the superclass SentryHandler
 of the mixin trait SentryTrait
       class MyHandler extends TwitterHandler with SentryTrait

As for the question - why traits, as I see it, this is because traits are stackable in order to solve the famous diamond problem

  trait Base { def x: Unit = () }
  trait A extends Base { override def x: Unit = { println("A"); super.x}}
  trait B extends Base { override def x: Unit = { println("B"); super.x}}

  class T1 extends A with B {}
  class T2 extends B with A {}

  (new T1).x  // Outputs B then A
  (new T2).x  // Outputs A then B

Even though trait A super is Base (for T1) it calls B implementation rather then Base. This is due to trait linearization

So for classes if you extend something - you can be sure that this base will be called next. But this is not true for traits. And that's probably why you do not have trait constructor parameters

Community
  • 1
  • 1
Archeg
  • 8,364
  • 7
  • 43
  • 90
  • Ouch, that's a bummer. I was tricked by my IDE into believing that inheritance would work :( Too bad. Well, now I know what is differentiates traits from classes, but still don't understand _why_ it needs to be this way. The linearization is a good theory, but I don't see any reason why the same process could not be applied to classes. – Dima Mar 18 '16 at 12:22
  • @Dima There are some languages like Perl or Python where I believe they did the stuff you are talking about. But I think this makes working with classes a little bit more difficult. You would want to avoid linearization - because it is complex. I am not that familiar with Perl or Python, but I think Python classes always have default constructor. And you cannot call other constructor when inheriting. And this is because of these problems. So the natural way to get rid of these problems - introduce traits. – Archeg Mar 18 '16 at 12:42
  • @Dima There is also other thing about all of this. I don't think this is the reason why traits were introduced, but it definetely affected the decision. Scala is based on Java which does not have multi inheritence. So you just cannot make classes multiinherit in Scala – Archeg Mar 18 '16 at 12:43
  • I am sure it's not the _name_ (trait vs. class) that let's you multiinherit :) Scala has some clever tricks to allow classes inherit from multiple traits, and still work with java. I don't see how replacing the word "trait" with "class" in the last sentence would make it any more or less difficult. – Dima Mar 18 '16 at 13:19
  • @Dima Scala compiles traits into classes. You can't do that with classes and multiinheritence and expect Java to see those classes afterwards – Archeg Mar 18 '16 at 13:20
  • I don't follow: if it can compile traits into classes, why could not it compile classes into classes? – Dima Mar 18 '16 at 13:26
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/106724/discussion-between-dima-and-archeg). – Dima Mar 18 '16 at 13:26
  • @Dima Scala actually compiles traits into interfaces, so far as inheritance is concerned. If it used the same approach to compile classes, then the rule would be that you can't inherit from multiple classes defined in Java, which would be worse (IMO). – Alexey Romanov Mar 18 '16 at 14:41
  • @AlexeyRomanov I think scala compiles "simple" (no value or method implementations) traits into interfaces, but more complex into classes. – Ende Neu Mar 18 '16 at 15:16
  • @EndeNeu It's both. But it may skip comping into classes if trait does not have an implementation – Archeg Mar 18 '16 at 15:35
  • @AlexeyRomanov this cannot be true entirely, because interfaces in java cannot have concrete methods (or could not before 1.8 anyway), so simply compiling a trait into an interface would not work, it has to be more complicated than that, like other pointed out. – Dima Mar 18 '16 at 15:44
  • It's actually a combination of both http://stackoverflow.com/questions/2557303/how-are-scala-traits-compiled-into-java-bytecode – Ende Neu Mar 18 '16 at 16:44
  • @Dima "so far as inheritance is concerned". I meant that when you have `trait SomeTrait`, `SomeTrait` is an interface in bytecode and `Foo extends SomeTrait` puts `SomeTrait` into the interface table of `Foo`, not as the superclass. Obviously, classes are needed as well if the trait has implementation or initialization code. – Alexey Romanov Mar 19 '16 at 12:02
  • @AlexeyRomanov I don't see your point. So, to inherit for a trait, scala generates an interface, right? What is it that would prevent it to do the same if a trait was a class? – Dima Mar 19 '16 at 12:17
  • @Dima 1. The limitations on traits (no constructor parameters, etc.) aren't random. They are what the Scala developers needed to allow for multiple inheritance. So "classes" would need the same limitations if they were to be compiled in the same way. I.e. you are now asking not "why do we need traits" but "why do we need classes". – Alexey Romanov Mar 19 '16 at 22:34
  • @Dima 2. Ignoring the first point (say you don't care about the limitations, or find a way to work around them), the Java compiler certainly won't be using these tricks. So if `class Foo1` and `class Foo2` are both defined in Java, you still couldn't write `class Foo3 extends Foo1 with Foo2` in Scala; if one or both are defined in Scala, you could. So since you need to know the difference, why not call "Java-defined classes" `class`, and "Scala-defined classes" `trait`? But then it seems useful to allow Scala to define `class`es as well. And that's precisely the current situation. – Alexey Romanov Mar 19 '16 at 22:39
  • @AlexeyRomanov (1) it may very well be "what they needed", my question is _why_ exactly they needed it, because so far I just don't see any technical reason for such limitation. (2) actually makes sense ... Basically, you are saying, that everything, that extends a java class, has to be a class, because it cannot extends anything else that also extends a java class. That sounds reasonable. I'll think about it. – Dima Mar 19 '16 at 22:54
  • @Dima 1. See my answer. 2. Actually, a trait can extend a class, but this is rarely used (and if it is, you _can_ run into analogous trouble where you can't extend the trait because you have a superclass already). – Alexey Romanov Mar 20 '16 at 07:52
2

The question should rather be: why do we need classes in Scala? Martin Odersky has said that Scala could get by with just traits. We would need to add constructors to traits, so that instances of traits can be constructed. That's okay, Odersky has said that he has worked out a linearization algorithm for trait constructors.

The real purpose is platform interoperability.

Several of the platforms Scala intends to integrate with (currently Java, formerly .NET, maybe in the future Cocoa/Core Foundation/Swift/Objective-C) have a distinct notion of classes, and it is not always easy to have a 1:1 mapping between Scala traits and platform classes. This is different, for example, from interfaces: there is a trivial mapping between platform interfaces and Scala traits – a trait with only abstract members is isomorphic to an interface.

Classes, packages, and null are some examples of Scala features whose main purpose is platform integration.

The Scala designers try very hard to keep the language small, simple, and orthogonal. But Scala is also explicitly intended to integrate well with existing platforms. In fact, even though Scala is a fine language in itself, it was specifically designed as a replacement for the major platform languages (Java on the Java platform, C# on the .NET platform). And in order to do that, some compromises have to be made:

  • Scala has classes, even though they are redundant with traits (assuming we add constructors to traits), because it's easy to map Scala classes to platform classes and almost impossible to map traits to platform classes. Just look at the hoops Scala has to jump through to compile traits to efficient JVM bytecode. (For every trait there is an interface which contains the API and a static class which contains the methods. For every class the trait is mixed into, a forwarder class is generated that forwards the method calls to trait methods to the static class belonging to that trait.)
  • Scala has packages, even though they are redundant with objects. Scala packages can be trivially mapped to Java packages and .NET namespaces. Objects can't.
  • Package Objects are a way to overcome some of the limitations of packages, if we didn't have packages, we wouldn't need package objects.
  • Type Erasure. It is perfectly possible to keep generic types around when compiling to the JVM, e.g. you could store them in annotations. But third-party Java libraries will have their types erased anyway, and other languages won't understand the annotations and treat Scala types as erased, too, so you have to deal with Type Erasure anyway, and if you have to do it anyway, then why do both?
  • null, of course. It is just not possible to automatically map between null and Option in any sane way, when interoperating with real-world Java code. You have to have null in Scala, even though we rather wished it weren't there.
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
2

The problem with having constructors and state in a trait (which then makes it a class) is with multiple inheritance. While this is technically possible in a hypothetical language, it is terrible for language definition and for understanding the program code. The diamond problem, mentioned in other responses to this question), causes the highest level base class constructor to be called twice (the constructor of A in the example below).

Consider this code in a Scala-like language that allows multiple inheritance:

Class A(val x: Int)
class B extends A(1)
class C extends A(2)
class D extends B, C

If state is included, then you have to have two copies of the value x in class A. So you have two copies of class A (or one copy and the diamond problem - so called due to the diamond shape of the UML inheritance diagram).

Diamond Multiple Inheritance

The early versions of the C++ compiler (called C-Front) had lots of bugs with this and the compiler or the compiled code often crashed handling them. Issues include if you have a reference to B or C, how do you (the compiler, actually) determine the start of the object? The compiler needs to know that in order to cast the object from the Base type (in the image below, or A in the image above) to the Descendant type (D in the image above).

Multiple Inheritance Memory Layout

But, does this apply to traits? The way I understand it, Traits are an easy way to implement composition using the Delegation Pattern (I assume you all know the GoF patterns). When we implement Delegation in any other language (Java, C++, C#), we keep a reference to the other object and delegate a message to it by calling the method in its class. If traits are implemented in Scala internally by simply keeping a reference and calling its method, then traits do exactly the same thing as Delegation. So, why can't it have a constructor? I think it should be able to have one without violating its intent.

dsa42
  • 21
  • 1
  • You can have state and diamond inheritance with traits just as well as with classes, there is nothing preventing you from that. It's true, that you can't have (non-default) constructors in a trait, but I fail to see how it makes anything better. And no, traits aren't implemented by keeping a reference. They are actual real classes, generated on the fly by the compiler. It is not the delegation pattern at all, it's the actual inheritance. – Dima Oct 11 '16 at 19:58
-1

The only real difference I can think of is that a trait cannot have constructor parameters. But what is the significance of that? I mean, why not?

Consider

trait A(val x: Int)
trait B extends A(1)
trait C extends A(2)
class D extends B with C

What should (new D {}).x be? Note: there are plans to add trait parameters in Scala 3, but still with restrictions, so that the above is not allowed.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • 1
    You can do `trait A {def x: Int}`, and then have two traits override it and inherit from both. Vals could be handled in the same way. – Dima Mar 20 '16 at 12:58