2017-02-24 3 views
5

I ont ces modèles:types résolutives en polymorphisme borné 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() } 

Si j'obtiens une instance d'un Vehicle[Car] et invoque update(), je vais obtenir un Car. Depuis Car étend Vehicle[Car] (ou simplement, voiture est un véhicule [Car]), je peux en toute sécurité définir le type du résultat explicitement Vehicle[Car]:

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

Mais si je veux, par exemple, mettre des instances de Car et Bus en une seule liste, je dois définir le type de liste à Vehicle[_ <: Vehicle[_]] (ayant une liste de simplement Vehicle[_] et invoquant update() sur un élément produirait Any, mais je veux être en mesure d'utiliser update() donc je dois utiliser le type F-borné). L'utilisation de vis types existentiels les des relations de type, car une fois que je vais chercher la voiture sous-jacente/bus du véhicule, je ne peux plus lancer à des véhicules parce que ... eh bien, il est juste un certain type existentiel:

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

Alors , Vehicle est paramétré avec un certain type T qui est un sous-type de Vehicle[T]. Lorsque je déchire le T (en utilisant update()), dans le cas de types de béton c'est ok - p. si je déchire le Car, je peux affirmer sans risque que j'ai arraché un Vehicle[Car] parce que Car <: Vehicle[Car]. Mais si je déchire un type existentiel, je ne peux rien faire avec. L'exemple précédent a fonctionné parce que Car est un Vehicle[Car], mais dans ce cas _ n'est pas un Vehicle[_].

Pour spécifier ma question concrète: pour les modèles donnés ci-dessus (Véhicule, Voiture, Bus), y at-il un moyen d'y parvenir?

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 

Notez que vous pouvez changer les traits donnés, des classes et le type de seq, mais il y a une restriction: update()doit retourner T, pas Vehicle[T]. Je sais que l'utilisation de HList sans forme permettrait de résoudre le problème car je n'aurais pas besoin d'utiliser des types existentiels (j'aurais simplement une liste de voiture et de bus, et cette information de type serait préservée). Mais je me demande pour ce cas d'utilisation particulier avec un simple List.

EDIT:

@RomKazanova oui, ça marcherait bien sûr, mais je dois conserver le même type avant et après update() (ici un upvote est pour l'effort que;)). Je crois que ce n'est pas possible sans HList ou une structure de données similaire, car l'unification des voitures et des bus nous oblige à utiliser le type de véhicule qui perd de l'information si son type sous-jacent était Car, Bus ou autre (tout ce que nous pouvons savoir qu'il s'agissait d'un type _ <: Vehicle). Mais je veux vérifier avec vous les gars.

Répondre

4

Je ne suis pas très bon avec des types existentiels , donc je ne peux pas expliquer trop à ce sujet :-p Mais quand vous changez le type de seq à List[Vehicle[T] forSome {type T <: Vehicle[T]}] tout semble "s'entraîner". Rappelez-vous que vous devez passer le type à la méthode constructeur/apply 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]) 

Je pense que la principale chose à sortir de cette réponse est que cela vous permet de préciser le contenu récursif du constructeur de type Vehicle.

Je ne suis pas sûr que je recommanderais ce bien ...

+0

j'aurais pensé que 'Liste [des véhicules [_ <: véhicule [_]]]' est du même type que 'Liste [des véhicules [ T] forSome {type T <: Véhicule [T]}] '. En règle générale, cela ne me dérangerait pas d'utiliser 'forSome' partout, mais j'ai entendu dire qu'il est expulsé dans Scala 2.13 ou 2.14. Quoi qu'il en soit, merci beaucoup, c'est exactement le genre de solution que j'espérais. – slouc

+0

J'ai élidé tous les avertissements de type existentiel de ma transcription REPL. Donc je pense que c'est le genre d'existentiel qui est inexprimable avec seulement des jokers. Je ne suis pas sûr * si * il sera complètement supprimé, mais si oui alors je * pense * le plus tôt serait quand Dotty devient Scala 3.0 ou quelque chose comme ça. –

+0

BTW J'ai accidentellement downvoted vous :) fixe – slouc

2

Il y a deux façons de le résoudre:

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

ou mode d'utilisation correspondant à

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

Mais intéressant:

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)