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.
- If two objects are equal, they should have the same hashCode
- 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