在 GitHub 上编辑此页面

与 Scala 2 隐式的关系

Scala 3 中的许多(但并非全部)新的上下文抽象功能都可以映射到 Scala 2 的隐式。此页面概述了新旧功能之间的关系。

使用 Scala 2 隐式模拟 Scala 3 上下文抽象概念

给定实例

给定实例可以映射到隐式对象、类和隐式方法的组合。

  1. 没有参数的给定实例映射到隐式对象。例如,

    given intOrd: Ord[Int] with { ... }
    

    映射到

    implicit object intOrd extends Ord[Int] { ... }
    
  2. 带参数的给定实例映射到类和隐式方法的组合。例如,

    given listOrd[T](using ord: Ord[T]): Ord[List[T]] with { ... }
    

    映射到

    class listOrd[T](implicit ord: Ord[T]) extends Ord[List[T]] { ... }
    final implicit def listOrd[T](implicit ord: Ord[T]): listOrd[T] =
     new listOrd[T]
    
  3. 别名给定实例映射到隐式方法或隐式惰性值。如果别名既没有类型也没有上下文参数,则将其视为惰性值,除非右侧是一个简单引用,在这种情况下,我们可以使用转发器指向该引用而不缓存它。

示例

given global: ExecutionContext = new ForkJoinContext()

val ctx: Context
given Context = ctx

将映射到

final implicit lazy val global: ExecutionContext = new ForkJoinContext()
final implicit def given_Context = ctx

匿名给定实例

匿名给定实例获取编译器合成的名称,这些名称以可重现的方式从实现的类型中生成。例如,如果上面给出的 IntOrdListOrd 的名称被省略,则会合成以下名称

given given_Ord_Int: Ord[Int] with { ... }
given given_Ord_List[T](using ord: Ord[T]): Ord[List[T]] with { ... }

合成的类型名称由以下部分组成

  1. 前缀 given_
  2. 实现的类型的简单名称(省略任何前缀),
  3. 这些类型的顶级参数类型构造函数的简单名称。

元组被视为透明的,即类型 F[(X, Y)] 将获得合成的名称 F_X_Y。直接实现的函数类型 A => B 表示为 A_to_B。用作其他类型构造函数参数的函数类型表示为 Function

使用子句

使用子句在很大程度上对应于 Scala 2 的隐式参数子句。例如,

def max[T](x: T, y: T)(using ord: Ord[T]): T

将被编写为

def max[T](x: T, y: T)(implicit ord: Ord[T]): T

在 Scala 2 中。主要区别在于此类参数的应用。使用子句的参数的显式参数必须使用 (using ...) 编写,以反映定义语法。例如,max(2, 3)(using IntOrd)。Scala 2 使用普通应用 max(2, 3)(IntOrd)。Scala 2 语法有一些固有的歧义和限制,新语法克服了这些歧义和限制。例如,旧语法中没有多个隐式参数列表,即使可以使用“Aux”模式中的辅助对象模拟它们也是如此。

summon 方法对应于 Scala 2 中的 implicitly。它与 Shapeless 中的 the 方法完全相同。summon(或 the)与 implicitly 之间的区别在于 summon 可以返回比所请求类型更精确的类型。

上下文界限

上下文界限在两种语言版本中都是相同的。它们扩展到隐式参数的各个形式。

注意:为了简化迁移,Scala 3 中的上下文界限在有限时间内映射到旧式隐式参数,这些参数可以在 using 子句或普通参数列表中传递。一旦旧式隐式参数被弃用,上下文界限将映射到 using 子句。

扩展方法

扩展方法在 Scala 2 中没有直接对应项,但可以使用隐式类来模拟它们。例如,扩展方法

extension (c: Circle)
  def circumference: Double = c.radius * math.Pi * 2

可以通过以下方式在一定程度上模拟

implicit class CircleDecorator(c: Circle) extends AnyVal {
  def circumference: Double = c.radius * math.Pi * 2
}

在给定实例中实现的特质中的抽象扩展方法在 Scala 2 中没有直接对应项。模拟这些方法的唯一方法是通过导入使隐式类可用。Simulacrum 宏库可以在某些情况下自动化此过程。

类型类派生

类型类派生在 Scala 2 语言中没有直接对应项。可以通过基于宏的库(例如 ShapelessMagnoliascalaz-deriving)实现类似的功能。

上下文函数类型

上下文函数类型在 Scala 2 中没有类似项。

隐式按名称参数

Scala 2 中不支持隐式按名称参数,但可以在一定程度上通过 Shapeless 中的 Lazy 类型进行模拟。

在 Scala 3 中模拟 Scala 2 隐式

隐式转换

Scala 2 中的隐式转换方法可以在 Scala 3 中表示为 scala.Conversion 类的给定实例。例如,可以编写

implicit def stringToToken(str: String): Token = new Keyword(str)

而不是

given stringToToken: Conversion[String, Token] with
  def apply(str: String): Token = KeyWord(str)

given stringToToken: Conversion[String, Token] = KeyWord(_)

隐式类

Scala 2 中的隐式类通常用于定义扩展方法,Scala 3 中直接支持扩展方法。隐式类的其他用途可以通过一对常规类和给定的 Conversion 实例来模拟。

隐式值

Scala 2 中的隐式 val 定义可以在 Scala 3 中使用常规 val 定义和别名 given 来表示。例如,Scala 2 的

lazy implicit val pos: Position = tree.sourcePos

可以用 Scala 3 表示为

lazy val pos: Position = tree.sourcePos
given Position = pos

抽象隐式

Scala 2 中的抽象隐式 valdef 可以使用常规抽象定义和给定的别名在 Scala 3 中表示。例如,Scala 2 的

implicit def symDecorator: SymDecorator

可以用 Scala 3 表示为

def symDecorator: SymDecorator
given SymDecorator = symDecorator

实现状态和时间表

Scala 3 实现同时实现了 Scala 2 的隐式和新抽象。事实上,对 Scala 2 隐式的支持是 2.13 和 Scala 3 之间通用语言子集的重要组成部分。通过提供自动重写功能,将支持向新抽象的迁移。

根据采用模式,旧式隐式可能会在 Scala 3.0 之后的版本中开始弃用。