Reputation: 1754
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
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.
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.
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