2017-08-17 69 views
3

所以,讓我們說我有一個類與逆變類型參數:故障類型差異

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

類型參數的想法是,以限制實施的,它可以返回值類型的上邊界。因此,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]無需進行轉換存儲,以證明它可以讀取該類型的對象。

任何想法表示讚賞。

回答

1

StorageIterator2[-P, +T <: P]失敗,因爲它是無意義的。如果您有StorageIterator2[Foo, Bar]Bar <: Foo,那麼因爲它在第一個參數中是逆變的,所以它也是StorageIterator[Nothing, Bar],但Nothing沒有子類型,因此在邏輯上不可能有Bar <: Nothing,但這是必須如此才能擁有StorageIterator2 。因此,StorageIterator2不能存在。

根本問題是Storage不應該是逆變。根據它給用戶的合同,考慮一下Storage[T]。 A Storage[T]是您指定路徑的對象,並將輸出T s。對於Storage而言,它是非常有意義的:例如,知道如何輸出String的東西也正在輸出Any s,因此從邏輯意義上說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,就像一個你以前有:

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

「的存儲[T ]是你給路徑的對象,並且會輸出Ts。「這不是真的。 'T'不是'Storage'返回的對象的類型,而是它可能返回的所有_all_對象的共同超類型(當然,它們都是Ts,但這是一個技術性問題,而不是概念性特徵)。 它需要是不相容的,因爲'Storage [Any]'可以像'Stroage [Foo]'一樣使用。 – Dima

+0

「你說這應該是相反的,Storage [T]應該知道如何輸出T的任何子類型,但是如何工作?」 我在問題中展示了一個例子。例如,如果'T'是avro structs的基類,那麼'Storage'可以讀取任何avro doc。它可以是'ThriftStruct'來讀取thrift,或者''Message'用於protobuf,'Product'用於csv等等。對於應用程序中的每個avro doc都有一個單獨的'Storage'類是沒有意義的,這就是如果它是協變的,將會發生什麼。 – Dima

+0

Casting和'match''ing可能是一個解決方法,但這是一種java方式來做事情。避免這種類型的黑客正是首先出現類型差異的目的。這個想法是能夠在編譯時聲明:「該對象可以讀取任何avro文檔而不是其他任何東西」,而不會在運行時產生錯誤,當它運行到不應該的東西時。 – Dima