类型稳定参数多态性

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。但是我希望能够构造一个函数来统一一些以前定义的方法,并使它们类型稳定。谁能解释一下?

此外,请注意以下 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()

一般来说,对于“return 当前类型”问题的解决方案,请参阅 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 of A, then f should return that particular subtype 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。鉴于我们有 BC,现在编译器必须取两者中的最小上限,即 A。但是 A 肯定不总是 T。因此类型检查失败。

现在让我们用

做同样的练习
def f[T <: A](s: T): A

这个规范的意思是(再观察一下“for all”)

For all types T that are a subtype of A, then f should return their supertype A.

现在让我们输入方法主体表达式

s match {
  case s: B => s.funcB
  case s: C => s.funcC
}

在我们到达类型 BC 之前,编译器采用上限,即超类型 A。事实上,这正是我们指定的 return 类型。所以类型检查成功了。然而,尽管成功了,但在编译时我们丢失了一些类型信息,因为编译器将不再考虑在调用站点传入的特定 T 附带的所有信息,而只考虑通过其超类型 A 提供的信息.例如,如果T有一个成员不存在于A,那么我们将无法调用它。

要避免什么?

关于asInstanceOf,这是我们告诉编译器停止帮助我们,因为我们会下雨。两组人倾向于在 Scala 中使用它来使事情正常进行, 库作者和那些从其他更动态类型的语言转换过来的人。然而,在大多数应用程序级代码中,这被认为是不好的做法。

回答问题为什么它不起作用f returns 语句的结果 s match {...}.

该语句的类型是A(有时是returnsB,有时是returnsC),不是 T,因为它应该是。 T 有时是 C,有时 Bs match {...} 从不 中的任何一个。它是它们的超类型,即 A.

回复。这个:

 s match {
  case s: B => s
  case s: C => s
}

这条语句的类型显然是T,因为sT。它肯定 does compile 尽管 @jwvh 可能会说:)

这一切都归结于我们的老朋友(恶魔?)compile-time/run-time 障碍。 (以后再也不会见面了。)

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

你的第二个“同样有效”的例子不适合我。