After defining the following :
abstract class A {
type T
def print(p: T) = println(p.toString)
}
trait B extends A {
type T <: String
}
As expected, we can not create an object with T = Int :
scala> val a = new A with B {type T = Int}
<console>:9: error: overriding type T in trait B with bounds >: Nothing <: String;
type T has incompatible type
val a = new A with B {type T = Int}
^
As expected, we can create an object with T = String:
scala> val a = new A with B {type T = String}
a: A with B{type T = String} = $anon$1@692dec
scala> a.print("test")
test
After casting our value a to the type A with B, we get an error when calling the print method. It seems the type field T lost its information about the type (?).
scala> val b = a.asInstanceOf[A with B]
b: A with B = $anon$1@1927275
scala> b.print("test")
<console>:15: error: type mismatch;
found : java.lang.String("test")
required: b.T
b.print("test")
^
Question 1: Why is the information about the type field T lost after the cast ?
Okay, so we try it again with a cast which explicitly sets the type field T to a String type:
scala> val c = a.asInstanceOf[A with B {type T = String}]
c: A with B{type T = String} = $anon$1@1927275
scala> c.print("test")
test
Okay, this works – good.
Now let’s try something crazy:
scala> val d = a.asInstanceOf[A with B {type T = Int}]
d: A with T{type T = Int} = $anon$1@1927275
scala> d.print(3)
3
Question 2: Huh ? Trait B restricted type T to be a subtype of String, but now the print method works with integers. Why is this working ?
Question 1 — “After casting our value a to the type A with B, we get an error when calling the print method.” What information about
Tis there after the cast? It is precisely what is inB:type T <: StringTherefore the type is not known, just its upper bound. The following shows why the
printcall forA with Bis forbidden:So it’s a problem of what can happen to types occurring in contravariant (method argument) positions. Had you defined
Bby narrowing the lower bound, i.e.type T >: X, it would be possible to calltesteven ifTwas not fixed.Question 2 — Of course it works. You can make the compiler allow any call with the type casting. After you cast to
A with B {type T = Int}, you force the compiler to accept thatT = Int. Now thetoStringmethod you call is defined forjava.lang.Object, and due to the generic structure ofA, yourIntis boxed into ajava.lang.Integer, and therefore you do not witness any runtime problem when callingtoString.But it is wrong to think that you are doing something correct here. For example: