【问题标题】:Resolving types in F-bounded polymorphism解析 F 有界多态性中的类型
【发布时间】:2017-02-24 10:30:26
【问题描述】:

我有这些模型:

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](或者简单地说,Car Vehicle[Car]),我可以安全地将结果的类型显式设置为Vehicle[Car]

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

但是,如果我想将CarBus 的实例放在一个列表中,那么我必须将列表类型设置为Vehicle[_ &lt;: Vehicle[_]](有一个简单的Vehicle[_] 列表并调用@元素上的 987654333@ 会产生 Any,但我希望能够使用 update() 所以我必须使用 F 有界类型)。使用存在类型会破坏类型关系,因为一旦我从 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

因此,Vehicle 被参数化为某种类型 T,它是 Vehicle[T] 的子类型。当我撕掉T(使用update())时,在具体类型的情况下没关系 - 例如如果我撕掉Car,我可以肯定地说我撕掉了Vehicle[Car],因为Car &lt;: 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]

我知道使用 shapeless HList 可以解决问题,因为我不必使用存在类型(我只需要一个汽车和公共汽车的列表,并且会保留该类型信息)。但我想知道这个特殊的用例有一个简单的List

编辑

@RomKazanova 是的,这当然可以,但我需要在update() 之前和之后保留相同的类型(尽管这是对这项工作的支持;))。

我相信没有 HList 或类似的数据结构是不可能的,因为统一汽车和公共汽车会迫使我们使用车辆类型,这会丢失关于其基础类型是 Car、Bus 还是其他东西的信息(我们所知道的是这是某种类型的_ &lt;: Vehicle)。但我想和你们核实一下。

【问题讨论】:

    标签: scala generics f-bounded-polymorphism


    【解决方案1】:

    我不太擅长存在类型,所以我不能对此解释太多:-p 但是当你将seq 的类型更改为List[Vehicle[T] forSome {type T &lt;: Vehicle[T]}] 时,一切似乎都“解决了”。请注意,您必须将类型传递给 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(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)
    

    我认为摆脱这个答案的主要问题是,这可以让您阐明 Vehicle 类型构造函数的递归性质。

    我不确定我是否会推荐这个...

    【讨论】:

    • 我还以为List[Vehicle[_ &lt;: Vehicle[_]]]List[Vehicle[T] forSome {type T &lt;: Vehicle[T]}] 是同一类型。作为一般的经验法则,我不介意在任何地方使用forSome,但我听说它在 Scala 2.13 或 2.14 中被驱逐了。无论如何,非常感谢,这正是我所希望的解决方案。
    • 我确实从我的 REPL 成绩单中删除了所有存在类型警告。所以我认为这是一种只有通配符无法表达的存在主义。我不确定如果它将被完全删除,但如果是,那么我认为最快会是 Dotty 成为 Scala 3.0 或类似的东西。
    • @slouc 一种可能的直觉来解释为什么会这样:在List[Vehicle[_ &lt;: Vehicle[_]]] 中,第一个_ 和第二个_ 是两种完全不相关的类型,而在List[Vehicle[T] forSome {type T &lt;: Vehicle[T]}] 中你明确提到了相同的类型输入T 两次。
    【解决方案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)
    

    【讨论】:

      猜你喜欢
      • 2016-09-22
      • 1970-01-01
      • 2015-03-09
      • 1970-01-01
      • 2018-05-20
      • 2015-06-03
      • 2015-09-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多