在这种情况下,为什么类型推断失败?

时间:2013-03-05 11:17:16

标签: scala type-inference

以下代码效果很好:

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
  }     
}

但以下代码将报告类型推断错误:

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 => ""
   }
}

那么这些代码之间有什么区别,以及scala如何在这里进行类型推断?

1 个答案:

答案 0 :(得分:6)

问题与使用Class而不是Tag无关,而是与匹配案例对象(例如IntegerTagStringTag)相匹配仅仅是价值(例如TagOfIntegerClassOfIntegerClassOfString)。

让我们尝试编译第一个示例的4个变体:

版本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 => ""
}     

第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() => ""
}     

第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 => ""
}     

第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
} 

如果您尝试编译它们,您将看到版本1,2和3编译正常,而版本4则没有。 原因是在版本1,2和3中,模式匹配允许编译器确定哪个类型是T

  • 在版本1中,我们执行case IntegerTag =>。由于IntegerTag是一个案例对象,我们确信不会有任何等于IntegerTag的实例(IntegerTag本身除外)。因此,如果此处匹配,则IntegerTag的运行时类型只能是IntegerTag,其扩展为Tag[Int]。因此,我们可以安全地推断出T = Int

  • 在版本2中,我们执行case IntegerTag() =>。此处IntegerTag是一个案例类,因此我们知道如果typIntegerTag的实例,则Tag[Int]扩展T = Int只能匹配。因此,我们可以安全地推断出case _: IntegerTag =>

  • 在版本3中,我们执行IntegerTag。换句话说,我们明确地与typ类型匹配。因此,我们再次知道IntegerTag类型为Tag[Int],其范围为T = Int,我们可以安全地推断出typ

现在,版本4的问题是我们无法保证case IntegerTag =>的运行时类型。这是因为在此版本中我们只是IntegerTag,其中valtyp == IntegerTag。换句话说,当且仅当typ时才会有匹配。问题是IntegerTag等于typ.==(IntegerTag)(或换句话说typ返回true)这一事实告诉我们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

StringTag返回true,这意味着如果我们将defaultValue传递给方法case IntegerTag =>,则会与StringTag匹配,即使Tag[String]确实存在是Tag[Int]而不是case IntegerTag =>的实例。这表明,typ匹配的事实确实没有告诉我们typ的运行时类型。所以编译器不能假设Tag[T]的确切类型:我们只从其声明的静态类型知道它是T但{{1}}仍然是未知的。