【问题标题】:Scala - how come using a super-type with two generic parameters cause the scala type checker to treat the child-type differently?Scala - 为什么使用具有两个泛型参数的超类型会导致 scala 类型检查器以不同方式对待子类型?
【发布时间】:2016-04-17 11:59:30
【问题描述】:

我遇到了一个有趣的情况。我想实现如下所示的东西。

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 时,类型不会签出。这一切似乎都按预期进行。有趣的是,在代码库的其他地方,我们有如下内容:(将 DesiredKeyConstraints 替换为 WorkingKeyConstraints

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 是逆变的,所以我们可以将Constraint[Any] 视为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 类型检查器中的某种错误,但也许我遗漏了一些东西。

【问题讨论】:

  • 它是如何失败的?哪条线和什么错误?目前,你所有的例子都为我编译。
  • @AlvaroCarrasco,不是第一个,对吧?

标签: scala generics subclass contravariance


【解决方案1】:

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() 模式将Any 匹配为Identity[Any],并且由于类型擦除,这与Identity[Int] 匹配,从而导致错误。


相反,如果我们将Any 更改为Identity[_]

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}

(这只是说明性的;您目前无法在模式匹配时实际编写类型参数。)

【讨论】:

  • 知道为什么使用SuperTrait[A, B] 会隐藏类型,但使用SuperTrait[A] 不会?
  • @RichardShurtz,这是因为KeyConstraint[A] extends SuperTrait[A] 暗示KeyConstraint[T] <: SuperTrait[T] 用于任何类型T。但是KeyConstraint[A] extends SuperTrait[A, Unit] 并不意味着KeyConstraint[T] <: SuperTrait[T, S] 用于任何类型TS。关系丢失。把你的类型注解从SuperTrait[_, _]改成SuperTrait[_, Unit],我怀疑会有不同的结果。
  • 啊...是的,这就解释了
猜你喜欢
  • 2014-02-04
  • 1970-01-01
  • 1970-01-01
  • 2015-07-09
  • 2020-03-27
  • 1970-01-01
  • 2015-09-11
  • 2012-04-11
  • 2020-06-30
相关资源
最近更新 更多