Deconstructing the Scala Map Literal
November 12, 2009
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")
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]
implicit def any2ArrowAssoc[A](x: A):
ArrowAssoc[A] = new ArrowAssoc(x)
This means our code is now effectively:
val band = Map(("Dave" , "Bass"),
("Tony" , "Guitar"),
("Greg" , "Drums"))
This use two additional Scala features:
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