scalacovariant

Using private mutable collections of a covariant type


I have a covariant Scala type Thing[+B]. The implementation uses an internal mutable queue:

private val queue : AsyncQueue[B]()

AsyncQueue is a custom mutable queue implementation, with special properties which I can't easily implement in an immutable version. Because it's mutable, AsyncQueue is invariant. So I can't use it in my covariant type Thing.

Since queue is private, I can guarantee the correctness of my code: e.g. I won't try to assign queue to a reference of type Queue[Any]. How can I make this work, keeping Thing covariant in B, without using casts?

(The solution with casts is to declare an AsyncQueue[Object] and cast objects on enqueue/dequeue, which is very ugly.)

ETA: I understand type covariance, and I understand why I shouldn't be able to declare an AsyncQueue of a covariant type, or make AsyncQueue itself covariant. My question is how to design this code to avoid using casts everywhere.


Solution

  • You can make your member immune to the variance check by making it private[this], according to the spec.

    scala> trait Thing[+A] { def next(): A }
    defined trait Thing
    

    expectedly,

    scala> class Thingie[+A](implicit t: ClassTag[A]) extends Thing[A] { val as = mutable.ArrayBuffer.fill[A](10)(t.runtimeClass.newInstance.asInstanceOf[A]) ; private val it = as.iterator ; def next() = it.next() }
    <console>:12: error: covariant type A occurs in invariant position in type => scala.collection.mutable.ArrayBuffer[A] of value as
           class Thingie[+A](implicit t: ClassTag[A]) extends Thing[A] { val as = mutable.ArrayBuffer.fill[A](10)(t.runtimeClass.newInstance.asInstanceOf[A]) ; private val it = as.iterator ; def next() = it.next() }
    

    but

    scala> class Thingie[+A](implicit t: ClassTag[A]) extends Thing[A] { private[this] val as = mutable.ArrayBuffer.fill[A](10)(t.runtimeClass.newInstance.asInstanceOf[A]) ; private val it = as.iterator ; def next() = it.next() }
    defined class Thingie
    

    and

    scala> class X
    defined class X
    
    scala> val xs = new Thingie[X]
    xs: Thingie[X] = Thingie@49f5c307
    
    scala> xs.next
    res1: X = X@4816c290