类型差异问题 [英] Trouble with type variance

查看:47
本文介绍了类型差异问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以,假设我有一个具有逆变类型参数的类:

So, let's say I have a class with a contravariant type parameter:

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

类型参数的想法是将实现限制在它可以返回的类型的上边界.所以,Storage[Any] 可以读取任何东西,而 Storage[avro.SpecificRecord] 可以读取 avro 记录,但不能读取其他类:

The idea of the type parameter is to constrain the implementation to the upper boundary of types that it can return. So, Storage[Any] can read anything, while Storage[avro.SpecificRecord] can read avro records, but not other classes:

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

现在,我有一个实用程序类,可用于遍历给定位置中的对象:

Now, I have a utility class that can be used to iterate through objects in a given location:

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,以从辅助位置读取另一种类型的对象:

This works, but sometimes I need to access the underlying storage from the iterator downstream, to read another type of object from an aux location:

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

这当然不行,因为storage类型参数是通配符,没有证据证明可以用来读取AuxAvroDoc

This does not work, of course, because storage type parameter is a wildcard, and there is no proof that it can be used to read AuxAvroDoc

我尝试像这样修复它:

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

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

This works, but now I have to specify two type params when creating it, and that sucks :( I tried to work around it by adding a method to the Storage itself:

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

但这不会编译,因为它把 T 放在一个不变的位置:(如果我使 P 逆变,则 StorageIterator2[-P, +T <: P] 失败,因为它认为 P 出现在类型中的协变位置P 值 T.

But this doesn't compile because it puts T into an invariant position :( And if I make P contravariant, then StorageIterator2[-P, +T <: P] fails, because it thinks that P occurs in covariant position in type P of value T.

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

This last error I don't understand. Why exactly cannot P be contravariant here? If this position is really covariant (why is it?) then why does it allow me to specify an invariant parameter there?

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

Finally, does anyone have an idea how I can work around this? Basically, the idea is to be able to

  1. 执行 storage.iterate[MyAvroDoc] 而不必再次给它上边界,并且
  2. 执行 iterator.storage.getObject[AnotherAvroDoc] 无需转换存储以证明它可以读取此类对象.
  1. Do storage.iterate[MyAvroDoc] without having to give it the upper boundary again, and
  2. Do iterator.storage.getObject[AnotherAvroDoc] without having to cast the storage to prove that it can read this type of object.

感谢任何想法.

推荐答案

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

StorageIterator2[-P, +T <: P] fails because it is nonsensical. If you have a StorageIterator2[Foo, Bar], and Bar <: Foo, then because it is contravariant in the first parameter, it is also a StorageIterator[Nothing, Bar], but Nothing has no subtypes, so it is logically impossible that Bar <: Nothing, yet this is what must be true to have a StorageIterator2. Therefore, StorageIterator2 cannot exist.

根本问题是 Storage 不应该是逆变的.想一想 Storage[T] 是什么,就其给予用户的契约而言.Storage[T] 是您提供路径的对象,并将输出 Ts.Storage 协变是完全合理的:例如,知道如何输出 Strings 的东西也输出 Anys,所以Storage[String] <: Storage[Any] 是合乎逻辑的.你说它应该是相反的,Storage[T] 应该知道如何输出 T 的任何子类型,但是这将如何工作?如果有人事后向 T 添加子类型怎么办?T 甚至可以是 final 并且仍然有这个问题,因为单例类型.这是不必要的复杂,并反映在您的问题中.也就是说,Storage 应该是

The root problem is that Storage should not be contravariant. Think of what a Storage[T] is, in terms of the contract it gives to its users. A Storage[T] is an object that you give paths to, and will output Ts. It makes perfect sense for Storage to be covariant: something that knows how to output Strings, for example, is also outputting Anys, so it makes logical sense that Storage[String] <: Storage[Any]. You say that it should be the other way around, that a Storage[T] should know how to output any subtype of T, but how would that work? What if someone adds a subtype to T after the fact? T can even be final and still have this problem, because of singleton types. That is unnecessarily complicated, and is reflected in your problem. That is, Storage should be

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

这不会让您了解您在问题中给出的示例错误:

This does not open you up to the example mistake you gave in your question:

val x: Storage[avro.SpecificRecord] = ???
x.get(???): avro.SpecificRecord // ok
x.get(???): String // nope
(x: Storage[String]).get(???) // nope

现在,您的问题是您不能执行诸如 storage.getObject[T] 之类的操作,并且无法隐式转换.你可以改为 match:

Now, your issue is that you can't do something like storage.getObject[T] and have the cast be implicit. You can instead a match:

storage.getObject(path) match {
  case value: CorrectType => ...
  case _ => // Oops, something else. Error?
}

一个普通的asInstanceOf(不可取),或者你可以添加一个辅助方法到Storage,就像你之前的那样:

a plain asInstanceOf (undesirable), or you can add a helper method to Storage, like the one you had before:

def getCast[U <: T](path: String)(implicit tag: ClassTag[U]): Option[U] = tag.unapply(get(path))

这篇关于类型差异问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆