jackt
jackt

Reputation: 55

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

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.

Upvotes: 1

Views: 617

Answers (3)

Andrey Tyukin
Andrey Tyukin

Reputation: 44918

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

Upvotes: 3

Sascha Kolberg
Sascha Kolberg

Reputation: 7162

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
}

Upvotes: 0

meucaa
meucaa

Reputation: 1515

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

Upvotes: 0

Related Questions