Scala中given ... with {}与given ... = new {}为何表现不同?
Scala中
given两种实现方式的差异解析 问题场景
以下代码尝试通过DoubleEncoder串联两个Transformer实现Int -> Y的转换,但最后一行编译失败:
// 定义转换特质 trait Transformer[A, B] { def transform(a: A): B } // 定义带中间类型的双层编码器特质 trait DoubleEncoder[From, To] { type Intermediate def transformer(using Transformer[From, Intermediate], Transformer[Intermediate, To]): Transformer[From, To] } // 定义中间类型及对应的Transformer实例 class X(i: Int) class Y(x: X) given Transformer[Int, X] = X.apply _ given Transformer[X, Y] = Y.apply _ // 原始的DoubleEncoder实现(编译失败) given DoubleEncoder[Int, Y] = new DoubleEncoder[Int, Y] { type Intermediate = X override def transformer(using t1: Transformer[Int, X], t2: Transformer[X, Y]): Transformer[Int, Y] = new Transformer { def transform(i: Int): Y = t2.transform(t1.transform(i)) } } // 通过DoubleEncoder自动生成Transformer的隐式规则 given [P, Q](using de: DoubleEncoder[P, Q])(using Transformer[P, de.Intermediate], Transformer[de.Intermediate, Q]): Transformer[P, Q] = de.transformer // 编译失败:无法找到Transformer[Int, Y]的隐式实例 summon[Transformer[Int, Y]].transform(1)
编译报错核心为:无法为DoubleEncoder[Int, Y]找到匹配的Transformer[Int, de.Intermediate]和Transformer[de.Intermediate, Y]实例,编译器无法推断de.Intermediate的具体类型。
将DoubleEncoder的实现修改为以下写法后,代码即可正常编译运行:
// 修改后的DoubleEncoder实现(编译成功) given DoubleEncoder[Int, Y] with { type Intermediate = X override def transformer(using t1: Transformer[Int, X], t2: Transformer[X, Y]): Transformer[Int, Y] = new Transformer { def transform(i: Int): Y = t2.transform(t1.transform(i)) } }
核心原因解析
1. 两种写法的类型信息差异
- 第一种写法(
new DoubleEncoder[Int, Y] { ... }):通过匿名类创建实例后,将其向上转型为DoubleEncoder[Int, Y]。这个过程会丢失实例内部的细化类型信息——编译器只能识别它是DoubleEncoder[Int, Y],但无法知晓其Intermediate类型被具体化为X,因为trait DoubleEncoder本身的Intermediate是抽象的。 - 第二种写法(
given ... with { ... }):这是Scala 3专为given设计的特质实现语法,不会进行向上转型。编译器会将该实例视为DoubleEncoder[Int, Y] { type Intermediate = X }的细化类型,完整保留Intermediate的具体类型信息。
2. 编译失败的直接原因
当处理最后一个通用given规则时,需要根据de: DoubleEncoder[P, Q]的Intermediate类型匹配对应的Transformer实例。第一种写法的DoubleEncoder实例丢失了Intermediate = X的信息,编译器无法推断出需要匹配Transformer[Int, X]和Transformer[X, Y],导致隐式解析失败。
补充提问解答:为何参数类型是DoubleEncoder[P, Q]仍有差异
虽然通用given的参数声明为DoubleEncoder[P, Q],但Scala的隐式解析会追踪实例的实际运行时类型(细化类型)。第二种写法的实例带有Intermediate = X的细化类型标记,当它被传入时,编译器能通过类型推导确定de.Intermediate = X,从而匹配到已定义的Transformer实例;而第一种写法的实例因为向上转型丢失了该标记,编译器无法完成类型推断,导致隐式链断裂。
内容的提问来源于stack exchange,提问作者nedsociety
相关产品推荐
相关产品推荐

