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 的角度來看,這顯然是一種改進,有助於我們找出程式碼中的錯誤。要解決這種不相容性,最好找到一種可以由編譯器檢查的解決方案。這並不總是容易的,有時甚至是不可能的,在這種情況下,程式碼很可能會在執行時失敗。

在此範例中,我們可以放寬對 xy 的約束,指出 A 是兩個類型參數的共同祖先。這使編譯器可以成功對程式碼進行類型檢查。

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

或者,一個通用的但並不安全的解決方案是進行強制轉換。

此頁面的貢獻者