2

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?

1 Answer 1

6

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 StringTagactuall 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.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.