问题
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