Permissive types in F-restricted polymorphism

I have these models:

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

If I get an instance of a Vehicle[Car] and call update() , I get a Car . Since Car extends Vehicle[Car] (or is simply put, Car is Vehicle [Car]), I can safely set the result type explicitly to Vehicle[Car] :

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

But if I want to, say, put Car and Bus instances together in one list, then I need to set the list type to Vehicle[_ <: Vehicle[_]] (having the list just Vehicle[_] and calling update() on the element will give Any but I want to be able to use update() , so I need to use an F-restricted type). Using existential types spins type relationships because, as soon as I pick up the base car / bus from the vehicle, I can no longer drop it onto the Car, because ... well, it's just some kind of existential type:

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

So, Vehicle parameterized by some type of T , which is a subtype of Vehicle[T] . When I pull out T (using update() ), in the case of specific types, this is normal - for example. if I rip out of Car , I can safely say that I pulled out a Vehicle[Car] , because Car <: Vehicle[Car] . But if I destroy the existential type, there is nothing I can do about it. The previous example worked because Car is Vehicle[Car] , but in this case _ not Vehicle[_] .

To point out my specific question : for the models above (Vehicle, Car, Bus), is there any way to achieve this?

 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 

Note that you can change the specified traits, classes, and type of seq , but there is one limitation: update() must return T , not Vehicle[T] .

I know that using a shapeless HList would solve the problem, since I would not have to use existential types (I would just have a list of cars and buses, and this type information would be saved). But I'm interested in this particular use case for a simple List .

EDIT :

@RomKazanova yes, this will work, of course, but I need to keep the same type before and after update() (here is an excerpt for efforts, though;)).

I believe that this is not possible without an HList or similar data structure, because combining cars and buses forces us to use a vehicle type that loses information about whether its base type is Car, Bus or something else (all that we can know, it was some type of _ <: Vehicle ). But I want to check with you guys.

+5
source share
2 answers

I'm not very good at existential types, so I can't explain too much about this: -p But when you change the seq type to List[Vehicle[T] forSome {type T <: Vehicle[T]}] , everything seems , "works". Keep in mind that you must pass the type to the List / apply constructor method.

 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( Car@31e53802 , Bus@54d569e7 ) scala> sameType(seq.head.update +: seq.tail, seq) res3: Boolean = true scala> seq.head.update res4: T forSome { type T <: Vehicle[T] } = Car@79875bd2 scala> seq.head.update.update res5: T forSome { type T <: Vehicle[T] } = Car@6928c6a0 scala> new Car +: seq res6: List[Vehicle[T] forSome { type T <: Vehicle[T] }] = List( Car@51f0a09b , Car@31e53802 , Bus@54d569e7 ) 

I think the main thing to get out of this answer is that it allows you to describe the recursive nature of a constructor like Vehicle .

I'm not sure I recommend this though ...

+4
source

There are two ways to solve this problem:

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

or use the matching pattern

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

But it is interesting that:

 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) 
+2
source

Source: https://habr.com/ru/post/1264674/


All Articles