Andrey Kuznetsov
Andrey Kuznetsov

Reputation: 11830

Scala generics subtypes

I am lost in scala generics.

I need a method storeUnit which accepts Unit's subclasses's instances (e.q. Visit) and returns StoredUnit's subclasses's instances (e.q. StoredVisit) but I am getting compilation errors.

trait StatsUnit { val ip: String } 
case class Visit(ip: String) extends StatsUnit
case class Click(ip: String) extends StatsUnit

trait StoredStatsUnit extends StatsUnit { val id: String }
case class StoredVisit(id: String, ip: String) extends StoredStatsUnit
case class StoredClick(id: String, ip: String) extends StoredStatsUnit

def storeUnit[A <: StatsUnit, B <: StoredStatsUnit](statsUnit: A): B = {
  statsUnit match {
    case x: Visit => StoredVisit("myid", x.ip)
    case x: Click => StoredClick("myid", x.ip)
  }
}

/tmp/1.scala:11: error: type mismatch;
 found   : this.StoredVisit
 required: B
    case x: Visit => StoredVisit("myid", x.ip)
                                    ^
/tmp/1.scala:12: error: type mismatch;
 found   : this.StoredClick
 required: B
    case x: Click => StoredClick("myid", x.ip)

Upvotes: 3

Views: 8662

Answers (3)

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297275

Let's lie to the compiler to make the code compile, and then I'll show what the compiler is complaining about. First:

scala> def storeUnit[A <: StatsUnit, B <: StoredStatsUnit](unit: A): B = {
     |   StoredVisit("myid", unit.ip).asInstanceOf[B]
     | }
storeUnit: [A <: StatsUnit, B <: StoredStatsUnit](unit: A)B

Now let's create another subclass of StoredStatsUnit:

case class UnStoredVisit(id: String, ip: String, n: Int) extends StoredStatsUnit

And now let's show why the compiler was complaining about that method definition:

scala> val visit: UnStoredVisit = storeUnit(Visit("1.2.3.4"))
java.lang.ClassCastException: StoredVisit cannot be cast to UnStoredVisit

In other words, you are not returning a parameterized B which is an arbitrary subclass of StoredStatsUnit. You are returning a StoredVisit, which is one specific subclass of it.

Upvotes: 3

Impredicative
Impredicative

Reputation: 5069

A comment first:

  1. Don't name your trait Unit! Unit has specific meaning in Scala - it's equivalent to Java's void - and shadowing that definition is only going to cause troubles!

However, the problem here is that you specify that your method will return an instance of B and then you try to return something of type StoredVisit. You don't need B at all in this example, so the following will work fine:

def storeUnit[A <: StatsUnit](unit: A): StoredStatsUnit = {
  StoredVisit("myid", unit.ip)
}

Upvotes: 3

Randall Schulz
Randall Schulz

Reputation: 26486

B is a subtype of StoredUnit and StoredVisit is a subtype of StoredUnit but there is no valid inference that StoredVisit is compatible with B.

Upvotes: 1

Related Questions