Scala 使用协变类型的私有可变集合

Scala 使用协变类型的私有可变集合,scala,covariant,Scala,Covariant,我有一个协变的Scala类型东西[+B]。该实现使用内部可变队列: private val队列:异步队列[B]() AsyncQueue是一个定制的可变队列实现,具有我无法在不可变版本中轻松实现的特殊属性。因为它是可变的,所以AsyncQueue是不变的。所以我不能在我的协变类型中使用它 由于queue是私有的,我可以保证我的代码的正确性:例如,我不会尝试将queue分配给queue[Any]类型的引用。如果不使用强制转换,我如何使东西在B中保持协变 (使用强制转换的解决方案是声明一个Async

我有一个协变的Scala类型
东西[+B]
。该实现使用内部可变队列:

private val队列:异步队列[B]()

AsyncQueue是一个定制的可变队列实现,具有我无法在不可变版本中轻松实现的特殊属性。因为它是可变的,所以AsyncQueue是不变的。所以我不能在我的协变类型
中使用它

由于
queue
是私有的,我可以保证我的代码的正确性:例如,我不会尝试将
queue
分配给
queue[Any]
类型的引用。如果不使用强制转换,我如何使
东西在
B
中保持协变

(使用强制转换的解决方案是声明一个
AsyncQueue[Object]
并将对象强制转换为enqueue/dequeue,这非常难看。)


ETA:我理解类型协变,我理解为什么我不能声明协变类型的AsyncQueue,或者使AsyncQueue本身协变。我的问题是如何设计此代码以避免到处使用强制类型转换。

如果
B
Thing[+B]
中是协变的,那么您将无法使
B
Thing
中处于逆变位置,即

def put(b:B) {...} // will fail to compile, can't use a covariant type in this position
但是可以为
东西
创建两个接口,一个在协变位置使用类型,另一个在逆变位置使用类型,如下所示:

trait ThingProduce[+B] {
  def get: B 
}
trait ThingConsume[-B] {
  def put(b: B)
}

class Thing[B] extends ThingConsume[B] with ThingProduce[B] {
  private val queue = new scala.collection.mutable.Queue[B]

  def put(b: B) {queue.enqueue(b)}
  def get: B = queue.dequeue

  def both(b: B): B = ???
}
因此,对于类层次结构:

class Animal
class Mammal extends Animal
class Dog extends Mammal
可以做到以下几点:

val mammalThing: Thing[Mammal] = new Thing[Mammal]{}
val dogConsumer: ThingConsume[Dog] = mammalThing
val animalProducer: ThingProduce[Animal] = mammalThing
但不是:

val dogConsumer: ThingConsume[Dog] = animalProducer
//or
val animalProducer: ThingProduce[Animal] = dogConsumer

因此,
事物[B]
既可以看作是共变的,也可以看作是逆变的,但仅限于某些成员。

您需要
@uncheckedVariance

import scala.annotation.unchecked.uncheckedVariance

class A[T] {}
class B[+T] {
  val a: A[T @uncheckedVariance] = null
}

甚至Scala标准库也利用了
@uncheckedVariance
,特别是允许不变的可变集合从协变特征继承。

你可以通过将其设置为私有[此]
,使你的成员不受方差检查的影响

预料之中

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() }


这听起来像是导致java泛型设计者将差异声明放在对象使用点而不是声明位置的确切用例。
Thing
上使用
queue
的方法是什么?
AsyncQueue
是一个基于未来的有界队列。一个方法调用
queue.enqueue
,它返回一个未来,该未来在项目排队时完成(在队列中没有空间时等待)。另一个调用
queue.dequeue
,它返回一个未来,该未来在项目出列时完成(队列为空时等待)。这些方法让我可以构建一个基于未来的状态机,在队列已满时暂停。进入队列的
B
s从哪里来?您能否举例说明您必须在何处使用强制转换。
B
s来自用户(即通过
Thing
的公共API),并被推到队列中。如果我声明了一个
AsyncQueue[AnyRef]
,那么我必须将退出队列的值从
AnyRef
转换回
B
。我只能接受一个答案,但我也很高兴向您学习。谢谢
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() }
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
scala> class X
defined class X

scala> val xs = new Thingie[X]
xs: Thingie[X] = Thingie@49f5c307

scala> xs.next
res1: X = X@4816c290