Friday, February 14, 2014

A word on mutability in Scala

If you familiar with Scala you've probably been reminded a few times to use immutable objects instead of mutable. There are many reasons and the one most often argued is that immutable state makes concurrency easier. This is true but its not the only reason, and immutable object should not always be used for that matter either. Some designs are more suited for mutable states even in Scala.

To entertain myself and you readers I would like to show an example where mutable perhaps behaves unexpectedly, all in the purpose of getting more comfortable with Scala programming.

Let's begin with a simple class called PairOfShoes. This class overrides the hashCode and equals functions which is common for objects. Pay attention to the variables in the constructor and the mix function which can mix-up the pair.

The 'eq' is a member of AnyRef which is the parent to all (reference) classes, if you were wondering.

Next we will instantiate shoes of different sizes and put these in a HashMap.
  val p1 = new PairOfShoes(10, 10)        
  val p2 = new PairOfShoes(7, 7)       
  val map = new HashMap[PairOfShoes, String]()  
  map += (p1 -> "BigPair")            
  map += (p2 -> "SmallPair")  
Pretty boring so far right, but here comes the fun part. Imagine that you are leaving a house party and you are looking for your shoes which are size 10. You can be sure that the pair of size 7 is not yours but by mistake you grab one size 10 and the other size 11 without noticing. In code this this writes to that you are sure the SmallPair does not equal the BigPair and that you mix-up a pair (two actually), leaving you with odd sizes.
  p1 == p2   // -> false  
  p1.mix(11, 10)  
You'll have a different pair of shoes and its still a big size but if you try the HashMap an exception will be thrown since the Pair is mutated.
  map(p1)   // -> throws NoSuchElementException  
This happens because the state of p1 has mutated and the hash has changed. To show this Scala provides a brief syntax to evaluate the hash (the double pound)
  p1## // -> 320  
  p1.mix(11, 10)  
  p1## // -> 321  
Lets create a third pair, also of size 10, and use this with the HashMap and see what happens
 val p3 = new PairOfShoes(10, 10)  
 map(p3)  // -> throws NoSuchElementException  
Even though the hashCode of p3 is the same as p1 before the mix-up the map still gives a NoSuchElementException. The strictEquals function gives false since p1 has changed. Now try to set p3 like this instead
 val p3 = new PairOfShoes(11, 10)  
The canEqual and equals does not help because, and I may be corrected here, in this case Scala use something called object location equality, the address in memory determines the equality. Finally, remove the mix(11, 10) and set the sizes of p1 and p3 equal to each other and the map finds a value of p3. This is confusing and the point I'm getting to is to use immutable values where possible.

I hope you enjoyed this short example and that it gives you something to think about. I recommend and personally believe in two rules for handling equality in Scala, both of which the example didn't follow.
  1. If two objects are equal, they should have the same hashCode
  2. A hashCode computed for an object should not change for the life of the object.
To avoid this perhaps unwanted behavior, the constructors arguments should be changed to immutable and the class modified to cater for this.

If you are interested in a more detailed text of equality I recommend this article:
http://www.artima.com/pins1ed/object-equality.html

No comments:

Post a Comment