Wednesday, September 22, 2010

My first experience with Scala collections

In my previous post, I explained some concepts for improving my city builder simulations. By now, all code has been implemented, reimplemented, and reimplemented again. This has resulted in a codebase which, in my humble opinion, resembles real Scala. As a bonus, it also does what I wanted it to do.

I will talk about collections, traits, design patterns, and having fun with Scala. This post is dedicated to Scala collections; the traits and design patterns will be discussed in the next post.

Getting to know Collections

The Scala 2.8 Collection API is a powerful beast, with dangerous fangs and it took me a while to really appreciate it. So let's start with a warning to all other Scala newbies: do not try to learn the Collection API by reading the Scaladoc. I did, and I ended up really confused. The right document to get to know collections is the Scala 2.8 Collections API.

One of the first things that bit me, was the annoying habit of thinking in Java too much. In Java, all collections are mutable. Some are unmodifiable, but that turns out to be something completely different. Scala is a functional programming languages, and as such favors immutable collections over mutable collections. Which means that a lot of your code will create a new collection. This is not very Java-like, and it took me a while to appreciate this.

Getting to use Collections

For example, let's have a look at the following interface I wrote for the controller:

class Controller {

  private var actorStates = Map[ScalasimActor, State]()

  def addActor(a: ScalasimActor): ScalasimActor = {
    actorStates = actorStates + (a -> Idle)
    a
  }

  def actors: Set[ScalasimActor] = actorStates.keySet.toSet

}

Two observations. First of all, notice how actorStates is an immutable Map. So, when I need to add an actor, I create a new map by adding actorStates and a key/value pair. Performance considerations might dictate the use of a mutable map in the future. But for now, I have the impression this is how it should be done in Scala.

The other observation is the line actorStates.keySet.toSet. That's right, actorStates.keySet gives me a set, which I then convert to a set. Why?

It all starts with the fact that I want the actors method to return an immutable set of actors. Immutable because I've had enough fun with race conditions in the past, and a set because that's exactly what it is – the resulting collection needs to contain each actor exactly once. Documenting this by using the type system feels much cleaner than using, er..., documentation.

However, Map.keySet does not return an immutable set. At first, this confused me, because I failed to see the difference between immutable (no one will ever be able to modify this collection) and unmodifiable (which only ensures the caller will not be able to modify the collection). Given the genericity of Map, it follows that keySet can only return a generic set; after all, the key set of a mutable Map might (and will) change when the underlying map changes. For example:

scala> import collection.mutable
import collection.mutable

scala> val m = mutable.HashMap("duck"->"quack")
m: scala.collection.mutable.HashMap[java.lang.String,java.lang.String] 
   = Map((duck,quack))

scala> val keys = m.keySet  
keys: scala.collection.Set[java.lang.String] = Set(duck)

scala> m("cat") = "meow"   

scala> keys
res1: scala.collection.Set[java.lang.String] = Set(duck, cat)

Which shows that the value keys is clearly not immutable.

Lessons learned

Scala collections are very powerful, but if you're new to Scala and coming from Java, you have to unlearn a lot. The Scala 2.8 Collections API is probably the best place to start, and it's a shame it's hard to find – I only found it because of this post on the scala-user mailing list.

The first few times you will get confused, and if you're like me, you will yell at the compiler a lot. But in the end, you will appreciate how the design of the collections API is actually very clean and semantically sound.

No comments:

Post a Comment