scala型稳定参数多态性

xurqigkl  于 2021-07-14  发布在  Java
关注(0)|答案(3)|浏览(399)

我不明白为什么下面的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
}
ds97pgxw

ds97pgxw1#

回答为什么它不起作用的问题。 f 返回语句的结果 s match {...} .
该语句的类型是 A (有时它会回来 B ,有时还会回来 C ),不是 T 应该是这样的。 T 有时是 C ,有时 B , s match {...} 从来都不是。它是它们的超型,也就是 A .
重新。这是:

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

这种说法显然是 T ,因为 sT . 尽管@jwvh可能会说:)它确实可以编译

pprl5pva

pprl5pva2#

是什么让它起作用的?

特别是在Scala3中,可以使用匹配类型

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 fT <: A: 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

本规范是指(并再次遵守“适用于所有人”)
适用于所有类型 T 这是 A ,那么 f 应该返回它们的超类型 A .
现在让我们键入方法体表达式

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

就像我们之前提到的类型一样 B 以及 C ,因此编译器采用上界,即父类型 A . 实际上,这正是我们指定的返回类型。所以typecheck成功了。然而,尽管取得了成功,但在编译时我们丢失了一些类型信息,因为编译器将不再考虑特定代码附带的所有信息 T 在调用站点传入,但仅通过其父类型提供信息 A . 例如,如果 T 中不存在成员 A ,那么我们将无法调用它。

避免什么?

关于 asInstanceOf ,这是我们告诉编译器不要再帮助我们了,因为我们要冒雨了。两组人倾向于在scala中使用它来工作,一组是疯狂科学家库的作者,另一组是从其他更动态类型的语言转换过来的。然而,在大多数应用程序级代码中,这被认为是不好的做法。

n9vozmp4

n9vozmp43#

这一切都归结于我们的老朋友(恶魔?)编译时/运行时的障碍(两人再也不会相遇。) T 在编译时在调用站点解析。当编译器看到 f(B) 那么 T 手段 B 当编译器看到 f(C) 那么 T 变成 C .
但是 match { case ... 在运行时解析。编译器不知道是哪个 case 将选择分支。从编译器的Angular 来看 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

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

相关问题