[英]type stable parametric polymorphism
我不明白为什么以下 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
。 但我希望能够构建一个 function 来统一一些先前定义的方法,并使它们类型稳定。 谁能解释一下?
另外,请注意以下f
也可以编译:
def f[T <: A](s:T): T = s match {
case s: B => s
case s: C => s
}
特别是,在 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 常见问题解答超类中的方法如何返回“当前”类型的值?
诸如类型类和匹配类型之类的编译时技术可以被认为是一种编译时模式匹配,它指示编译器减少到调用站点使用的最具体的信息丰富的类型,而不是必须确定可能更差的上限类型。
要理解的关键概念是参数多态性是一种通用量化,这意味着它必须对编译器在调用点的类型参数的所有实例化都有意义。 考虑输入规范
def f[T <: A](s: T): T
编译器可能会这样解释
对于属于
A
子类型的所有类型T
,则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
和C
,现在编译器必须采用两者中最小的上限,即A
。 但A
肯定不总是T
。 因此类型检查失败。
现在让我们做同样的练习
def f[T <: A](s: T): A
该规范意味着(并再次遵守“为所有人”)
对于作为
A
子类型的所有类型T
,f
应该返回它们的超类型A
。
现在让我们输入方法体表达式
s match {
case s: B => s.funcB
case s: C => s.funcC
}
和之前我们到达类型B
和C
,因此编译器采用上界,即超类型A
。 事实上,这正是我们指定的返回类型。 所以类型检查成功了。 然而,尽管成功了,但在编译时我们丢失了一些类型信息,因为编译器将不再考虑在调用站点传入的特定T
附带的所有信息,而只考虑通过其超类型A
可用的信息。 例如,如果T
有一个不存在于A
中的成员,那么我们将无法调用它。
关于asInstanceOf
,这是我们告诉编译器停止帮助我们,因为我们会下雨。 两组人倾向于在 Scala 中使用它来使事情正常进行,疯狂的科学家库作者和从其他更动态类型的语言过渡的人。 然而,在大多数应用程序级代码中,这被认为是不好的做法。
回答为什么它不起作用的问题。 f
返回语句s match {...}
的结果。
该语句的类型是A
(有时它返回B
,有时它返回C
),而不是应该的T
。 T
有时是C
,有时B
, s match {...}
绝不是其中任何一个。 它是它们的超类型,即A
。
回覆。 这个:
s match {
case s: B => s
case s: C => s
}
该语句的类型显然是T
,因为s
是T
。 尽管@jwvh 可能会说什么,它确实可以编译:)
这一切都归结为我们的老朋友(恶魔?)编译时/运行时障碍。 (而且这对双胞胎永远不会相遇。)
T
在调用站点的编译时解析。 当编译器看到f(B)
时, T
表示B
,当编译器看到f(C)
时, T
变为C
。
但是match { case...
在运行时解决。 编译器无法知道将选择哪个case
分支。 从编译器的角度来看,所有case
选项的可能性都相同。 因此,如果T
解析为B
但代码可能采用C
分支......好吧,编译器不允许这样做。
查看编译的内容:
def f[T <: A](s:T): A = s match { //f() returns an A
case s: B => s.funcB //B is an A sub-type
case s: C => s.funcC //C is an A sub-type
} //OK, all is good
您的第二个“也有效”示例无法为我编译。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.