2017-02-24 17 views
5

mam te modele:Rozwiązywanie typy w F-ograniczonego polimorfizmu

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() } 

Gdybym uzyskać instancję Vehicle[Car] i powołać update() będę dostać Car. Od Car rozciąga Vehicle[Car] (lub po prostu, samochodów jest pojazdu [samochodu]), mogę bezpiecznie ustawić typ wyniku wyraźnie do Vehicle[Car]:

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

Ale jeśli chcę, powiedzmy, umieścić instancje z Car i Bus razem w jednej listy, to muszę ustawić typ listy do Vehicle[_ <: Vehicle[_]] (o listę po prostu Vehicle[_] i powołując update() na elemencie przyniesie Any, ale chcę, aby móc korzystać update() więc muszę używać typ F-bounded). Korzystanie rodzajów egzystencjalne śruby up relacje typu, bo kiedyś pobrać leżących u podstaw samochód/autobus z pojazdu, nie mogę już oddanych do pojazdu, ponieważ ... dobrze, to po prostu jakiś rodzaj egzystencjalnej:

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

Tak , Vehicle jest sparametryzowany z pewnym typem T, który jest podtypem Vehicle[T]. Kiedy wyrywam T (przy użyciu update()), w przypadku typów betonu jest ok - np. jeśli wyrwę Car, mogę bezpiecznie twierdzić, że wyrzuciłem Vehicle[Car] ponieważ Car <: Vehicle[Car]. Ale jeśli wyrywam typ egzystencjalny, nie mogę z tym nic zrobić. Wcześniejszy przykład zadziałał, ponieważ Car to Vehicle[Car], ale w tym przypadku _ nie jest Vehicle[_].

Aby określić moje konkretne pytanie: w przypadku modeli wymienionych powyżej (pojazd, samochód, autobus), czy istnieje sposób, aby to osiągnąć?

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 

Pamiętaj, że możesz zmienić danych cech, klas i rodzaj seq, ale istnieje jedno ograniczenie: update()musi powrócić T, nie Vehicle[T].

Wiem, że używanie bezkształtnego HList rozwiązałoby problem, ponieważ nie musiałbym używać typów egzystencjalnych (po prostu miałbym listę samochodów i autobusów, a informacje o tym byłyby zachowane). Ale zastanawiam się nad tym konkretnym przypadkiem użycia z prostym List.

EDIT:

@RomKazanova tak, że będzie działać oczywiście, ale trzeba zachować ten sam typ przed i po update() (oto upvote za wysiłek chociaż;)).

Uważam, że nie jest to możliwe bez HList lub podobnej struktury danych, ponieważ ujednolicenie samochodów i autobusów zmusza nas do użycia typu pojazdu, który traci informacje o tym, czy jego podstawowym typem był samochód, autobus lub coś innego (wszystko, co możemy wiedzieć, to że był to jakiś typ _ <: Vehicle). Ale chcę się z wami skontaktować.

Odpowiedz

4

nie jestem bardzo dobry z typów egzystencjalnych , więc nie mogę wyjaśnić tego zbyt wiele :-p Ale kiedy zmienisz typ seq na List[Vehicle[T] forSome {type T <: Vehicle[T]}] wszystko wydaje się "pracować". Pamiętaj, że musisz przekazać typ do metody konstruktora/zastosowania List.

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]) 

myślę, że najważniejsze, aby wydostać się z tej odpowiedzi jest to, że pozwala okre rekurencyjną naturę konstruktora Vehicle typu.

Nie jestem pewien, polecam ten chociaż ...

+0

myślałem, że 'Lista [pojeździe [_ <: Pojazd [_]]]' jest tego samego typu co 'listy [pojazdu [ T] forSome {type T <: Vehicle [T]}] '. Jako ogólna zasada, nie miałbym nic przeciwko używaniu 'forSome' wszędzie, ale słyszałem, że jest eksmitowany w Scali 2.13 lub 2.14. W każdym razie, dziękuję bardzo, jest to dokładnie takie rozwiązanie, na które miałem nadzieję. – slouc

+0

Usunąłem wszystkie ostrzeżenia egzystencjalne z mojego transkrypcji REPL. Myślę więc, że jest to rodzaj egzystencjalny, który jest niewyrażalny tylko za pomocą symboli wieloznacznych. Nie jestem pewien * jeśli * zostanie całkowicie usunięty, ale jeśli tak, to * wydaje mi się, że najwcześniej będzie, gdy Dotty zmieni się w Scala 3.0 lub coś w tym stylu. –

+0

BTW Przypadkowo Cię popuściłem :) naprawiono – slouc

2

Istnieją dwa sposoby, aby go rozwiązać:

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

lub wykorzystanie wzorzec dopasowania

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

ale ciekawe, że:

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)