我遇到了一个有趣的情况。我想实现类似于以下内容的东西。
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
的是互变的,所以我们可以将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类型擦除和模式匹配
键入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]
,从而变成了错误。
在constrast如果我们改变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}
(这只是说明性的;当前,在模式匹配时您实际上不能写类型参数。)
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句