【问题标题】:Why does type inference fails in this case?为什么在这种情况下类型推断会失败?
【发布时间】:2013-03-05 11:17:15
【问题描述】:

以下代码运行良好:

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

【问题讨论】:

    标签: scala type-inference


    【解决方案1】:

    问题与使用Class 而不是Tag 无关,而是与匹配一个case 对象(例如IntegerTag 和StringTag)而不是匹配一个单纯的值(例如@ 987654325@、ClassOfInteger 和 ClassOfString)。

    让我们尝试编译第一个示例的 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 是一个case 类,因此我们知道,如果typ 是IntegerTag 的一个实例,它扩展Tag[Int],那么这里只能匹配。因此我们可以安全地推断出T = Int。

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-17
      相关资源
      最近更新 更多