当泛型类型对同一泛型类型进行操作时,Scala 类型不匹配

Scala Type mismatch when a generic type operates on the same generic type

我有一个通用案例 class 路线,它接收位置的子 class 列表。但是在下面的方法中,我在 distance expected: head.T, actual: T

的调用中得到类型不匹配
case class Route[T <: Location](route: List[T]) {
  def measureDistance: Double = {
    def measure(head: T, tail: List[T], acc: Double = 0.0): Double = tail match {
      case Nil => acc
      case h :: t => measure(h, t, head.distance(h) + acc)
    }
    if (route.isEmpty) 0.0
    else measure(route.head, route.tail)
  }
}

基本摘要位置class如下

abstract class Location(val name: String) {

type T <: Location

def distance(that: T): Double
}

因为 head 和 h 都来自同一个列表 route 我不明白为什么它们不是同一类型。

在这种情况下,看起来 F-bounded 多态性就是您想要的:

abstract class Location[L <: Location[L]](val name: String) {
  def distance(that: L): Double
}

case class Route[T <: Location[T]](route: List[T]) {
  def measureDistance: Double = {
    def measure(head: T, tail: List[T], acc: Double = 0.0): Double = tail match {
      case Nil => acc
      case h :: t => measure(h, t, head.distance(h) + acc)
    }
    if (route.isEmpty) 0.0
    else measure(route.head, route.tail)
  }
}

但是,您也可以考虑使用 Metric 类型类:

trait Metric[L] {
  def dist(a: L, b: L): Double
}

case class Route[T: Metric](route: List[T]) {
  def measureDistance: Double = {
    def measure(head: T, tail: List[T], acc: Double = 0.0): Double = tail match {
      case Nil => acc
      case h :: t => measure(h, t, implicitly[Metric[T]].dist(head, h) + acc)
    }
    if (route.isEmpty) 0.0
    else measure(route.head, route.tail)
  }
}

后一种解决方案将适用于更多类型,例如 (Double, Double),即使它们不继承自 Location

这里又是类型类解决方案,但使用了稍微更精致的 Cats 风格语法,避免了 implicitly:

trait Metric[L] {
  def dist(a: L, b: L): Double
}

object Metric {
  def apply[T](implicit m: Metric[T]): Metric[T] = m
}

case class Route[T: Metric](route: List[T]) {
  def measureDistance: Double = {
    def measure(head: T, tail: List[T], acc: Double = 0.0): Double = tail match {
      case Nil => acc
      case h :: t => measure(h, t, Metric[T].dist(head, h) + acc)
    }
    if (route.isEmpty) 0.0
    else measure(route.head, route.tail)
  }
}

您不需要在 Location 摘要 class 中定义类型 T。您应该按以下步骤进行:

abstract class Location[T <: Location[T]](val name: String) {
  def distance(that: T): Double
}

case class Route[T <: Location[T]](route: List[T]) {
  def measureDistance: Double = {
    def measure(head: T, tail: List[T], acc: Double = 0.0): Double = tail match {
      case Nil => acc
      case h :: t => measure(h, t, head.distance(h) + acc)
    }
    if (route.isEmpty) 0.0
    else measure(route.head, route.tail)
  }
}

scala 编译器无法知道 class Location 中定义的 type T <: Location 与 Route 的类型参数 [T <: Location] 是同一类型。

我认为您将不得不更改 def distance(...) 的签名。我不确定,但如果您将 T 定义为 Location:

的类型参数,它应该可以工作
abstract class Location[T <: Location[T]](val name: String) {
  def distance[T](that: T): Double
}