【问题标题】:Using private mutable collections of a covariant type使用协变类型的私有可变集合
【发布时间】:2014-07-05 23:59:58
【问题描述】:

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

private val queue : AsyncQueue[B]()

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

由于queue 是私有的,我可以保证我的代码的正确性:例如我不会尝试将queue 分配给Queue[Any] 类型的引用。我怎样才能使这项工作,保持ThingB 中的协变,而不使用强制转换?

(使用强制转换的解决方案是声明一个AsyncQueue[Object] 并在入队/出队上强制转换对象,这非常难看。)

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

【问题讨论】:

  • 这听起来像是导致 java 泛型的设计者将变量声明放在使用对象的位置而不是声明它们的位置的确切用例。
  • Thing上有哪些方法利用了queue
  • AsyncQueue 是一个基于 Future 的有界队列。一种方法调用queue.enqueue,它返回一个在项目入队时完成的Future(在队列中没有空间时等待)。另一个调用queue.dequeue,它返回一个Future,当一个项目出队时完成(队列为空时等待)。这些方法让我可以构建一个基于 Future 的状态机,它会在队列满时暂停。
  • 进入queueBs 来自哪里?你能举个例子说明你必须在哪里使用演员表吗?
  • Bs 来自用户(即通过Thing 的公共 API)并被推入队列。如果我声明一个AsyncQueue[AnyRef],那么我必须将出列值从AnyRef 转换回B

标签: scala covariant


【解决方案1】:

根据the spec,您可以通过将其设置为private[this] 来使您的成员免受差异检查。

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

预料之中,

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

【讨论】:

    【解决方案2】:

    你需要@uncheckedVariance:

    import scala.annotation.unchecked.uncheckedVariance
    
    class A[T] {}
    class B[+T] {
      val a: A[T @uncheckedVariance] = null
    }
    

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

    【讨论】:

    • 我只能接受一个答案,但我也很高兴向您学习。谢谢!
    【解决方案3】:

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

    def put(b:B) {...} // will fail to compile, can't use a covariant type in this position
    

    但是可以为Thing创建两个接口,一个用于协变位置,一个用于逆变位置,如下所示:

    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
    

    因此Thing[B] 可以被视为既是协变的又是逆变的,但仅限于某些成员。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-07-02
      • 1970-01-01
      • 2011-12-27
      • 1970-01-01
      • 2011-11-16
      • 2021-11-06
      • 2020-01-25
      • 1970-01-01
      相关资源
      最近更新 更多