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

标签 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是如何进行类型推断的呢?

最佳答案

问题与在 Tag 上使用 Class 无关,而与匹配 case 对象(例如 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 是一个 case 对象,我们可以肯定地知道不可能有任何实例等于 IntegerTag(IntegerTag 本身除外)。所以如果这里有匹配,IntegerTag的运行时类型只能是IntegerTag,它扩展了Tag[Int]。因此我们可以安全地推断出 T = Int

  • 在版本 2 中,我们使用 case IntegerTag() =>。这里 IntegerTag 是一个案例类,因此我们知道如果 typIntegerTag 的实例,那么这里只能匹配,它扩展标签[Int]。因此我们可以安全地推断出 T = Int

  • 在版本 3 中,我们使用 case _: IntegerTag =>。换句话说,我们显式匹配 IntegerTag 类型。所以我们再次知道 typIntegerTag 类型,它扩展了 Tag[Int],我们可以安全地推断出 T = Int.

现在,版本 4 的问题是我们无法保证 typ 的运行时类型。这是因为在这个版本中,我们只执行 case IntegerTag =>,其中 IntegerTag 是一个 val。换句话说,当且仅当 typ == IntegerTag 时才会匹配。问题在于 typ 等于 IntegerTag(或者换句话说 typ.==(IntegerTag) 返回 true)这一事实表明我们对 typ 的运行时类型一无所知。 实际上,可以很好地重新定义相等性,使其等于不相关类的实例(或者简单地等于相同泛型类但具有不同类型参数的实例)。例如,考虑:

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 返回 true,这意味着如果我们将 StringTag 传递给方法 defaultValue,将与 匹配>case IntegerTag =>,即使 StringTagactuall 是 Tag[String] 而不是 Tag[Int] 的实例.这表明确实存在 case IntegerTag => 的匹配这一事实并没有告诉我们任何有关 typ 的运行时类型的信息。因此编译器不能假设任何关于 typ 的确切类型:我们只从它声明的静态类型知道它是一个 Tag[T]T 仍然未知。

关于scala - 为什么在这种情况下类型推断会失败?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15222398/

相关文章:

scala - Scala 中 `def` 和 `val` 的不同类型推断

scala - 如何从 spark 执行器读取 HDFS 文件?

scala - MutableList 和 ListBuffer 的区别

scala - 本地分配影响类型?

java - 如何构建高效的Kafka Broker健康检查?

typescript - 如何能够区分基于联合类型的两个对象?

arrays - 如何在 Scala 中创建异构数组?

haskell - Fundeps 和 GADT : When is type checking decidable?

Haskell:a -> a 类型函数的示例,除了标识

c++ - 在这种情况下如何使模板类型推断起作用?