More on Scala arrays and type inference

This is not just a post about arrays, but also tuples and other Scala structures. I’ve already written about Scala type inference. Well, let’s see what happens when one of the element of an Array[Int] is changed into a Double:

scala> val mix = Array(1, 2, 3)
mix: Array[Int] = Array(1, 2, 3)

scala> val mix = Array(1, 2, 3.3)
mix: Array[Double] = Array(1.0, 2.0, 3.3)

The first Array has type Int, while the second has type Double. This is quite understandable: Double is the best type to represent both integers and floating point numbers. Now, what happens when a string is added to the array?

scala> val mix = Array(1, 2, 3.3, "quattro")
mix: Array[Any] = Array(1, 2, 3.3, quattro)

If you don’t know it, “quattro” is the italian word for four. Now the array type has become Any. This is the ancestor of any type in Scala. The REPL had to climb the class hierarchy up to its source to find a common ancestor of two integers, one double and a string. This behaviour is common to many situations in Scala. But let’s see what happens with tuples:

scala> val mixTuple = 1 -> 2.0
mixTuple: (Int, Double) = (1,2.0)

A tuple groups a set of objects of arbitrary types. It’s not required that all the objects have the same type. Now, let’s do some experiment on maps:

scala> val mixMap = Map(1 -> 2.0, 3 -> 4.0)
mixMap: scala.collection.immutable.Map[Int,Double] = Map(1 -> 2.0, 3 -> 4.0)

I’ve created an immutable map (its values can’t be modified, but a new map some values changed can be derived from this one) which type is [Int, Double]. Now just suppose I would like to save a string as one of the values:

scala> val mixMap = Map(1 -> 2.0, 4 -> "cinque")
mixMap: scala.collection.immutable.Map[Int,Any] = Map(1 -> 2.0, 4 -> cinque)

The map type has changed to [Int, Any], as already seen with the third array created in this post. But! What happens if I change a key?

scala> val mixMap = Map(1 -> 2.0, "cinque" -> 4)
mixMap: scala.collection.immutable.Map[Any,AnyVal{def getClass(): 
  java.lang.Class[_ >: Int with Double <: AnyVal]}] = Map(1 -> 2.0, cinque -> 4)

This is really unexpected! [Any,AnyVal{def getClass(): java.lang.Class[_ >: Int with Double <: AnyVal]}] ? The new type is a real mess! The type of the key is Any, quite predictable. But what about the value type? I’ll try to decode it.

AnyVal is the ancestor of all Scala types like Int and Double:

Scala class hierarchy

If I don’t get this wrong the definition means: any value (AnyVal) which implements a method getClass() which returns a Java Class (so anything) with an ancestor of type Int with trait Double and which is a descendant of AnyVal. Sometimes type inference can lead to really complex results. Is it possible to simplify this declaration by explicitly defining map types?

scala> val mixMap = Map[Any,AnyVal](1 -> 2.0, "cinque" -> 4)
mixMap: scala.collection.immutable.Map[Any,AnyVal] = Map(1 -> 2.0, cinque -> 4)

Yes it is! Type inference is a very handy feature, but its best may not correspond to our idea of ideal type. Sometimes adding a keystroke or two is worth the effort.