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"

此重写可以通过 scala/scala-rewrites 中的 fix.scala213.Any2StringAdd Scalafix 规则应用。

早期初始化器

早期初始化器在 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 中来重构此案例,该对象按需初始化

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 声明中获得类似的好处。

此页面的贡献者