为什么编译器将 Builder 中的逆变位置标记为不变?

Why contravariant position in Builder is marked as invariant by compiler?

我想了解为什么 scala 编译器会针对以下代码段发出错误:

import scala.collection.mutable

class X[+A, +C] {
  val x = mutable.Map.empty[Int, mutable.Builder[A, C]]
}

错误输出:

<console>:13: error: covariant type A occurs in invariant position in type => scala.collection.mutable.Map[Int,scala.collection.mutable.Builder[A,C]] of value x
       val x = mutable.Map.empty[Int, mutable.Builder[A, C]]
           ^

据我了解,方差规则是这样从外到内推导出来的:

  1. x处于正位置。
  2. mutable.Map.empty类型参数处于中立位置。
  3. 由于2.Builder[-Elem, +To]的方差标注,A处于负位置,C处于正位置

因此,A 协变被错误地置于负(逆变)位置 -Elem。为什么编译器说它是一个“不变位置”?

如果我使 A 不变,如:

class X[A, +C] {
  val x = mutable.Map.empty[Int, mutable.Builder[A, C]]
}

错误变为:

On line 2: error: covariant type C occurs in invariant position in type scala.collection.mutable.Map[Int,scala.collection.mutable.Builder[A,C]] of value x

我认为 C 位置应该是正的(协变的),但编译器说它也是不变的?

这里的问题是您正在尝试使用 A 和 C 作为 val x 的类属。 编译器不知道要使用哪种类型。

您可以尝试以下方法:

trait A1
trait C1
class X[A <: A1, C <: C1] {
  val x = mutable.Map.empty[Int, mutable.Builder[A, C]]
}

这将确保 A 和 C 来自您想要的类型。

因为Map的值类型是不变的。值类型可能同时出现在协变位置(如 get 的 return 值)和逆变位置(如 += 的右侧)。

如果编译器允许你继续,就会有类型安全问题。

class A0
class A1 extends A0
class A2 extends A0

class C0
class C1 extends C0
class C2 extends C0

class X[+A, +C] {
  val x = mutable.Map.empty[Int, mutable.Builder[A, C]]   // won't compile
}

val x1: X[A1, C1] = new X[A1, C1]
val x2: X[A0, C0] = x1            // works because X[+A, +C]

x2.x += (new A2, new SomeBuilder[A0, C2])  // breaks type system
// we placed A2 as key, while the original x1 only accepts A1
// similar conflict happens for the value position