Scala 3 遷移指南

已移除功能

語言

有些功能已移除以簡化語言。這些變更大多數可以在 Scala 3 遷移編譯期間自動處理。

不相容性 Scala 2.13 Scala 3 遷移重寫 Scalafix 規則
符號文字 不建議使用  
do-while 結構    
自動應用 不建議使用
值 eta 展開 不建議使用
any2stringadd 轉換 不建議使用  
早期初始化器 不建議使用    
存在類型 功能警告    
@specialized 不建議使用    

符號文字

符號文字語法在 Scala 2.13 中不建議使用,並已在 Scala 3 中移除。但 scala.Symbol 類別仍然存在,因此每個字串文字都可以安全地替換為 Symbol 的應用。

這段程式碼無法使用 Scala 3 編譯

val values: Map[Symbol, Int] = Map('abc -> 1)

val abc = values('abc) // In Scala 3, Migration Warning: symbol literal 'abc is no longer supported

Scala 3 遷移編譯將程式碼重寫為

val values: Map[Symbol, Int] = Map(Symbol("abc") -> 1)

-val abc = values('abc)
+val abc = values(Symbol("abc"))

儘管 Symbol 類別在轉換期間很有用,但請注意它已過時,且將在未來的版本中從 scala-library 中移除。建議您第二步將每個 Symbol 用法替換為純文字字串 "abc" 或自訂專用類別。

do-while 結構

新的控制語法 中,do 關鍵字已取得不同的意義。

為避免混淆,傳統的 do <body> while (<cond>) 結構已被捨棄。建議使用等效的 while ({ <body>; <cond> }) (),它可以跨編譯,或使用新的 Scala 3 語法 while { <body>; <cond> } do ()

以下程式碼片段無法使用 Scala 3 編譯。

do { // In Scala 3, Migration Warning: `do <body> while <cond>` is no longer supported
  i += 1
} while (f(i) == 0)

Scala 3 遷移編譯會將其改寫為。

while ({ {
  i += 1
} ; f(i) == 0}) ()

自動應用

自動應用是呼叫空括號方法的語法,例如 def toInt(): Int,而不傳遞空引數清單。它在 Scala 2.13 中已過時,並在 Scala 3 中捨棄。

以下程式碼在 Scala 3 中無效

object Hello {
  def message(): String = "Hello"
}

println(Hello.message) // In Scala 3, Migration Warning: method message must be called with () argument

Scala 3 遷移編譯會將其改寫為

object Hello {
  def message(): String = "Hello"
}

-println(Hello.message)
+println(Hello.message())

自動應用在 Scala 3 參考文件 此頁面 中有詳細說明。

值 eta 展開

Scala 3 引入了 自動 Eta 展開,這將使方法到值語法 m _ 過時。此外,Scala 3 不再允許將值 eta 展開為零元函式。

因此,以下程式碼片段在 Scala 3 中無效

val x = 1
val f: () => Int = x _ // In Scala 3, Migration Warning: The syntax `<function> _` is no longer supported;

Scala 3 遷移編譯會將其改寫為

val x = 1
-val f: () => Int = x _
+val f: () => Int = (() => x)

any2stringadd 轉換

隱含的 Predef.any2stringadd 轉換在 Scala 2.13 中已過時,並在 Scala 3 中捨棄。

以下程式碼片段在 Scala 3 中不再編譯。

val str = new AnyRef + "foo" // In Scala 3, Error: value + is not a member of Object

必須明確套用轉換為 String,例如使用 String.valueOf

-val str = new AnyRef + "foo"
+val str = String.valueOf(new AnyRef) + "foo"

此重寫可透過 fix.scala213.Any2StringAdd Scalafix 規則套用在 scala/scala-rewrites 中。

早期初始化器

早期初始化器在 Scala 2.13 中已棄用,並在 Scala 3 中移除。它們很少使用,而且大多用於彌補 特質參數 的不足,而特質參數現已在 Scala 3 中支援。

這就是為什麼以下程式碼在 Scala 3 中無法再編譯的原因。

trait Bar {
  val name: String
  val size: Int = name.size
}

object Foo extends {
  val name = "Foo"
} with Bar

Scala 3 編譯器產生兩個錯誤訊息

-- Error: src/main/scala/early-initializer.scala:6:19 
6 |object Foo extends {
  |                   ^
  |                   `extends` must be followed by at least one parent
-- [E009] Syntax Error: src/main/scala/early-initializer.scala:8:2 
8 |} with Bar
  |  ^^^^
  |  Early definitions are not supported; use trait parameters instead

它建議使用特質參數,這將提供我們

trait Bar(name: String) {
  val size: Int = name.size
}

object Foo extends Bar("Foo")

由於特質參數在 Scala 2.13 中不可用,因此無法跨編譯。如果您需要跨編譯的解決方案,可以使用一個中間類別,它將早期初始化的 valvar 作為建構函式參數。

abstract class BarEarlyInit(val name: String) extends Bar

object Foo extends BarEarlyInit("Foo")

在類別的情況下,也可以使用具有固定值的次要建構函式,如下所示

class Fizz private (val name: String) extends Bar {
  def this() = this("Fizz")
}

早期初始化器在 Scala 2 中的另一個用例是子類別中的私有狀態,可透過超類別的建構函式(透過覆寫的方法)存取

class Adder {
  var sum = 0
  def add(x: Int): Unit = sum += x
  add(1)
}
class LogAdder extends {
  private var added: Set[Int] = Set.empty
} with Adder {
  override def add(x: Int): Unit = { added += x; super.add(x) }
}

此案例可透過將私有狀態移至巢狀 object 來重構,此巢狀 object 會依需求初始化

class Adder {
  var sum = 0
  def add(x: Int): Unit = sum += x
  add(1)
}
class LogAdder extends Adder {
  private object state {
    var added: Set[Int] = Set.empty
  }
  import state._
  override def add(x: Int): Unit = { added += x; super.add(x) }
}

存在類型

存在類型是 已移除的功能,這使得以下程式碼無效。

def foo: List[Class[T]] forSome { type T } // In Scala 3, Error: Existential types are no longer supported

存在類型是 Scala 2.13 中的實驗性功能,必須透過匯入 import scala.language.existentials 或設定 -language:existentials 編譯器旗標來明確啟用。

在 Scala 3 中,建議的解決方案是引入一個封裝類型,它會承載依賴類型

trait Bar {
  type T
  val value: List[Class[T]]
}

def foo: Bar

請注意,使用萬用字元引數 _? 通常較簡單,但並非總是可行。例如,您可以用 List[?] 取代 List[T] forSome { type T }

專門化

Scala 2 中的 @specialized 注解在 Scala 3 中會被忽略。

但是,對於專門化的 FunctionTuple 有有限的支援。

可以從 inline 宣告中獲得類似的優點。

此頁面貢獻者