是什么让它发挥作用?
特别是,在 Scala 3 中,您可以使用匹配类型
scala> type Foo[T <: A] = T match {
| case B => B
| case C => C
| }
|
| def f[T <: A](s:T): Foo[T] = s match {
| case s: B => s.funcB
| case s: C => s.funcC
| }
def f[T <: A](s: T): Foo[T]
scala> f(B())
val res0: B = B()
scala> f(C())
val res1: C = C()
一般来说,有关“返回当前类型”问题的解决方案,请参阅 Scala FAQ超类中的方法如何返回“当前”类型的值?
诸如类型类和匹配类型之类的编译时技术可以被认为是一种编译时模式匹配,它指示编译器减少到调用站点使用的最具体的信息丰富的类型,而不是必须确定可能更差的上限类型。
为什么它不起作用?
要理解的关键概念是参数多态性是一种通用量化,这意味着它必须对编译器在调用点的类型参数的所有实例化都有意义。考虑输入规范
def f[T <: A](s: T): T
编译器可能会这样解释
对于T
属于 的子类型的所有类型,A
则应f
返回该特定子类型T
。
因此expr
代表身体的表达式f
def f[T <: A](s:T): T = expr
必须键入特定T
的 . 现在让我们尝试输入我们的expr
s match {
case s: B => s.funcB
case s: C => s.funcC
}
的类型
case s: B => s.funcB
是B
, 的类型
case s: C => s.funcC
是C
。鉴于我们有B
and C
,现在编译器必须采用两者中的最小上限,即A
. 但A
肯定不会总是这样T
。因此类型检查失败。
现在让我们做同样的练习
def f[T <: A](s: T): A
该规范意味着(并再次遵守“为所有人”)
对于T
属于 的子类型的所有类型,A
则应f
返回其超类型A
。
现在让我们输入方法体表达式
s match {
case s: B => s.funcB
case s: C => s.funcC
}
和之前我们得到类型B
和一样C
,编译器采用上界,即超类型A
。事实上,这正是我们指定的返回类型。所以类型检查成功了。然而,尽管成功了,但在编译时我们丢失了一些类型信息,因为编译器将不再考虑T
在调用站点传入的特定信息,而只考虑通过其 supertype 可用的信息A
。例如,如果T
有一个成员不存在于 中A
,那么我们将无法调用它。
要避免什么?
关于asInstanceOf
,这是我们告诉编译器停止帮助我们,因为我们会下雨。有两组人倾向于在 Scala 中使用它来使事情正常运行,疯狂的科学家库作者和从其他更动态类型的语言过渡的人。然而,在大多数应用程序级代码中,这被认为是不好的做法。