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

主宰稳场 提交于 2020-01-15 10:55:09

问题


I have an generic case class Route that takes in a List of subclasses of Location. However in the following method I get a type mismatch in the call to 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)
  }
}

The basic abstract Location class is as follows

abstract class Location(val name: String) {

type T <: Location

def distance(that: T): Double
}

As head and h both come from the same list route I can't understand why these are not the same type.


回答1:


It looks as if F-bounded polymorphism is what you want in this case:

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

However, you might also consider using a Metric-typeclass instead:

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

The latter solution would be applicable to more types, for example to (Double, Double), even if they don't inherit from Location.

Here is the typeclass solution again, but with slightly more polished Cats-style syntax that avoids 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)
  }
}



回答2:


There is not need for you to define a type T inside your Location abstract class. You should proceed as follows:

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



回答3:


There is no way for the scala compiler to know that type T <: Location defined in class Location is the same type as the type parameter [T <: Location] of Route.

I think you will have to chage the signature of def distance(...). I am not sure, but it should work if you define T as type parameter of Location:

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


来源:https://stackoverflow.com/questions/50757780/scala-type-mismatch-when-a-generic-type-operates-on-the-same-generic-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!