Scala: abstract class constructor parameter vs Trait val members? - scala

I notice that there were several discussions about how to choose between abstract classes and traits, but it seems that none of them focused on the following point. One reason that made me use abstract classes is, they can have constructor parameters, while traits cannot. But why not the following
trait X {
def haha: Int
}
class Y(val haha: Int) extends X
and early definition is even not necessary to get everything work properly (which I worried about). The abstract class version is
abstract class X(haha: Int)
class Y(val haha: Int) extends X(haha)
and I don't like the abstract class version because, when you extend several times, these constructor parameters appear everywhere (maybe someone tells me how to avoid this?).
I am aware that abstract classes interpolate with Java better, and match the "is-a" concept more. Despite these, is there any reason that I should use abstract classes somewhere? Thanks!

The class parameter does not have to be a member (field or def).
abstract class X(haha: Int) {
val hoho = 2 * haha // compile-time constant
}
Similarly, trait initialization order depends on linearization (mix-in order), which is why trait members should be defs and not vals. (And you can always override the def with a val.) With an abstract class, you know who your supers are, and you're defining extension points for subclasses.
But note your abstract class has the val in the wrong place:
abstract class X(val haha: Int)
class Y(haha: Int) extends X(haha)
That is, you would expect X to decide if the param is a val (and it doesn't have to be). Usage of the param in either X or Y could turn it into a field.
Your observation about value params for classes also applies to type params: What a nuisance to pass Foo[A] up the hierarchy. So in Scala, we can have a member type A instead that can remain abstract until defined in a leaf. But this doesn't actually bear on whether to define a trait or a class.
But trait parameters are coming to Scala. (See the Scala bugs for early definitions which are low-priority for this reason.)

Related

What's the difference between abstract classes with zero parameters, and those with no parameters?

In Scala, what's the difference between:
abstract class Foo
// And
abstract class Bar()
There doesn't seem to be a difference between them when inheriting:
// All of these seem to work
case class FooA() extends Foo()
case class FooB() extends Foo
case class BarA() extends Bar()
case class BarB() extends Bar
Are these really all equivalent, or is there a difference? And if they are all equivalent, which abstract class definition should I use, Foo or Bar?
I know methods make this distinction because of currying. Can you curry when constructing a class? Is that why this weird duplication exists?
There is no difference. An abstract class is just a class with the abstract modifier, which imposes the rule that you cannot instantiate it unless the abstract members are implemented in some way (whether through mixins, etc). And there is no difference been a parameterless class and a class with an empty parameter list. If a class has no parameter list, the compiler assumes an empty one.
This is stated clearly in the SLS Section 5.3:
If no formal parameter sections are given, an empty parameter section () is assumed.
Since they are equivalent, it's really just a matter of taste which you should use. I prefer to omit the parentheses for brevity, but the compiler will add them back in anyway.
The only time this can make things confusing is when you have a class with only implicit parameters. For example, this:
abstract class Foo(implicit bar: Bar)
is actually
abstract class Foo()(implicit bar: Bar)
which causes confusion when one tries to supply the implicit manually, and the parentheses must be supplied. e.g.
new Foo()(new Bar)

Trait that, when mixed in, turns a class into a final class

Is it possible, in Scala, to have a trait that makes the class into which it is mixed in final?
I need to prevent inheriting from a class if this class mixes in a trait, but I'm not sure if this is possible. Maybe I need a macro for that.
Or a trick, like defining a final member in the trait to avoid mixing that trait twice, but this is not what I need to achieve.
Example:
trait MyTrait
class A extends MyTrait
class B extends A
def myFun[T <: MyTrait](t: T)
This function not only requires T to implement/mixin MyTrait, but suppose it contains logic that doesn't work if T is a base class and what is actually passed in as t is a child of T. For example myFun[A](new B).
Clearly this is a corner case but maybe there's a way to ensure/specify that the t passed as parameter must be exactly of type T and not one of its children. I don't know if this is possible, and that would be the best solution.
The closest thing I could find is making sure T is final and the best would be writing something in MyTrait that enforces it.
So my question.
Alternatively, how can I make sure T is a case class?
No, it isn't. Macros won't help either: you can't trigger a macro by extending something, only by an annotation or by calling a method (as far as I know).
What about this approach:
trait MyTrait
class A extends MyTrait
class B extends A
def myFun[T](t: T)(implicit ev: T =:= A) = ()
defined function myFun
# myFun(new A)
# myFun(new B)
Main.scala:61: Cannot prove that cmd2.B =:= cmd1.A.
myFun(new B)
^
Compilation Failed
It's a bit different from what you ask, since it has nothing to do with the trait. It just enforces you can only pass type A into that function, not its children.

Why do we need traits in scala?

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)
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
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.
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.
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.

Why it is impossible to define a this-constructor in the trait?

I read in Martin Odersky's book that trait extends a superclass AnyRef. So it's like a class for me. I know I cannot use default constructor in the trait which I usually use in the class
class B(s: String)
At the same time, I can see that it's impossible to do something like this
trait A {
def this(s: String) {
super()
}
}
Compiler says:
Error:(14, 7) 'this' expected but 'super' found.
super()
^
Why is that so?
Straight from the Scala Language Specification:
A trait is a class that is meant to be added to some other class as a mixin. Unlike normal classes, traits cannot have constructor parameters. Furthermore, no constructor arguments are passed to the superclass of the trait. This is not necessary as traits are initialized after the superclass is initialized.
What it comes down to is:
traits boil down to java interfaces, which have no parameters, which is nice for java-interop.
While it may be theoretically possible to allow traits to have
constructor parameters, it makes the language more complex than it
needs to be. There is almost certainly a way to accomplish the same functionality, without the need for trait constructor parameters.

What is a sealed trait?

Sealed classes are described in 'Programming in Scala', but sealed traits are not.
Where can I find more information about a sealed trait?
I would like to know, if a sealed trait is the same as a sealed class?
Or, if not, what are the differences?
When is it a good idea to use a sealed trait (and when not)?
A sealed trait can be extended only in the same file as its declaration.
They are often used to provide an alternative to enums. Since they can be only extended in a single file, the compiler knows every possible subtypes and can reason about it.
For instance with the declaration:
sealed trait Answer
case object Yes extends Answer
case object No extends Answer
The compiler will emit a warning if a match is not exhaustive:
scala> val x: Answer = Yes
x: Answer = Yes
scala> x match {
| case No => println("No")
| }
<console>:12: warning: match is not exhaustive!
missing combination Yes
So you should use sealed traits (or sealed abstract class) if the number of possible subtypes is finite and known in advance. For more examples you can have a look at list and option implementations.
a sealed trait is the same as a sealed class ?
As far as sealed goes, yes. They share the normal differences between trait and class, of course.
Or, if not, what are the differences ?
Moot.
When is it a good idea to use a sealed trait (and when not) ?
If you have a sealed class X, then you have to check for X as well as any subclasses. The same is not true of sealed abstract class X or sealed trait X. So you could do sealed abstract class X, but that's way more verbose than just trait and for little advantage.
The main advantage of using an abstract class over a trait is that it can receive parameters. That advantage is particularly relevant when using type classes. Let's say you want to build a sorted tree, for instance. You can write this:
sealed abstract class Tree[T : Ordering]
but you cannot do this:
sealed trait Tree[T : Ordering]
since context bounds (and view bounds) are implemented with implicit parameters. Given that traits can't receive parameters, you can't do that.
Personally, I prefer sealed trait and use it unless some particular reason makes me use a sealed abstract class. And I'm not talking about subtle reasons, but in-your-face reasons you cannot ignore, such as using type classes.
From the daily-scala blog:
When a trait is "sealed" all of its subclasses are declared within the
same file and that makes the set of subclasses finite which allows
certain compiler checks.
Also I feel the need to point you to the specifications:
The sealed modifier applies to class definitions. A sealed class may not be directly inherited, except if the inheriting template is defined in the same source
file as the inherited class. However, subclasses of a sealed class can be inherited anywhere.
— M. Odersky. The Scala language specification, version 2.8. online, Sept., 2013.
‌‌Briefly:
Sealed traits can only be extended in the same file
List this lets the compiler easily know all possible subtypes
Use sealed traits when the number of possibly subtypes is finite and known in advance
A way of creating something like enum in Java
Help to define algebraic data types (ADTs)
and for more details
Everything about sealed traits in Scala
Traits can also be defined sealed, and only extended by a fixed set of case classes.
The core difference between normal traits and sealed traits can be summarized as follows:
Normal traits are open, so any number of classes can inherit from the trait as long as they provide
all the required methods, and instances of those classes can be used interchangeably via the trait's
required methods.
A normal trait hierarchy makes it easy to add additional sub-classes: just define your class and
implement the necessary methods. However, it makes it difficult to add new methods: a new
method needs to be added to all existing subclasses, of which there may be many.
Sealed traits are closed: they only allow a fixed set of classes to inherit from them, and all
inheriting classes must be defined together with the trait itself in the same file or REPL command.
A sealed trait hierarchy is the opposite: it is easy to add new methods, since a new method can
simply pattern match on each sub-class and decide what it wants to do for each. However, adding
new sub-classes is difficult, as you need to go to all existing pattern matches and add the case to
handle your new sub-class.
As an example
object SealedTraits extends App{
sealed trait Point
case class Point2D(x: Double, y: Double) extends Point
case class Point3D(x: Double, y: Double, z: Double) extends Point
def hypotenuse(p: Point) = p match {
case Point2D(x, y) => math.sqrt(x x + y y)
case Point3D(x, y, z) => math.sqrt(x x + y y + z z)
}
val points: Array[Point] = Array(Point2D(1, 2), Point3D(4, 5, 6))
for (p <- points) println(hypotenuse(p))
// 2.23606797749979
// 8.774964387392123
In general, sealed traits are good for modelling hierarchies where you expect the number of sub-classes
to change very little or not-at-all. A good example of something that can be modeled using sealed trait is
JSON.
A JSON value can only be JSON null, boolean, number, string, array, or dictionary.
JSON has not changed in 20 years, so it is unlikely that anyone will need to extend our JSON
with additional subclasses.
While the set of sub-classes is fixed, the range of operations we may want to do on a JSON blob is
unbounded: parse it, serialize it, pretty-print it, minify it, sanitize it, etc.
Thus it makes sense to model a JSON data structure as a closed sealed trait
hierarchy rather than a normal open trait hierarchy.
sealed trait Json
case class Null() extends Json
case class Bool(value: Boolean) extends Json
case class Str(value: String) extends Json
case class Num(value: Double) extends Json
case class Arr(value: Seq[Json]) extends Json
case class Dict(value: Map[String, Json]) extends Json