Scala 3 移轉指南

Scala 3 語法重寫

語言

Scala 3 透過新的控制結構和重要的縮排語法擴充了 Scala 語言的語法。兩者都是選用的,因此 Scala 2 程式碼風格在 Scala 3 中仍然完全有效。

控制結構的新語法可以撰寫 if 運算式的條件、while 迴圈的條件或 for 運算式的產生器,而不用加上括號。

重要的縮排語法讓大括號 {...} 在許多情況下都不需要:類別和方法主體、if 運算式、match 運算式等等。您可以在 Scala 3 參考網站的 選用大括號 頁面中找到完整的說明。

手動將現有的 Scala 程式碼轉換為新的語法既繁瑣又容易出錯。在本章中,我們將展示如何使用編譯器自動將您的程式碼從經典的 Scala 2 風格重寫為新的風格,反之亦然。

語法重寫選項

讓我們先展示一下我們可用的編譯器選項,以達成我們的目標。如果我們只在命令列中輸入 scalac,它會列印出我們可以使用的所有選項。對於我們的目的,我們將使用以下五個選項

$ scalac
Usage: scalac <options> <source files>
where possible standard options include:
...
-indent</b>            Allow significant indentation
...
-new-syntax</b>        Require `then` and `do` in control expressions.
-no-indent</b>          Require classical {...} syntax, indentation is not significant.
...
-old-syntax</b>        Require `(...)` around conditions.
...
-rewrite</b>           When used in conjunction with a `...-migration` source version,
                       rewrites sources to migrate to new version.
...

前四個選項中的每一個都對應到一個特定的語法

語法 選項
新的控制結構 -new-syntax
舊的控制結構 -old-syntax
語法 編譯器選項
顯著縮排 -indent
傳統大括號 -noindent

我們將在後續詳細說明,這些選項可與 -rewrite 選項結合使用,以自動轉換為特定語法。讓我們看看這在一個小範例中如何運作。

新的語法重寫

給定以下以 Scala 2 樣式撰寫的原始程式碼。

case class State(n: Int, minValue: Int, maxValue: Int) {
  
  def inc: State =
    if (n == maxValue)
      this
    else
      this.copy(n = n + 1)
  
  def printAll: Unit = {
    println("Printing all")
    for {
      i <- minValue to maxValue
      j <- 0 to n
    } println(i + j)
  }
}

我們將能夠透過兩個步驟自動將其移至新的語法:首先使用新的控制結構重寫 (-new-syntax -rewrite),然後使用顯著縮排重寫 (-indent -rewrite)。

-indent 選項不適用於傳統控制結構。因此,請務必按正確順序執行兩個步驟。

遺憾的是,編譯器無法同時套用兩個步驟:-indent -new-syntax -rewrite

新的控制結構

我們可以透過將 -new-syntax -rewrite 選項新增到建置工具中 scalac 選項清單來使用它們。

// build.sbt, for Scala 3 project
scalacOptions ++= Seq("-new-syntax", "-rewrite")

編譯程式碼後,結果如下所示

case class State(n: Int, minValue: Int, maxValue: Int) {
  
  def inc: State =
    if n == maxValue then
      this
    else
      this.copy(n = n + 1)
  
  def printAll: Unit = {
    println("Printing all")
    for
      i <- minValue to maxValue
      j <- 0 to n
    do println(i + j)
  }
}

請注意,n == maxValue 周圍的括號消失了,i <- minValue to maxValuej <- 0 to n 產生器的周圍大括號也消失了。

顯著縮排語法

在第一次重寫之後,我們可以使用顯著縮排語法來移除剩餘的大括號。為此,我們將 -indent 選項與 -rewrite 選項結合使用。這將引導我們到以下版本

case class State(n: Int, minValue: Int, maxValue: Int):
  
  def inc: State =
    if n == maxValue then
      this
    else
      this.copy(n = n + 1)
  
  def printAll: Unit =
    println("Printing all")
    for
      i <- minValue to maxValue
      j <- 0 to n
    do println(i + j)

移回傳統語法

從我們程式碼範例的最新狀態開始,我們可以向後移回其初始狀態。

讓我們在保留新控制結構的同時使用大括號重寫程式碼。在使用 -no-indent -rewrite 選項編譯後,我們將取得以下結果

case class State(n: Int, minValue: Int, maxValue: Int) {
  
  def inc: State =
    if n == maxValue then
      this
    else
      this.copy(n = n + 1)
  
  def printAll: Unit = {
    println("Printing all")
    for {
      i <- minValue to maxValue
      j <- 0 to n
    }
    do println(i + j)
  }
}

再重寫一次,使用 -old-syntax -rewrite,將我們帶回原始的 Scala 2 樣式程式碼。

case class State(n: Int, minValue: Int, maxValue: Int) {
  
  def inc: State =
    if (n == maxValue)
      this
    else
      this.copy(n = n + 1)
  
  def printAll: Unit = {
    println("Printing all")
    for {
      i <- minValue to maxValue
      j <- 0 to n
    }
    println(i + j)
  }
}

有了最後一次的重寫,我們已經完成了整個循環。

在循環使用語法版本時格式遺失

當使用格式化工具(例如 scalafmt)將自訂格式套用至您的程式碼時,在不同的 Scala 3 語法變體之間來回循環可能會在完成整個循環時造成差異。

強制執行特定語法

可以在單一程式碼庫中混合舊語法和新語法。雖然我們建議不要這麼做,因為這會降低可讀性並使程式碼更難維護。更好的方法是選擇一種樣式,並將其一致地套用至整個程式碼庫。

-no-indent-new-syntax-old-syntax 可用作獨立選項,以強制執行一致的語法。

例如,使用 -new-syntax 選項時,編譯器會在遇到 if 條件周圍的括號時發出錯誤。

-- Error: /home/piquerez/scalacenter/syntax/example.scala:6:7 ------------------
6 |    if (n == maxValue)
  |       ^^^^^^^^^^^^^^^
  |This construct is not allowed under -new-syntax.
  |This construct can be rewritten automatically under -new-syntax -rewrite -source 3.0-migration.

-indent 語法始終是選用的,編譯器無法強制執行。

此頁面的貢獻者