0

I want to understand why scala compiler emits error for the following snippet:

import scala.collection.mutable

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

The error ouput:

<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]]
           ^

As I understand it, the variance rule is deduced outside-in like this:

  1. x is at positive position.
  2. type parameters of mutable.Map.empty is at neutral position.
  3. Because of 2. and variance annotation of Builder[-Elem, +To], A is at negative position, and C is at positive position.

Therefore, A being covariant was misplaced in a negative (contravariant) position -Elem. Why does compiler say that it's an "invariant position"?

If I make A invariant, as in:

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

The error became:

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

I thought C position should have been positive (covariant), but compiler says it's also invariant?

Naitree
  • 1,088
  • 1
  • 14
  • 23
  • 1
    because `Map` requires invariant types – SwiftMango Jul 30 '20 at 07:25
  • 2
    I suppose it's because the value type of the mutable map is invariant. The second snippet compiles for immutable map, though. because the value type there is covariant – Saskia Jul 30 '20 at 07:27

2 Answers2

1

Because value type of Map is invariant. The value type may appear both at covariant position (like in the return value of get) and contravariant position (like in the right hand side of +=).

If the compiler allows you to proceed, there will be type safety problem.

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

Arie Xiao
  • 13,909
  • 3
  • 31
  • 30
  • Type parameter of `mutable.Map` is in neutral/invariant position, so nested type's (in this case `Builder`) type parameter positions all became neutral/invariant, is that right? – Naitree Jul 30 '20 at 08:18
  • 1
    Yes. For immutable Map[K, +V], you can have covariant type parameter in the value type. – Arie Xiao Jul 30 '20 at 08:33
0

The issue here is that you are trying to use the A, and C as genercis to val x. The compiler does not know which type to use.

You can try the following:

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

Which will ensure that A and C are from the type you want.

Tomer Shetah
  • 8,413
  • 7
  • 27
  • 35