Scoobie
Scoobie

Reputation: 1139

Why do `scala.collection.mutable.Map`s `withDefaultValue`s not properly retain default values?

What is happening here?

Welcome to Scala 2.13.5 (OpenJDK 64-Bit Server VM, Java 1.8.0_312).

...

> import scala.collection.mutable.{Map => MMap, ListBuffer => MList}
import scala.collection.mutable.{Map=>MMap, ListBuffer=>MList}

> val m = MMap[String, MList[String]]().withDefaultValue(MList[String]())
val m: scala.collection.mutable.Map[String,scala.collection.mutable.ListBuffer[String]] = Map()

> m("a")
val res0: scala.collection.mutable.ListBuffer[String] = ListBuffer()

> m("a").addOne("b")
val res1: scala.collection.mutable.ListBuffer[String] = ListBuffer(b)

> m
val res2: scala.collection.mutable.Map[String,scala.collection.mutable.ListBuffer[String]] = Map()

> m.keys
val res3: Iterable[String] = Set()

> m("a")
val res4: scala.collection.mutable.ListBuffer[String] = ListBuffer(b)

some of the text above has been removed for readability

Upvotes: 1

Views: 83

Answers (1)

Alin Gabriel Arhip
Alin Gabriel Arhip

Reputation: 2638

Because your default value is mutable, you just mutated the default value. But you never inserted anything in the Map that is why you don't see anything inside.

m("a") is syntactic sugar for m.apply("a") which since there is no value for that key, but there is a default value set, it results in the default value you just set earlier.

If you'll check the withDefaultValue doc, you'll see this:

The same map with a given default value. Note: The default is only used for apply. Other methods like get, contains, iterator, keys, etc. are not affected by withDefaultValue. Invoking transformer methods (e.g. map) will not preserve the default value.

Since ListBuffer is mutable you are directly mutating the contents of the default value, when using the addOne method on it.

So now every key that has no value in the Map will result in ListBuffer(b):

scala> m("a")
val res1: scala.collection.mutable.ListBuffer[String] = ListBuffer(b)

scala> m("c")
val res2: scala.collection.mutable.ListBuffer[String] = ListBuffer(b)

scala> m("whatever")
val res3: scala.collection.mutable.ListBuffer[String] = ListBuffer(b)

You should use an immutable List as the default value if you want it to remain unchanged.

Upvotes: 2

Related Questions