我不明白为什么下面的scala代码不能编译:
sealed trait A
case class B() extends A {
def funcB: B = this
}
case class C() extends A {
def funcC: C = this
}
def f[T <: A](s:T): T = s match {
case s: B => s.funcB
case s: C => s.funcC
}
它可以替代 f
具有
def f[T <: A](s:T): A = s match {
case s: B => s.funcB
case s: C => s.funcC
}
然后在 f
调用,使用 asInstanceOf
,例如。但是我希望能够构造一个函数来统一一些先前定义的方法,并且使它们保持类型稳定。谁能解释一下吗?
另外,请注意: f
还汇编:
def f[T <: A](s:T): T = s match {
case s: B => s
case s: C => s
}
3条答案
按热度按时间ds97pgxw1#
回答为什么它不起作用的问题。
f
返回语句的结果s match {...}
.该语句的类型是
A
(有时它会回来B
,有时还会回来C
),不是T
应该是这样的。T
有时是C
,有时B
,s match {...}
从来都不是。它是它们的超型,也就是A
.重新。这是:
这种说法显然是
T
,因为s
是T
. 尽管@jwvh可能会说:)它确实可以编译pprl5pva2#
是什么让它起作用的?
特别是在Scala3中,可以使用匹配类型
一般来说,对于“返回当前类型”问题的解决方案,请参见scala faq超类中的方法如何返回“当前”类型的值?
诸如类型类和匹配类型之类的编译时技术可以被视为一种编译时模式匹配,它指示编译器将调用位置使用的信息最丰富的类型缩减为最具体的类型,而不是必须确定可能较差的上界类型。
为什么不起作用?
要理解的关键概念是,参数多态性是一种通用的量化,这意味着编译器必须理解调用站点上类型参数的所有示例化。考虑输入规范
编译器可能会这样解释
适用于所有类型
T
这是A
,那么f
应该返回特定的子类型T
.因此,表达
expr
代表身体的f
```def fT <: A: T = expr
类型
是
B
,以及是
C
. 既然我们有B
以及C
,现在编译器必须取二者中的最小上界,即A
. 但是A
当然不总是这样T
. 因此类型检查失败。现在让我们做同样的练习
本规范是指(并再次遵守“适用于所有人”)
适用于所有类型
T
这是A
,那么f
应该返回它们的超类型A
.现在让我们键入方法体表达式
就像我们之前提到的类型一样
B
以及C
,因此编译器采用上界,即父类型A
. 实际上,这正是我们指定的返回类型。所以typecheck成功了。然而,尽管取得了成功,但在编译时我们丢失了一些类型信息,因为编译器将不再考虑特定代码附带的所有信息T
在调用站点传入,但仅通过其父类型提供信息A
. 例如,如果T
中不存在成员A
,那么我们将无法调用它。避免什么?
关于
asInstanceOf
,这是我们告诉编译器不要再帮助我们了,因为我们要冒雨了。两组人倾向于在scala中使用它来工作,一组是疯狂科学家库的作者,另一组是从其他更动态类型的语言转换过来的。然而,在大多数应用程序级代码中,这被认为是不好的做法。n9vozmp43#
这一切都归结于我们的老朋友(恶魔?)编译时/运行时的障碍(两人再也不会相遇。)
T
在编译时在调用站点解析。当编译器看到f(B)
那么T
手段B
当编译器看到f(C)
那么T
变成C
.但是
match { case ...
在运行时解析。编译器不知道是哪个case
将选择分支。从编译器的Angular 来看case
选择的可能性同样大。所以如果T
决心B
但代码可能需要一段时间C
分支…编译器不允许这样。看看编译什么:
你的第二个“同样有效”的例子不适合我。