Reputation: 2101
The type T
below is a subtype of Fruit
. Apple
is also a subtype of Fruit
. So, why doesn't the compiler coerce Apple to T when a type ascription is used below. Why do we need an explicit casting to make it work? What are the rules for coercion when inferring types?
trait Fruit
case class Apple(id: String) extends Fruit
type T <: Fruit
val t: T = Apple("apple") // why doesn't the compiler coerce Apple to T?
val t: T = Apple("apple").asInstanceOf[T] // works!
Upvotes: 2
Views: 626
Reputation: 1160
Let's simplify this a bit. Replce T with Pear and Fruit with Fetus.
trait Fetus
class Apple extends Fetus
type Pear <: Fetus
val t: Pear = new Apple()
We declared Fetus, then we said "Apple is Fetus" and then "Pear is Fetus". Our logic is correct so far. But then we try to "put the apple into the box for pears". And here is our mistake.
Let's consider another example:
trait Fetus
class Apple extends Fetus
type Fruit >: Apple <: Fetus
val t: Fruit = new Apple()
Here we declared Fetus, then we said "Apple is Fetus" and then we said that "Fruit is something in the middle between Apple and Fetus". In another words we built following hierarchy: Fetus -> Fruit -> Apple. So, now Apple is subtype of Fruit and you can "put apples into the box for fruit".
UPDATE:
A Pear
definitely cannot be and Apple
, like a Cat cannot be a Dog in spite of they both are animals. You can go by hierarchy only vertically but not horizontally:
Creature
|
Animal
_|_
/ \
Dog Cat
\
White Cat
Upvotes: 1
Reputation: 37832
I think you're confusing the meaning of the definition
type T <: Fruit
: It's an abstract type defintion (unlike type T = Fruit
which is a type alias). Absract types do not create a class T that extends Fruit
, rather it is an abstract declaration that must be overridden in some subclass to be used.
From Learning Scala, chapter 10:
... abstract types are specifications that may resolve to zero, one, or many classes. They work in a similar way to type aliases, but being specifications they are abstract and cannot be used to create instances
Notice that the declaration type T <: Fruit
can only reside in a class/trait (not an object, nor top-level definitions), because it's meaningless until that class/trait is extended. Where it is defined, it's still abstract and therefore compiler can't know for sure that Apple
extends it.
An example for using such a definition:
trait Price {
type T <: Fruit
def printPrice(x: T): Unit
}
class ApplePrice extends Price {
override type T = Apple
override def printPrice(x: Apple): Unit = ???
}
Here, ApplePrice
must override this abstract type with something. Another subclass of Price
might override this with something different (e.g. Banana extends Fruit
), which should make it clear that the expression val t: T = Apple("apple")
placed in Price
can't compile - for some possible extending class, T
isn't Apple
, nor does Apple
extend T
.
Upvotes: 1