scalajvmscala-collectionstype-erasure

Using collect on maps in Scala


I recently stumbled over this post, which "introduces" the collect method for Scala collections. The usage is straight forward:

scala> val ints = List(1, "2", 3) collect { case i: Int => i }
ints: List[Int] = List(1, 3)

Now maps are basically lists of key-value pairs, which are represented by tuples in Scala. So you might wanna try something like this:

scala> val pairs = Map(1 -> "I", "II" -> 2)
pairs: scala.collection.immutable.Map[Any,Any] = Map(1 -> I, II -> 2)

scala> val intsToStrings = pairs collect { case pair: (Int, String) => pair }

The compiler complains of course due to the type erasure model of the JVM, so the first thing we try is using existential types:

scala> val intsToStrings = pairs collect { case pair: (_, _) => pair }
intsToString: scala.collection.immutable.Map[Any,Any] = Map(1 -> I, II -> 2)

Although the code passed the compiler, and the result is "correct" (we wanted pairs => we got pairs) we still didn't get what we actually wanted. The second attempt looks like this:

scala> val intsToStrings = pairs collect {
     |    case pair: (_, _) if pair._1.isInstanceOf[Int] && pair._2.isInstanceOf[String] => pair
     | }
intsToStrings: scala.collection.immutable.Map[Any,Any] = Map(1 -> I)

Ok, we are almost there:

scala> val realIntsToRealStrings = intsToStrings map {
     |    pair => (pair._1.asInstanceOf[Int], pair._2.asInstanceOf[String])
     | }
realIntsToRealStrings: scala.collection.immutable.Map[Int,String] = Map(1 -> I)

We did it, but instead of only casting from (Any,Any) to (Int,String) we actually copied each pair and thus created a new pair.

Now comes the question part. As I mentioned "The compiler complains of course..." I made it sound like I really know what I'm talking about. I don't! All I know is that Java didn't have generics from the beginning. At some point generics came into Java but not into the JVM. So the compiler checks all the types, but as soon as the code is running, JVM does not care for the parametric type. It only sees that it's a Map or a List but not that it's a Map[String, Int] or List[Int].

So here is my question.

With all the checking, casting and mapping, we managed to transfer a Map[Any,Any] to Map[String,Int]. Is there a better way of doing that? I mean the types are there, JVM just does not see them (as far as I am concerned)...


Solution

  • pairs collect { case p @ (_: Int, _: String) => p.asInstanceOf[(Int, String)] }
    

    or more concise, but with some overhead, I think

    pairs collect { case (x: Int, y: String) => (x, y) }