2

我不明白为什么以下 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
}
4

3 回答 3

2

是什么让它发挥作用?

特别是,在 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超类中的方法如何返回“当前”类型的值?

诸如类型类和匹配类型之类的编译时技术可以被认为是一种编译时模式匹配,它指示编译器减少到调用站点使用的最具体的信息丰富的类型,而不是必须确定可能更差的上限类型。

为什么它不起作用?

要理解的关键概念是参数多态性是一种通用量化,这意味着它必须对编译器在调用点的类型参数的所有实例化都有意义。考虑输入规范

def f[T <: A](s: T): T

编译器可能会这样解释

对于T属于 的子类型的所有类型,A则应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。鉴于我们有Band 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。事实上,这正是我们指定的返回类型。所以类型检查成功了。然而,尽管成功了,但在编译时我们丢失了一些类型信息,因为编译器将不再考虑T在调用站点传入的特定信息,而只考虑通过其 supertype 可用的信息A。例如,如果T有一个成员不存在于 中A,那么我们将无法调用它。

要避免什么?

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

于 2021-04-17T00:39:27.330 回答
2

回答为什么它不起作用的问题。 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 可能会说什么,它确实可以编译:)

于 2021-04-17T00:41:52.797 回答
2

这一切都归结为我们的老朋友(恶魔?)编译时/运行时障碍。(而且这对双胞胎永远不会相遇。)

T在调用站点的编译时解析。当编译器看到f(B)thenT意味着B当编译器看到f(C)thenT就变成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

您的第二个“也有效”示例无法为我编译。

于 2021-04-17T00:45:42.053 回答