Samar
Samar

Reputation: 2101

subtyping and type coercion

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

Answers (2)

Avseiytsev Dmitriy
Avseiytsev Dmitriy

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

Tzach Zohar
Tzach Zohar

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

Related Questions