Scala 3 迁移指南

类型检查器

语言

Scala 2.13 类型检查器在某些特定情况下是不健全的。这可能导致我们在意想不到的地方遇到令人惊讶的运行时错误。Scala 3 基于更强大的理论基础,因此类型检查器中的这些不健全错误现在已得到修复。

方差检查中的不健全修复

在 Scala 2 中,默认参数和内部类不受方差检查的影响。这是不健全的,并且可能导致运行时失败,如 Scala 3 存储库中的 此测试 所示。

Scala 3 编译器不再允许这样做。

class Foo[-A](x: List[A]) {
  def f[B](y: List[B] = x): Unit = ???
}

class Outer[+A](x: A) {
  class Inner(y: A)
}

因此,如果你在 Scala 3 中编译,你将收到以下错误。

-- Error: src/main/scala/variance.scala:2:8 
2 |  def f[B](y: List[B] = x): Unit = y
  |        ^^^^^^^^^^^^^^^^^
  |contravariant type A occurs in covariant position in type [B] => List[A] of method f$default$1
-- Error: src/main/scala/variance.scala:6:14 
6 |  class Inner(y: A)
  |              ^^^^
  |covariant type A occurs in contravariant position in type A of parameter y

此类每个问题都需要特别注意。你可以逐案尝试以下选项

  • 使类型 A 不变
  • 在类型参数 B 上添加下界或上界
  • 添加一个新的方法重载

在我们的示例中,我们可以选择这两种解决方案

class Foo[-A](x: List[A]) {
-  def f[B](y: List[B] = x): Unit = ???
+  def f[B](y: List[B]): Unit = ???
+  def f(): Unit = f(x)
}

class Outer[+A](x: A) {
-  class Inner(y: A)
+  class Inner[B >: A](y: B)
}

或者,作为临时解决方案,你还可以使用 uncheckedVariance 注解

class Outer[+A](x: A) {
-  class Inner(y: A)
+  class Inner(y: A @uncheckedVariance)
}

模式匹配中的不健全修复

Scala 3 修复了模式匹配中的一些不健全错误,防止一些语义错误的匹配表达式进行类型检查。

例如,combineReq 中的匹配表达式可以使用 Scala 2.13 编译,但不能使用 Scala 3 编译。

trait Request
case class Fetch[A](ids: Set[A]) extends Request

object Request {
  def combineFetch[A](x: Fetch[A], y: Fetch[A]): Fetch[A] = Fetch(x.ids ++ y.ids)

  def combineReq(x: Request, y: Request): Request = {
    (x, y) match {
      case (x @ Fetch(_), y @ Fetch(_)) => combineFetch(x, y)
    }
  }
}

在 Scala 3 中,错误消息是

-- [E007] Type Mismatch Error: src/main/scala/pattern-match.scala:9:59 
9 |      case (x @ Fetch(_), y @ Fetch(_)) => combineFetch(x, y)
  |                                                           ^
  |                                                Found:    (y : Fetch[A$2])
  |                                                Required: Fetch[A$1]

正确的做法是,没有证据表明 xy 具有相同的类型参数 A

从 Scala 2 开始,这显然是一种改进,可帮助我们找到代码中的错误。为了解决这种不兼容性,最好找到一种可以由编译器检查的解决方案。这并不总是一件容易的事,有时甚至是不可能的,在这种情况下,代码很可能在运行时失败。

在此示例中,我们可以通过声明 A 是两个类型参数的共同祖先来放宽对 xy 的约束。这使得编译器成功地对代码进行类型检查。

def combineFetch[A](x: Fetch[_ <: A], y: Fetch[_ <: A]): Fetch[A] = Fetch(x.ids ++ y.ids)

或者,一种通用但又不安全的解决方案是强制转换。

此页面的贡献者