我不明白为什么以下 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
}
最佳答案
它的工作原理是什么?
特别是,在 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 How can a method in a superclass return a value of the “current” type?
诸如类型类和匹配类型之类的编译时技术可以被认为是一种编译时模式匹配,它指示编译器减少到在调用站点使用的最具体的信息丰富的类型,而不是必须确定可能的类型较差的上限类型。
为什么不起作用?
要理解的关键概念是参数多态性是一种通用量化,这意味着它必须对编译器有意义对于调用站点上类型参数的所有实例化。考虑打字规范
def f[T <: A](s: T): T
编译器可能会这样解释
For all types
T
that are a subtype ofA
, thenf
should return that particular subtypeT
.
因此表达式 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
这个规范的意思是(并再次遵守“for all”)
For all types
T
that are a subtype ofA
, thenf
should return their supertypeA
.
现在让我们输入方法体表达式
s match {
case s: B => s.funcB
case s: C => s.funcC
}
在我们到达类型 B
和 C
之前,编译器采用上限,即父类(super class)型 A
。事实上,这正是我们指定的返回类型。所以类型检查成功了。然而,尽管成功了,但在编译时我们丢失了一些类型信息,因为编译器将不再考虑在调用站点传入的特定 T
附带的所有信息,而只考虑通过其父类(super class)型 获得的信息>一个
。例如,如果 T
有一个成员不存在于 A
中,那么我们将无法调用它。
要避免什么?
关于asInstanceOf
,这是我们告诉编译器停止帮助我们,因为我们会下雨。两组人倾向于在 Scala 中使用它来使事情正常进行,mad scientist图书馆作者和那些从其他更动态类型的语言过渡的人。然而,在大多数应用程序级代码中,这被认为是不好的做法。
关于scala - 类型稳定参数多态性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67133436/