Scala - 使用带有两个通用参数的超类型如何导致 Scala 类型检查器以不同方式对待子类型?
Scala - how come using a super-type with two generic parameters cause the scala type checker to treat the child-type differently?
我运行陷入了一个有趣的境地。我想实现如下所示的东西。
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]
,因此代码可以编译。这里的关键 problem/question 是,为什么拥有超类型会导致类型检查器将其视为 KeyConstraint[Any]
?
此外,作为进一步的信息,我进一步研究了这个问题,它是具有两个泛型类型参数的超类型所特有的。如果我用两个泛型类型做 child-class 或者用一个泛型类型做父代,它仍然会失败。在下面查看我的其他失败尝试:
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 类型擦除和模式匹配
键入 Map
和 SuperTrait
隐藏了有关类型的信息,并导致模式匹配为您的提取器假设一个广泛的类型。
这是一个类似的示例,但使用 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: _ where type _
case f@Identity() => f("string")
知道f
是存在类型Identity[T] forSome {type T}
,不能证明String
符合通配类型T
.
在第一个示例中,您有效地进行了模式匹配
DesiredKeyConstraints.KeyConstraint[Any](_, constraint)
第二个信息比较多,你匹配的是
DesiredKeyConstraints.KeyConstraint[T](_, constraint) forSome {type T}
(这只是说明性的;您目前无法在模式匹配时实际编写类型参数。)
我运行陷入了一个有趣的境地。我想实现如下所示的东西。
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]
,因此代码可以编译。这里的关键 problem/question 是,为什么拥有超类型会导致类型检查器将其视为 KeyConstraint[Any]
?
此外,作为进一步的信息,我进一步研究了这个问题,它是具有两个泛型类型参数的超类型所特有的。如果我用两个泛型类型做 child-class 或者用一个泛型类型做父代,它仍然会失败。在下面查看我的其他失败尝试:
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 类型擦除和模式匹配
键入 Map
和 SuperTrait
隐藏了有关类型的信息,并导致模式匹配为您的提取器假设一个广泛的类型。
这是一个类似的示例,但使用 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: _ where type _
case f@Identity() => f("string")
知道f
是存在类型Identity[T] forSome {type T}
,不能证明String
符合通配类型T
.
在第一个示例中,您有效地进行了模式匹配
DesiredKeyConstraints.KeyConstraint[Any](_, constraint)
第二个信息比较多,你匹配的是
DesiredKeyConstraints.KeyConstraint[T](_, constraint) forSome {type T}
(这只是说明性的;您目前无法在模式匹配时实际编写类型参数。)