Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Different notations to express inheritance

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.

like image 716
kiritsuku Avatar asked Nov 26 '10 15:11

kiritsuku


2 Answers

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.

like image 62
Daniel C. Sobral Avatar answered Oct 04 '22 02:10

Daniel C. Sobral


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) = ...
like image 38
Kevin Wright Avatar answered Oct 04 '22 04:10

Kevin Wright