0

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!

Levi Ramsey
  • 18,884
  • 1
  • 16
  • 30
Mirceac21
  • 1,741
  • 17
  • 24
  • Note that you can make everything work by throwing out the code in `Play` which references `Model2` or `Repo2`, which is perhaps a sign that the example is over-minimized. – Levi Ramsey Aug 17 '20 at 15:46
  • 1
    You are right, I simplified things. Some of those classes live in different packages. Thanks for the note! – Mirceac21 Aug 17 '20 at 16:06

1 Answers1

5

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.

Martijn
  • 11,964
  • 12
  • 50
  • 96
  • I was suspecting that the compiler was confused but I was hoping for a way/pattern to make it work. I think your second solution is quite elegant and I'm going to use it. Thanks for your answer! – Mirceac21 Aug 17 '20 at 16:10
  • 2
    @Mirceac21 [here](https://scastie.scala-lang.org/BalmungSan/0JgoPzOeSeq43UGTpsxZOQ/5) is a somewhat more idiomatic reimplementation of your code following the suggestion made by Martijn. – Luis Miguel Mejía Suárez Aug 17 '20 at 17:07
  • Thanks, @LuisMiguelMejíaSuárez for the example. My code is simplified to highlight the problem and make the question generic. Your example is a good reference for anyone who has a similar issue. – Mirceac21 Aug 17 '20 at 19:03