简体   繁体   English

解决F-有界多态的类型

[英]Resolving types in F-bounded 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 obtain an instance of a Vehicle[Car] and invoke update() , I will get a Car . 如果我获得Vehicle[Car]的实例并调用update() ,我将获得一辆Car Since Car extends Vehicle[Car] (or simply put, Car is a Vehicle[Car]), I can safely set the type of the result explicitly to Vehicle[Car] : 由于Car扩展了Vehicle[Car] (或简单地说,Car Vehicle [Car]),我可以安全地将结果的类型设置为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 instances of Car and Bus together into one list, then I have to set the list type to Vehicle[_ <: Vehicle[_]] (having a list of simply Vehicle[_] and invoking update() on an element would yield Any , but I want to be able to use update() so I have to use the F-bounded type). 但是,如果我想将CarBus实例放在一个列表中,那么我必须将列表类型设置为Vehicle[_ <: Vehicle[_]] (具有简单的Vehicle[_]列表并调用对元素的update()会产生Any ,但我希望能够使用update()所以我必须使用F-bounded类型)。 Using existential types screws up the type relationships, because once I fetch the underlying car/bus from the Vehicle, I can no longer cast it to Vehicle because... well, it's just some existential type: 使用存在类型搞定了类型关系,因为一旦我从Vehicle获取底层汽车/公共汽车,我就不能再将其投射到Vehicle,因为......好吧,它只是一些存在类型:

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 is parameterized with some type T which is a subtype of Vehicle[T] . 因此, Vehicle用一些类型T参数化,这是Vehicle[T]的子类型。 When I rip out the T (by using update() ), in case of concrete types it's ok - eg if I rip out the Car , I can safely claim that I ripped out a Vehicle[Car] because Car <: Vehicle[Car] . 当我撕掉T (通过使用update() )时,如果是具体的类型就可以 - 例如,如果我撕掉Car ,我可以安全地声称我撕掉了Vehicle[Car]因为Car <: Vehicle[Car] But if I rip out an existential type, I can't do anything with it. 但如果我撕掉一个存在主义类型,我就无法做任何事情。 Earlier example worked because Car is a Vehicle[Car] , but in this case _ is not a Vehicle[_] . 之前的例子有效,因为Car是一辆Vehicle[Car] ,但在这种情况下_不是Vehicle[_]

To specify my concrete question : for models given above (Vehicle, Car, Bus), is there a 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 given traits, classes and type of seq , but there's one restriction: update() must return T , not Vehicle[T] . 请注意,您可以更改给定的特征,类和seq类型,但有一个限制: update() 必须返回T ,而不是Vehicle[T]

I know that using shapeless HList would solve the problem as I wouldn't have to use existential types (I would simply have a list of a car and a bus, and that type information would be preserved). 我知道使用无形HList可以解决问题,因为我不必使用存在类型(我只需要一个汽车和一个总线的列表,并且将保留该类型信息)。 But I'm wondering for this particular use case with a simple List . 但我想知道这个特殊的用例有一个简单的List

EDIT : 编辑

@RomKazanova yes, that would work of course, but I need to retain the same type before and after update() (here's an upvote for the effort though ;)). @RomKazanova是的,这当然会工作,但我需要在update()之前和之后保留相同的类型update()虽然这是努力的一个upvote;))。

I believe that it's not possible without HList or similar data structure, because unifying cars and buses forces us to use the vehicle type which loses info about whether its underlying type was Car, Bus or something else (all we can know is that it was some type _ <: Vehicle ). 我相信没有HList或类似的数据结构是不可能的,因为统一汽车和公共汽车迫使我们使用车辆类型,它失去了关于其底层类型是汽车,公共汽车还是其他东西的信息(我们只能知道它是一些type _ <: Vehicle )。 But I want to check with you guys. 但我想和你们一起检查一下。

I'm not very good with existential types, so I can't explain too much about this :-p But when you change the type of seq to List[Vehicle[T] forSome {type T <: Vehicle[T]}] everything seems to "work out". 我对存在类型不是很好,所以我不能解释太多:-p但是当你将seq的类型更改为List[Vehicle[T] forSome {type T <: Vehicle[T]}]一切似乎都“锻炼”了。 Mind you have to pass the type to the List constructor/apply method. 请注意,您必须将类型传递给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(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 this lets you spell out the recursive nature of the Vehicle type constructor. 我认为摆脱这个答案的主要原因是,这可以让你拼出Vehicle类型构造函数的递归性质。

I'm not sure I would recommend this though... 我不确定我会推荐这个......

There are two ways to resolve it: 有两种方法可以解决它:

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

or use pattern matching 或使用模式匹配

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM