Scala 3 遷移指南

其他已變更的功能

語言

為了讓語言更簡單、更安全或更一致,一些其他功能被簡化或限制。

不相容性 Scala 3 遷移重寫
繼承遮蔽
私有類別中的非私有建構函式 遷移警告
抽象覆寫  
案例類別伴侶  
明確呼叫 unapply  
隱形的 bean 屬性  
=>T 作為類型引數  
萬用字元類型引數  

繼承遮蔽

繼承的成員,來自父特質或類別,可以遮蔽在外層範圍中定義的識別碼。此模式稱為繼承遮蔽。

object B {
  val x = 1
  class C extends A {
    println(x)
  }
}

例如,在此前一段程式碼中,C 中的 x 詞彙可以參照在外層類別 B 中定義的 x 成員,或者可以參照父類別 Ax 成員。在您前往 A 的定義之前,您無法得知。

這已知容易出錯。

這就是為什麼在 Scala 3 中,如果父類別 A 確實有一個成員 x,編譯器需要消除歧義。

它會阻止以下程式碼編譯。

class A {
  val x = 2
}

object B {
  val x = 1
  class C extends A {
    println(x)
  }
}

但如果你嘗試使用 Scala 3 編譯,你應該會看到類似這樣的錯誤

-- [E049] Reference Error: src/main/scala/inheritance-shadowing.scala:9:14 
9 |      println(x)
  |              ^
  |              Reference to x is ambiguous,
  |              it is both defined in object B
  |              and inherited subsequently in class C

Scala 3 遷移編譯 可以自動透過將 println(x) 替換為 println(this.x) 來消除程式碼的歧義。

非私有建構函式在私有類別中

Scala 3 編譯器要求私有類別的建構函式必須是私有的。

例如,在範例中

package foo

private class Bar private[foo] () {}

如果你嘗試在 Scala 3 中編譯,你應該會收到以下錯誤訊息

-- Error: /home/piquerez/scalacenter/scala-3-migration-guide/incompat/access-modifier/src/main/scala-2.13/access-modifier.scala:4:19 
4 |  private class Bar private[foo] ()
  |                   ^
  |      non-private constructor Bar in class Bar refers to private class Bar
  |      in its type signature (): foo.Foo.Bar

Scala 3 遷移編譯 會警告這一點,但沒有提供自動重寫。

解決方案是將建構函式設為私有的,因為類別是私有的。

抽象覆寫

在 Scala 3 中,使用抽象 def 覆寫具體 def 會導致子類別將 def 視為抽象,而在 Scala 2 中則視為具體。

在以下程式碼片段中,Scala 2.13 編譯器將 C 中的 bar 方法視為具體,但 Scala 3 編譯器將其視為抽象,導致出現以下錯誤。

trait A {
  def bar(x: Int): Int = x + 3
}

trait B extends A {
  def bar(x: Int): Int
}

class C extends B // In Scala 3, Error: class C needs to be abstract, since def bar(x: Int): Int is not defined

此行為已在 Dotty 議題 #4770 中決定。

一個簡單的修復方法是直接移除抽象 def,因為在實務上它在 Scala 2 中沒有任何作用。

案例類別伴生物件

案例類別的伴生物件不再延伸任何 Function{0-23} 特質。特別是,它不會繼承它們的方法:tupledcurriedandThencompose

例如,以下不再被允許

case class Foo(x: Int, b: Boolean)

Foo.curried(1)(true)
Foo.tupled((2, false))

一個跨編譯的解決方案是明確 eta 展開方法 Foo.apply

-Foo.curried(1)(true)
+(Foo.apply _).curried(1)(true)

-Foo.tupled((2, false))
+(Foo.apply _).tupled((2, false))

或者,基於效能考量,你可以引入一個中間函式值。

val fooCtr: (Int, Boolean) => Foo = (x, b) => Foo(x, b)

fooCtr.curried(1)(true)
fooCtr.tupled((2, false))

明確呼叫 unapply

在 Scala 中,案例類別有一個自動產生的萃取器方法,稱為其伴生物件中的 unapply。它的簽章在 Scala 2.13 和 Scala 3 之間有所變更。

新的簽章沒有選項(請參閱新的 模式比對 參考),當 unapply 被明確呼叫時,這會導致不相容性。

請注意,這個問題不會影響使用者定義的萃取器,其簽章在 Scala 版本中保持不變。

給定以下案例類別定義

case class Location(lat: Double, long: Double)

Scala 2.13 編譯器會產生以下 unapply 方法

object Location {
  def unapply(location: Location): Option[(Double, Double)] = Some((location.lat, location.long))
}

而 Scala 3 編譯器會產生

object Location {
  def unapply(location: Location): Location = location
}

因此,以下程式碼在 Scala 3 中不再編譯。

def tuple(location: Location): (Int, Int) = {
  Location.unapply(location).get // [E008] In Scala 3, Not Found Error: value get is not a member of Location
}

在 Scala 3 中,可能的解決方案是使用樣式繫結

def tuple(location: Location): (Int, Int) = {
-  Location.unapply(location).get
+  val Location(lat, lon) = location
+  (lat, lon)
}

隱形的 Bean 屬性

BeanProperty 註解產生的 getter 和 setter 方法現在在 Scala 3 中是隱形的,因為它們的主要用例是與 Java 框架的互操作性。

例如,以下 Scala 2 程式碼將無法在 Scala 3 中編譯

class Pojo() {
  @BeanProperty var fooBar: String = ""
}

val pojo = new Pojo()

pojo.setFooBar("hello") // [E008] In Scala 3, Not Found Error: value setFooBar is not a member of Pojo

println(pojo.getFooBar()) // [E008] In Scala 3, Not Found Error: value getFooBar is not a member of Pojo

在 Scala 3 中,解決方案是呼叫更慣用的 pojo.fooBar getter 和 setter。

val pojo = new Pojo()

-pojo.setFooBar("hello")
+pojo.fooBar = "hello"

-println(pojo.getFooBar())
+println(pojo.fooBar)

=> T 作為類型引數

形式為 => T 的類型不能再用作類型參數的引數。

此決定在 Scala 3 原始碼的此註解 中說明。

例如,不允許將類型為 Int => (=> Int) => Int 的函數傳遞給 uncurried 方法,因為它會將 => Int 指定給類型參數 T2

-- [E134] Type Mismatch Error: src/main/scala/by-name-param-type-infer.scala:3:41
3 |  val g: (Int, => Int) => Int = Function.uncurried(f)
  |                                ^^^^^^^^^^^^^^^^^^
  |None of the overloaded alternatives of method uncurried in object Function with types
  | [T1, T2, T3, T4, T5, R]
  |  (f: T1 => T2 => T3 => T4 => T5 => R): (T1, T2, T3, T4, T5) => R
  | [T1, T2, T3, T4, R](f: T1 => T2 => T3 => T4 => R): (T1, T2, T3, T4) => R
  | [T1, T2, T3, R](f: T1 => T2 => T3 => R): (T1, T2, T3) => R
  | [T1, T2, R](f: T1 => T2 => R): (T1, T2) => R
  |match arguments ((Test.f : Int => (=> Int) => Int))

解決方案取決於情況。在給定的範例中,你可以

  • 定義你自己的 uncurried 方法,並使用適當的簽章
  • 內嵌 uncurried 的實作

萬用字元類型引數

Scala 3 無法將高階抽象類型成員的應用程式減少為萬用字元引數。

例如,以下 Scala 2 程式碼將無法在 Scala 3 中編譯

trait Example {
  type Foo[A]

  def f(foo: Foo[_]): Unit // [E043] In Scala 3, Type Error: unreducible application of higher-kinded type Example.this.Foo to wildcard arguments 
}

我們可以使用類型參數來修復此問題

-def f(foo: Foo[_]): Unit
+def f[A](foo: Foo[A]): Unit

但是當 Foo 本身用作類型引數時,這個簡單的解決方案無法運作。

def g(foos: Seq[Foo[_]]): Unit

在這種情況下,我們可以在 Foo 周圍使用包裝類別

+class FooWrapper[A](foo: Foo[A])

-def g(foos: Seq[Foo[_]]): Unit
+def g(foos: Seq[FooWrapper[_]]): Unit

此頁面的貢獻者