Mirceac21
Mirceac21

Reputation: 1754

How do generic types work with inheritance in scala?

I'm trying to understand how generics work with inheritance in Scala. I have the following code:

sealed trait Model {}
case class Model1() extends Model
case class Model2() extends Model

trait Repo[A <: Model] {
  def doSomething(model: A)
}
class Repo1 extends Repo[Model1] {
  override def doSomething(model: Model1): Unit = {
    println("Model 1")
  }
}
class Repo2 extends Repo[Model2] {
  override def doSomething(model: Model2): Unit = {
    println("Model 2")
  }
}

object Play extends App {
  def getModel(i: Int): Model =
    i match {
      case 1 => Model1()
      case 2 => Model2()
      case _ => throw new RuntimeException("model not found")
    }
  val model = getModel(1)
  val repo = model match {
    case _: Model1 => new Repo1
    case _: Model2 => new Repo2
    case _         => throw new RuntimeException("something went wrong")
  }
  repo.doSomething(model)
}

On the last line repo.doSomething(model) I get Type mismatch. Required: _.$1 Found: Model

According to this answer What is the correct way to implement trait with generics in Scala? if my repos classes extend the trait with the type should work.

I'm new to Scala and I'm trying to wrap my head around the type system, generics, implicit, upper/lower bounds ...

What is _.$1 type and how can I make this work? Thanks!

Upvotes: 0

Views: 221

Answers (1)

Martijn
Martijn

Reputation: 12102

scala is statically typed, and the value model is of compile time type Model and repo of the compile time type Repo

So repo.doSomething is no further refined. The signature of doSomething says it'll take some subtype of Model of a parameter, but we don't know which one -- in other words, the compiler doesn't know that the type of model and the type of repo align.

To make them align, you have a few options.

  1. Because you know that the types align because you constructed it in a way where you know more than the compiler, tell the compiler so
val castRepo = repo.asInstanceOf[Repo[Any]]

That turns off the safeties, and you tell scala "trust me, I know what I'm doing". This is fine to some extent when you know what you're doing, but people who really know what they're doing tend to not trust themselves to know better than the compiler, so a different solution that does retain type safety is probably better.

  1. restructure the program so that things do align.

You could make a wrapper type for example, like so

case class Aligned[A <: Model](model: A, repo: Repo[A]) {
  def doIt = repo.doSomething(model)
}
val aligned = model match {
  case m: Model1 => Aligned(m, new Repo1)
  case m: Model2 => Aligned(m, new Repo2)
  case _         => throw new RuntimeException("something went wrong")
}

aligned.doIt

Within Aligned, scalac knows the Model type and the Repo type line up.

You don't even really need the instance method doIt; aligned.repo.doSomething(aligned.model) also works, because the compiler knows the A in aligned.repo and the A in aligned.model are both the same A in aligned.

Upvotes: 5

Related Questions