Reputation: 43
The following code works well:
object InfDemo {
class Tag[T]
case object IntegerTag extends Tag[Int]
case object StringTag extends Tag[String]
val TagOfInteger: Tag[Int] = IntegerTag
def defaultValue[T](typ: Tag[T]): T = typ match {
case IntegerTag => 0
case StringTag => ""
// case TagOfInteger => 0 // this not works
}
}
but the following code will report type inference error:
object InfDemo2 {
val ClassOfInteger: Class[Integer] = classOf[Integer]
val ClassOfString : Class[String] = classOf[String]
def defaultValue[T](typ: Class[T]): T = typ match {
case ClassOfInteger => 0
case ClassOfString => ""
}
}
So what is the difference between these code, and how scala does the type inference here?
Upvotes: 2
Views: 230
Reputation: 32739
The problem has nothing to do with using Class
over Tag
, and all to do with matching against a case object (such as IntegerTag
and StringTag
) over matching against a mere value (such as TagOfInteger
, ClassOfInteger
and ClassOfString
).
Let's try to compile 4 variants of your first example:
Version 1:
class Tag[T]
case object IntegerTag extends Tag[Int]
case object StringTag extends Tag[String]
def defaultValue[T](typ: Tag[T]): T = typ match {
case IntegerTag => 0
case StringTag => ""
}
Version 2:
class Tag[T]
case class IntegerTag() extends Tag[Int]
case class StringTag() extends Tag[String]
def defaultValue[T](typ: Tag[T]): T = typ match {
case IntegerTag() => 0
case StringTag() => ""
}
Version 3:
class Tag[T]
class IntegerTag extends Tag[Int]
class StringTag extends Tag[String]
def defaultValue[T](typ: Tag[T]): T = typ match {
case _: IntegerTag => 0
case _: StringTag => ""
}
Version 4:
class Tag[T]
val IntegerTag: Tag[Int] = new Tag[Int]
val StringTag: Tag[String] = new Tag[String]
def defaultValue[T](typ: Tag[T]): T = typ match {
case IntegerTag => 0 // error: type mismatch
case StringTag => "" // error: type mismatch
}
If you try to compile them you'll see that version 1, 2 and 3 compile fine, while version 4 does not.
The reason is that in version 1, 2 and 3, the pattern matching allows the compiler to know for sure which type is T
:
In version 1 we do case IntegerTag =>
. Because IntegerTag
is a case object, we know for sure that there cannot be any instance that is equal to IntegerTag
(except for IntegerTag
itself). So if there is a match here, the runtime type of IntegerTag
can only be IntegerTag
, which extends Tag[Int]
. Thus we can safely infer that T = Int
.
In version 2 we do case IntegerTag() =>
. Here IntegerTag
is a case class, and as such we know that there can only bea match here if typ
is an instance of IntegerTag
, which extends Tag[Int]
. Thus we can safely infer that T = Int
.
In version 3 we do case _: IntegerTag =>
. In other words, we explictly match against the IntegerTag
type. So once again we know that typ
is of type IntegerTag
, which extends Tag[Int]
, and we can safely infer that T = Int
.
Now, the problem with version 4 is that we have no guarantee about the runtime type of typ
. This is because in this version we just do case IntegerTag =>
, where IntegerTag
is a val
. In other words, there will be a match if and only if typ == IntegerTag
. The problem is that the fact that typ
is equal to IntegerTag
(or in other words that typ.==(IntegerTag)
returns true) tells us nothing about the runtime type of typ
.
Indeed, one can very well redefine equality in such a way that it can be equal to instance of unrelated classes (or simply be equal to instances of the same generic class but with different type arguments). By example, consider:
val StringTag: Tag[String] = new Tag[String]
val IntegerTag: Tag[Int] = new Tag[Int] {
override def equals( obj: Any ) = {
(obj.asInstanceOf[AnyRef] eq this) || (obj.asInstanceOf[AnyRef] eq StringTag)
}
}
println(StringTag == StringTag) // prints true
println(StringTag == IntegerTag) // prints false
println(IntegerTag == IntegerTag) // prints true
println(IntegerTag == StringTag) // prints true
IntegerTag == StringTag
returns true, which means that if we passed StringTag
to method defaultValue
, there would be a match with case IntegerTag =>
, even though StringTag
actuall is an instance of Tag[String]
rather than of Tag[Int]
. This shows that indeed the fact the there is a match for case IntegerTag =>
tells us nothing regarding the runtime type of typ
. And so the compiler cannot assume anything about the exact type of typ
: we only know from its declared static type that it is a Tag[T]
but T
is still unknown.
Upvotes: 6