2017-02-24 31 views
5

解決類型我有這些模型:在F-界多態性

trait Vehicle[T <: Vehicle[T]] { def update(): T } 
class Car extends Vehicle[Car] { def update() = new Car() } 
class Bus extends Vehicle[Bus] { def update() = new Bus() } 

如果我獲得Vehicle[Car]的實例,並調用update(),我會得到一個Car。由於Car擴展Vehicle[Car](或簡單地說,汽車是一個汽車[汽車]),我可以放心地明確設置結果的類型Vehicle[Car]

val car = new Car 
val anotherCar = car.update() 
val anotherCarAsVehicle: Vehicle[Car] = car.update() // works as expected 

但是,如果我想,說,把實例的CarBus連成一個列表,然後我到列表類型設置爲Vehicle[_ <: Vehicle[_]](具有簡單Vehicle[_]列表和元素調用update()將產生Any,但我希望能夠用update(),所以我不得不使用F-bound類型)。使用存在類型砸了類型的關係,因爲一旦我從車輛獲取潛在汽車/公交車,我再也不能將它轉換爲車輛因爲......好吧,這只是一些存在類型:

val seq = List[Vehicle[_ <: Vehicle[_]]](new Car, new Bus) 
val car = seq.head.update() 
val carAsVehicle: Vehicle[_ <: Vehicle[_]] = seq.head.update() // fails to compile 

所以,Vehicle用某種類型T參數化,這是Vehicle[T]的子類型。當我撕開T(使用update())時,在具體類型的情況下可以 - 例如,如果我剔除了Car,我可以放心地聲稱我剔除了Vehicle[Car],因爲Car <: Vehicle[Car]。但是如果我剔除存在類型,我無法做任何事情。之前的例子工作,因爲CarVehicle[Car],但在這種情況下_不是Vehicle[_]

要指定我的具體問題對於上面給出的模型(車輛,汽車,公交車),有沒有辦法實現這一點?

def sameType[T, U](a: T, b: U)(implicit evidence: T =:= U) = true 

val seq = List[Vehicle[_ <: Vehicle[_]]](new Car, new Bus) 

sameType(seq.head.update +: seq.tail, seq) // true 

請注意,你可以改變給定的特點,類別和類型的seq,但有一個限制:update()必須返回T,不Vehicle[T]

我知道使用無形HList可以解決這個問題,因爲我不需要使用存在類型(我只是簡單地列出了一輛汽車和一輛公交車,並且該類型的信息將被保留)。但是我想用這個簡單的List這個特殊的用例。

編輯

@RomKazanova是,將工作,當然,但我需要之前和之後update()保留同一類型(這裏所付出的努力的給予好評,雖然;))。

我相信,如果沒有HList或類似的數據結構,這是不可能的,因爲統一的汽車和公共汽車迫使我們使用車型,這種車型會丟失其底層類型是汽車,公交車還是別的東西(我們可以知道的是這是一些類型_ <: Vehicle)。但我想和你們覈對一下。

回答

4

我不是很好的生存類型,所以我不能解釋太多:-p但是,當您將seq的類型更改爲List[Vehicle[T] forSome {type T <: Vehicle[T]}]一切似乎都「解決」了。請注意,您必須將類型傳遞給List構造函數/ apply方法。

scala> val seq = List[Vehicle[T] forSome {type T <: Vehicle[T]}](new Car, new Bus) 
seq: List[Vehicle[T] forSome { type T <: Vehicle[T] }] = List([email protected], [email protected]) 

scala> sameType(seq.head.update +: seq.tail, seq) 
res3: Boolean = true 

scala> seq.head.update 
res4: T forSome { type T <: Vehicle[T] } = [email protected] 

scala> seq.head.update.update 
res5: T forSome { type T <: Vehicle[T] } = [email protected] 

scala> new Car +: seq 
res6: List[Vehicle[T] forSome { type T <: Vehicle[T] }] = List([email protected], [email protected], [email protected]) 

我認爲最主要的走出這個答案的是,這可以讓你拼出Vehicle類型構造函數的遞歸性質。

我不知道我會建議,雖然這...

+0

我本來以爲'列表[汽車[_ <:汽車[_]]]'是同一類型'名單[汽車[ T] forSome {type T <:Vehicle [T]}]'。作爲一般的經驗法則,我並不介意在任何地方使用'forSome',但我聽說它被Scala 2.13或2.14驅逐。無論如何,非常感謝你,這正是我期待的那種解決方案。 – slouc

+0

我確實從我的REPL記錄中刪除了所有的存在類型警告。所以我認爲這是一種只有通配符才能表達的存在感。我不確定*如果*它會被完全刪除,但如果是的話,我認爲最快的時候會是Dotty變成Scala 3.0或類似的東西。 –

+0

順便說一句,我意外地低估了你:)固定 – slouc

2

有兩種方法來解決它:

val carAsVehicle: Vehicle[_] = seq.head.update() 

或使用模式匹配

val carAsVehicle: Vehicle[Car] = seq.head match { 
    case car: Vehicle[Car] => car.update() 
} 

但有趣的是:

val seq = List[Vehicle[_ <: Vehicle[_]]](new Car, new Bus) 

val vehicleAsVihecles: List[Vehicle[_]]= seq.map(_.update()) // compiled 

val vehicleAsVihecles1: List[Vehicle[_ <: Vehicle[_]]]= seq.map(_.update()) //not compiled 

def somedef(vehicles: List[Vehicle[_ <: Vehicle[_]]]) = vehicles.map(_.update()) //compiled 
somedef(seq)