为什么编译器不能链式转换?

t9d*_*puy 0 scala implicit type-conversion scala-3 given

T1, T2, T3三种类型。我们还定义了该类的两个给定实例Conversion,以便编译器可以从T1toT2和从T2to进行转换T3

下面的代码可以正常编译:

type T1
type T2
type T3

given Conversion[T1, T2] with
    override def apply(x: T1): T2 = ???

given Conversion[T2, T3] with
    override def apply(x: T2): T3 = ???

val t1: T1 = ???
val t2: T2 = t1
val t3: T3 = t2
Run Code Online (Sandbox Code Playgroud)

但是当我们尝试从T1到 时会发生什么T3?编译器不会让我们:

val t3: T3 = t1
             ^^
             Found:    (t1: T1)
             Required: T3
Run Code Online (Sandbox Code Playgroud)

我的问题:编译器无法本机(参见解决方法)链转换是否有特定原因?

我的解决方法:事实证明,我们可以通过定义从Ato的通用转换来隐式告诉编译器如何链接转换C,前提是我们知道如何转换AtoBBto C

given [A, B, C](using conv1: Conversion[A, B])(using conv2: Conversion[B, C]): Conversion[A, C] with
    override def apply(x: A): C = conv2.apply(conv1.apply(x))
Run Code Online (Sandbox Code Playgroud)

To 编译器现在可以进行链式转换:

val t3: T3 = t1 //OK
Run Code Online (Sandbox Code Playgroud)

加分点:这个新的通用转换甚至可以递归地调用自身,这意味着我们可以链接无限转换。

Lev*_*sey 5

这不是“不能”的问题,而是“不”的问题。这种限制是有意识地选择的。

Odersky、Spoon 和 Venners在 Scala 中编程(我引用的是第三版,使用 Scala 2 术语,但对术语差异取模后仍然适用):

一次一个规则:仅插入一个隐式[转换]。编译器永远不会重写x + yconvert1(convert2(x)) + y. 这样做会导致错误代码的编译时间急剧增加,并且会增加程序员编写的内容与程序实际执行的内容之间的差异。为了保持理智,编译器在尝试另一个隐式转换时不会插入进一步的隐式转换。但是,可以通过让隐式采用隐式参数来规避此限制。

详细阐述该论点,除了围绕程序的明显显式含义与应用转换后的实际含义之间的差异进行更主观的争论之外(人们可以很容易地争论(并且(隐式地......)几乎所有其他语言设计者都这样做)没有 Scala 风格的转换),尝试任何隐式转换都会过度增加编写的内容和程序实际执行的内容之间的差异),请考虑对转换深度没有限制的后果(回想一下格言:“零,一,或没有限制”)

如果我有一种没有 method 的类型,则编译器不会拒绝foo()类似 的表达式,直到它从 的 类型计算类型转换的传递闭包并发现没有一个具有方法。在 IDE 中,这意味着需要相当长的延迟才能出现“红色波浪线”(或者 IDE 可能会认为经过一定深度的转换后,它可能值得波浪线,而不是说 Scala 中有任何广泛使用的 IDE 具有历史记录)不同意实际编译器接受/拒绝的内容...)。x.foo()xfoo()

即使对于已接受的程序,也存在一种情况,即应尝试通过转换空间的所有可能路径:作为一般规则,编译器(除非有一个转换比其他转换“更具体”)不会在以下情况下应用转换:有多个候选可以应用,并且编译器无法知道有多少个候选,而无需尝试每一条路径(一旦发现第二个转换,它可能会停止,但在只有一个的典型情况下,它会找到一个传递接受前关闭)。通过限制一次转换,关于如何处理这一问题的争论就消失了。