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 类定义

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

此页面的贡献者