类型差异问题 [英] Trouble with type variance
问题描述
所以,假设我有一个具有逆变类型参数的类:
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
- 执行
storage.iterate[MyAvroDoc]
而不必再次给它上边界,并且 - 执行
iterator.storage.getObject[AnotherAvroDoc]
无需转换存储以证明它可以读取此类对象.
- Do
storage.iterate[MyAvroDoc]
without having to give it the upper boundary again, and - 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]
是您提供路径的对象,并将输出 T
s.Storage
协变是完全合理的:例如,知道如何输出 String
s 的东西也输出 Any
s,所以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 T
s. It makes perfect sense for Storage
to be covariant: something that knows how to output String
s, for example, is also outputting Any
s, 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屋!