Something weird with realisation of the type of individual elements in a tuple (or map)

I am using Scala 2.12.13. There is a method with the following signature and body

def foo(m: Map[String, Double]) = {
  m foreach println //works fine
  println(m.head)  //works fine
  println(m.head._2) // crashes with java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Double (java.lang.String and java.lang.Double are in module java.base of loader 'bootstrap')
}

val m: Map[String, Any] = Map("1" -> "zoo")
val m1: Map[String, Double] = m.asInstanceOf[Map[String, Double]] // shouldn't it have crashed here?
foo(m1)

foo is being called with the wrong type here (called with Map[String ,String] instead of Map[String, Double]).

  1. Shouldn’t there be an error at the place where foo is being called from?
  2. Why does foreach println and println(m.head) work? Aren’t the types of the individual elements realised in a tuple eagerly?
  1. No. Because of type erasure, at runtime the type parameters cannot be checked

  2. Because the (String, String) has a method toString that works.

3 Likes

asInstanceOf promises the compiler you know what you’re doing. It should never appear in normal code. When you do use it, the responsibility’s on you not to make a mistake.

In some cases, using it wrongly will cause an immediate error at runtime:

scala 2.13.5> "zoo".asInstanceOf[Double]
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Double
  at scala.runtime.BoxesRunTime.unboxToDouble(BoxesRunTime.java:112)
  ... 32 elided

But as @gregor-rayman has indicated, that doesn’t happen in your Map example because of type erasure. The type parameters are erased and thus at runtime every Map is simply a Map and that’s it.

The same is true in Java; this isn’t just a Scala thing. The key phrase if you want to do more reading on this is “type erasure”.

3 Likes

Expanding on this a little, because it’s a fairly common mistake: folks who are new to Scala sometimes assume that asInstanceOf means “convert type A to type B” – that it is changing the value.

It totally does not do that, though. Instead, it is saying to the compiler, “Ignore what you think is going on, and believe me when I tell you that this value of type A already is a value of type B”. You’re saying to the compiler that it is mistaken, and you know what’s going on better than it does.

There are times when you have to do this, usually because the program was already badly-typed and you are working around it. But you’re basically lying to the compiler, so it is producing broken code – that’s why it crashes at runtime. You are saying “This Map[String, Any] here? It is really a Map[String, Double] – just trust me”. But that’s not true – it’s actually a Map[String, String].

As Seth says, this really is something you should never do unless you’re absolutely forced into it; unless you really know what you are doing, and are very careful, odds are pretty good that you’re going to be wrong. Most of the time, the compiler is paying better attention to the details than you are.

2 Likes