类型差异问题

时间:2017-08-17 17:28:03

标签: scala types variance

所以,让我们说我有一个带有逆变型参数的类:

  trait Storage[-T] {
    def list(path: String): Seq[String]
    def getObject[R <: T](path: String): Future[R]
  }

type参数的想法是将实现约束到它可以返回的类型的上边界。因此,Storage[Any]可以读取任何内容,而Storage[avro.SpecificRecord]可以读取avro记录,但不能读取其他类:

def storage: Storage[avro.SpecificRecord] 
storage.getObject[MyAvroDoc]("foo") // works
storage.getObject[String]("bar") // fails

现在,我有一个实用工具类,可用于迭代给定位置的对象:

class StorageIterator[+T](
  val storage: Storage[_ >: T], 
  location: String
)(filter: String => Boolean) extends AbstractIterator[Future[T]] {
  val it = storage.list(location).filter(filter)
  def hasNext = it.hasNext
  def next = storage.getObject[T](it.next)
}

这有效,但有时我需要从迭代器下游访问底层storage,从辅助位置读取另一种类型的对象:

def storage: Storage[avro.SpecificRecord]
val iter = new StorageIterator[MyAvroDoc]("foo")
iter.storage.getObject[AuxAvroDoc](aux)

当然,这不起作用,因为storage类型参数是通配符,并且没有证据表明它可用于阅读AuxAvroDoc

我试着像这样解决它:

class StorageIterator2[P, +T <: P](storage: Storage[P])
  extends StorageIterator[T](storage)

这有效,但现在我必须在创建它时指定两种类型的参数,这很糟糕:( 我试图通过向Storage本身添加一个方法来解决它:

trait Storage[-T] {
  ... 
  def iterate[R <: T](path: String) = 
    new StorageIterator2[T, R](this, path)
}

但这并没有编译,因为它将T置于一个不变的位置:( 如果我使P逆变,那么StorageIterator2[-P, +T <: P]会失败,因为它会认为P occurs in covariant position in type P of value T

这个最后一个错误我不明白。为什么P在这里不能反变?如果这个位置真的是协变的(为什么会这样?)那为什么它允许我在那里指定一个不变量参数呢?

最后,有没有人知道如何解决这个问题? 基本上,这个想法是能够

  1. 执行storage.iterate[MyAvroDoc]而不必再次给它上限,
  2. 执行iterator.storage.getObject[AnotherAvroDoc]而不必转储存储以证明它可以读取此类对象。
  3. 任何想法都表示赞赏。

1 个答案:

答案 0 :(得分:1)

StorageIterator2[-P, +T <: P]失败,因为它没有意义。如果您有StorageIterator2[Foo, Bar]Bar <: Foo,那么因为它在第一个参数中是逆变的,所以它也是StorageIterator[Nothing, Bar],但是Nothing没有子类型,所以它是Bar <: Nothing在逻辑上是不可能的,但是StorageIterator2这是必须的。因此,StorageIterator2不能存在。

根本问题是Storage不应该是逆变的。根据它给用户的合同,想一想Storage[T] 是什么。 Storage[T]是您提供路径的对象,并将输出TStorage具有协变性是完全合理的:例如,知道如何输出String的东西也输出Any s,因此从逻辑意义上讲{{1} }。你说它应该是另一种方式,Storage[String] <: Storage[Any]应该知道如何输出Storage[T]的任何子类型,但是它会如何工作?如果有人在事后添加了一个子类型到T怎么办?由于单例类型,T甚至可以T并且仍然存在此问题。这是不必要的复杂,并反映在您的问题中。也就是说,final应为

Storage

这不会打开您在问题中给出的示例错误:

trait Storage[+T] {
  def list(path: String]: Seq[String]
  def get(path: String): T
}

现在,您的问题是,您无法执行val x: Storage[avro.SpecificRecord] = ??? x.get(???): avro.SpecificRecord // ok x.get(???): String // nope (x: Storage[String]).get(???) // nope 之类的操作,并且必须隐式转换。您可以改为storage.getObject[T]

match

普通storage.getObject(path) match { case value: CorrectType => ... case _ => // Oops, something else. Error? } (不受欢迎),或者您可以向asInstanceOf添加辅助方法,就像之前的方法一样:

Storage