Scala-为什么使用具有两个泛型参数的超类型会导致Scala类型检查器对子类型进行不同的处理?

理查德·舒尔茨

我遇到了一个有趣的情况。我想实现类似于以下内容的东西。

object Test {
  abstract class Key[A]
  class Constraint[-A] {
    def doSomething(a: A): String = ""
  }

  object DesiredKeyConstraints {
    case class KeyConstraint[A](val key: Key[A], constraint: Constraint[A])
    val data: Map[Key[_], KeyConstraint[_]] = Map()
  }

  def useTheKeyConstraints[A](key: Key[A], value: A): String = {
    DesiredKeyConstraints.data.get(key).fold[String]("") {
      case DesiredKeyConstraints.KeyConstraint(_, constraint) =>  constraint.doSomething(value)
    }
  }

  def main(args: Array[String]) {
    println("hi")
  }
}

不幸的是,当我从地图中拉出KeyConstraint时,我不再知道它的类型。因此,当我尝试致电时doSomething,类型不会签出。这一切似乎都符合预期。有趣的是,在代码库的其他地方,我们的内容类似于以下内容:(替换DesiredKeyConstraintsWorkingKeyConstraints

object Test {
  abstract class Key[A]
  class Constraint[-A] {
    def doSomething(a: A): String = ""
  }

  object WorkingKeyConstraints {
    sealed trait SuperTrait[A, B] {
      val key: Key[A]
    }
    case class KeyConstraint[A](val key: Key[A], constraint: Constraint[A]) extends SuperTrait[A, Unit]
    val data: Map[Key[_], SuperTrait[_, _]] = Map()
  }

  def useTheKeyConstraints[A](key: Key[A], value: A): String = {
    WorkingKeyConstraints.data.get(key).fold[String]("") {
      case WorkingKeyConstraints.KeyConstraint(_, constraint) =>  constraint.doSomething(value)
    }
  }

  def main(args: Array[String]) {
    println("hi")
  }
}

这个编译并运行正常。由于某种原因,拥有超类型意味着当我们从Map中提取KeyConstraint时,会将其视为KeyConstraint[Any]而不是KeyConstraint[_]因为Constraint的是互变的,所以我们可以将aConstraint[Any]视为a Constraint[A],因此代码可以编译。此处的关键问题/问题是,为什么拥有超类型会导致类型检查器将其视为KeyConstraint[Any]

另外,作为进一步的信息,我进一步研究了这一点,这是具有具有两个泛型类型参数的超类型所特有的。如果我用两个泛型类型来做子类,或者用单个泛型类型来做一个父类,它仍然会失败。请参阅下面的其他失败尝试:

object AnotherCaseThatDoesntWorkKeyConstraints {
  case class KeyConstraint[A, B](val key: Key[A], constraint: Constraint[A])
  val data: Map[Key[_], KeyConstraint[_, _]] = Map()
}

object AThirdCaseThatDoesntWorkKeyConstraints {
  sealed trait SuperTrait[A] {
    val key: Key[A]
  }
  case class KeyConstraint[A](val key: Key[A], constraint: Constraint[A]) extends SuperTrait[A]
  val data: Map[Key[_], SuperTrait[_]] = Map()
}

我认为这是Scala类型检查器中的某种错误,但也许我遗漏了一些东西。

保罗·德雷珀

tl; dr类型擦除和模式匹配

键入MapSuperTrait有关类型的隐藏信息,并引起了模式匹配承担您提取了广阔的类型。


这是一个类似的示例,但是使用Any代替SuperTrait此示例还显示了如何从中产生运行时异常。

case class Identity[A : Manifest]() {
  def apply(a: A) = a match { case a: A => a } // seemingly safe no-op
}

val myIdentity: Any = Identity[Int]()

myIdentity match {
  case f@Identity() => f("string") // uh-oh, passed String instead of Int
}

引发异常

scala.MatchError: string (of class java.lang.String)
  at Identity.apply(...)

f@Identity()模式与匹配AnyIdentity[Any],并且由于类型擦除而与匹配Identity[Int],从而变成了错误。


在constrast如果我们改变AnyIdentity[_]

case class Identity[A : Manifest]() {
  def apply(a: A) = a match { case a: A => a }
}

val myIdentity: Identity[_] = Identity[Int]()

myIdentity match {
  case f@Identity() => f("string")
}

正确无法编译。

found   : String("string")
required: _$1 where type _$1
     case f@Identity() => f("string")

它知道这f是存在类型Identity[T] forSome {type T},无法证明String符合通配符类型T


在第一个示例中,您有效地将模式匹配为

DesiredKeyConstraints.KeyConstraint[Any](_, constraint)

在第二个中,有更多信息,您将匹配

DesiredKeyConstraints.KeyConstraint[T](_, constraint) forSome {type T}

(这只是说明性的;当前,在模式匹配时您实际上不能写类型参数。)

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

Related 相关文章

热门标签

归档