why are there different notations to express inheritance? In generics I have to use the <:-operator - in a normal class-inheritance I have to use the extends keyword.
For example I have to write this:
class X[A <: B] extends Y
But why not writing something like this:
class X[A <: B] <: Y
or
class X[A extends B] extends Y // like Java
I have no problem with the current notation, but I want to know whether there is a reason to notate the type hierarchy of generics in a different way.
Well, there is nothing stopping Scala from doing so, but, as a matter of fact, they do not express the same thing at all. And, in fact, you can see that in Java, where you can write X super Y
, but you can't say class X super Y
.
The keyword extends
express a relationship between classes, one of inheritance. On the other hand, <:
and >:
express a relationship between types, one of boundaries. When I say X <: Y
, then it is valid for both X
and Y
to be String
, for example, while String extends String
would be meaningless. It is also the case that List[String] <: List[AnyRef]
, though, again, List[String] extends List[AnyRef]
is meaningless. And, just to make the point, it is not true that Set[String] <: Set[AnyRef]
. In all these examples I just gave we are talking about the same class, but not, necessarily, about the same type.
And, of course, there are other relationships between types, such as view bounds (<%
) and context bounds (:
).
So, just because extends
implies <:
, it doesn't follow that <:
implies extends
, which, alone, is reason enough to avoid using the same keyword. Add to that the other relationships between types, and you have pretty much a closed deal.
It becomes a bit more obvious when you extend (no pun intended) your example beyond that one simple case.
Multiple inheritance:
class C[A] extends X with Y with Z
Mixins:
val x = new X with Y
Parameterization:
class X[A <: B] extends Y[A]
Multiple (related) type params:
class X[A >: B, B](x: A, xs: Seq[B])
Context bounds:
class X[A : Manifest]
View bounds:
class X[A <% Ordered[A]]
Generic Methods:
class Foo[B] {
def bar[A <: B](x: A) = ...
}
As you can see, the relationships that may be specified in a type parameter are far richer than the simple linear hierarchy available when declaring a class, especially when you allow for bounds.
It's also worth noting that generic type parameters for classes or methods will very often be inferred, allowing you to write:
val myList = List(1, 2, 3)
instead of
val myList = List[Int](1, 2, 3)
So the way in which the notations are used is very different.
update
One particular example has just spring to mind, demonstrating the use of both notations simultaneously and showing how they need to remain distinct:
def someMethod[T <: Foo with Bar](x: T) = ...
This requires that the type-param T
be a subtype something mixing in both Foo
and Bar
.
The same applies with structural types:
type Closable = { def close: Unit } //alias for a structural type
def someMethod[T <: Closable](x: T) = ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With