5

Consider the following code that counts the frequency of each string in the list and stores the results in the mutable map. This works great, but I don't understand where the += method is defined?! Is this some weird implicit conversion thing or what? I saw this code somewhere but it didn't include an explanation for the +=.

val list = List("a", "b", "a")
val counts = new scala.collection.mutable.HashMap[String, Int]().withDefaultValue(0)
list.foreach(counts(_) += 1)
counts
//> res7: scala.collection.mutable.Map[String,Int] = Map(a -> 2, b -> 1)

The apply of map returns an Int, but Int doesn't have a += and this method updates the map with a new value, so it looks as if the apply returns a mutable integer that has a += method...

frank.durden
  • 103
  • 1
  • 2
  • 5

2 Answers2

8

This is not an implicit conversion - it is a desugaring. Writing:

x += 1

desugars to:

x = x + 1

if the class of x does not have a += method defined on it.

In the same way:

counts("a") += 1

desugars to:

counts("a") = counts("a") + 1

because counts("a") is an Int, and Int does not have a += method defined.

On the other hand, writing:

x(expression1) = expression2

desugars to a call to the update method in Scala:

x.update(expression1, expression2)

Every mutable Map has an update method defined - it allows setting keys in the map.

So the entire expression is desugared to:

list.foreach(x => counts.update(x, counts(x) + 1))

This += is not to be confused with the += method on mutable.Maps in Scala. That method updates the entry in the map if that key already existed, or adds a new key-value pair. It returns the this reference, that is, the same map, so you can chain += calls. See ScalaDoc or the source code.

axel22
  • 32,045
  • 9
  • 125
  • 137
  • Yes, `+=` from `mutable.Map` is never called above. The `counts("a") += 1` is desugared into `counts("a") = counts("a") + 1`, which is further desugared into an `update` call on the map. – axel22 Jan 30 '13 at 13:53
  • Ok thanks guys that double desugaring explanation makes sense! I removed my first comment that was based on @axel22 first answer that he then modified. Anyway, now it makes sense, thanks! :D – frank.durden Jan 30 '13 at 14:02
3

For these moments where you wonder what compiler magic is happening in a part of your code, scalac -print is your best friend (see this question).

If you do a scalac -print C.scala where C.scala is

package test

class C {
    def myMethod() {
        val counts = new scala.collection.mutable.HashMap[String, Int]().withDefaultValue(0)
        counts("a") += 1
    }
}

you get

package test {
  class C extends Object {
    def myMethod(): Unit = {
      val counts: collection.mutable.Map = new collection.mutable.HashMap().withDefaultValue(scala.Int.box(0));
      counts.update("a", scala.Int.box(scala.Int.unbox(counts.apply("a")).+(1)))
    };
    def <init>(): test.C = {
      C.super.<init>();
      ()
    }
  }

It came as a surprise for me also, but apparently scalac will transform

map(key) =<op> rhs

to

map.update(key, map.apply(key) <op> rhs)
Community
  • 1
  • 1
Marius Danila
  • 10,311
  • 2
  • 34
  • 37