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...
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.Map
s 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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With