Deconstructing the Scala Map Literal

November 12, 2009

Join my mailing list…

I find that Scala is one giant Rube Goldberg Machine that manages to do something not easily be done otherwise. By this I mean that Scala has many features that, by themselves, seem very strange, but, in combination, enable some very cool functionality. This is why I initially started my personal tour of Scala. I read stuff like explicitly typed self-references and was left scratching my head.

I thought it might be fun to deconstruct the "map literal" in Scala and observie how the features interact to create a very handy piece of code that isn't baked into the language. This assumes and understanding of some Scala basics.

Although Java 7 is getting map literals, Scala already has it (or so it appears):

val band = Map("Dave" -> "Bass",
                "Tony" -> "Guitar",
                "Greg" -> "Drums")
This is not actually a literal, but enabled by Scala features to make it look like a literal. Which means that you can use these facilities to make your own literals. So, how does this work?

Most surprising to a Java programmer is the -> operator. This makes use of two Scala features:

It turns out that the -> operator is on the class Predef.ArrowAssoc. Predef is automatically imported in every Scala program, so you don't need to prefix anything with Predef. It returns a tuple of its caller and its argument, e.g.

val dave = new ArrowAssoc("Dave")
val entry = dave -> "Bass"
// entry is now ("Dave","Bass")
// which is a Tuple2[String,String]

Of course, we aren't creating ArrowAssoc instances anywhere, so how does this get called? This is where implicits come in. Suppose we change our simple example to:

val dave = "Dave"
val entry = dave -> "Bass"
// entry is still ("Dave","Bass")
// which is a Tuple2[String,String]
Here, Scala sees that the method -> needs to be called on an ArrowAssoc, but is being called on a String. Instead of giving up, Scala notices the method:
implicit def any2ArrowAssoc[A](x: A): 
  ArrowAssoc[A] = new ArrowAssoc(x)
This means that anything at all can be converted into an ArrowAssoc if there's some reason to. And we have a reason to here.

This means our code is now effectively:

val band = Map(("Dave" , "Bass"),
                ("Tony" , "Guitar"),
                ("Greg" , "Drums"))
It's not hard to imagine a Map constructor taking Tuple2, using the first part as the key and the second part as the value, however where is the constructor? Scala creates objects via the new keyword, just as Java does. So, what's going on here?

This use two additional Scala features:

  1. apply() shortcutting
  2. Scala singleton objects
This is much simpler to decode than the -> method; there is simply an object in scope named Map, and it has an apply method that takes a variable list of Tuple2 objects. Scala interprets a method-call syntax on an object, but lacking a method name, as a call to the apply method of that object (if it exists). So, removing this, we have:
val band = Map.apply(("Dave" , "Bass"),
                      ("Tony" , "Guitar"),
                      ("Greg" , "Drums"))

That's all there is to it! A few things to note about this:

  • Without the application of some Scala features, it's pretty ugly
  • The language itself didn't need to implement a special "map literal"; it simply combines smaller features in a way to make it appear as though it does. You can even create your own "literals" rather than waiting for the language to implement them