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 语法始终是可选的,编译器无法强制执行它。

此页面的贡献者