Несоответствие типов Scala, когда универсальный тип работает с тем же универсальным типом

У меня есть универсальный класс case Route, который принимает список подклассов Location. Однако в следующем методе я получаю несоответствие типов при вызове distanceexpected: 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)
  }
}

Базовый абстрактный класс Location выглядит следующим образом

abstract class Location(val name: String) {

type T <: Location

def distance(that: T): Double
}

Как голова, так и h оба происходят из одного списка route Я не могу понять, почему это не тот же тип.

3 ответа

Решение

Похоже, что в этом случае вам нужен F-ограниченный полиморфизм:

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

Вам не нужно определять тип T внутри вашего Location абстрактный класс. Вы должны действовать следующим образом:

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 не может знать, что type T <: Location определено в классе Location тот же тип, что и параметр типа [T <: Location] маршрута.

Я думаю, вам придется сменить подпись def distance(...), Я не уверен, но это должно работать, если вы определяете T как параметр типа Location:

abstract class Location[T <: Location[T]](val name: String) {
  def distance[T](that: T): Double
}
Другие вопросы по тегам