1

I have been counting occurences with a mutable map:

var bar = collection.mutable.Map[Int, Int]().withDefaultValue(0)

Now bar(a) += b works just fine, no matter if the key a is present in bar already or not (in which case it will then be added).

I tried the same thing with a mutable map of mutable maps:

var foo = collection.mutable.Map[Int, collection.mutable.Map[Int, Int]]().
          withDefaultValue(collection.mutable.Map().withDefaultValue(0))

How does foo(a)(b) += x look without syntactic sugar?

Using What are all the instances of syntactic sugar in Scala? I would assume it expands to:

foo.apply(a).put(b, foo.apply(a).apply(b) + x)

But why does this not update foo itself accordingly like in the introductory example (i.e. foo will not have a dedicated value for key a if it wasn't present before)?

Edit: As Perseids pointed out, foo(a)(b) += x will change the mutable default value. Is this a desired feature? Using getOrElseUpdate as suggested by DaoWen seems to be the best way to overcome both problems. But while that works well for functions of type Int => Int => Int it becomes really cumbersome for functions of type Int => Int => Int => Int => Int. So I'm still happy for any suggestions!

Community
  • 1
  • 1
  • `foo(a)` (in the first example) and `foo(a)(b)` (in the second example) are both of type Int. And there is no += method in the [Int class](http://www.scala-lang.org/api/current/#scala.Int). – user2860570 Oct 09 '13 at 09:42
  • absolutely right, I's under the scala-daze effect! – pagoda_5b Oct 09 '13 at 16:13
  • "_foo(a)(b) += x will change the mutable default value. Is this a desired feature?"_ I think this was designed with immutable objects in mind. It's fine to return the same immutable object as the default value (e.g., an `Int` or an immutable `Map`). It doesn't work so well for mutable values... – DaoWen Oct 09 '13 at 20:21
  • Is it really necessary to use that many levels of nesting in the map? Could you just use a Tuple as the key instead? In other words, instead of `Int => Int => Int => Int => Int`, you'd have `(Int, Int, Int, Int) => Int`. – DaoWen Oct 10 '13 at 00:56

2 Answers2

3

This is actually an issue with withDefaultValue, not the += operator. Your first withDefaultValue for foo returns a new mutable map if the given key does not exist. When you do the lookup foo(a)(b), if foo(a) doesn't exist, then it returns a new map, which we'll call tmp. foo(a)(b) += x then essentially expands to this:

val tmp = foo(a)
tmp(b) += x

The problem is that only tmp is being updated by +=, not foo. So your update is happening on tmp, but tmp gets thrown away after the call because it's never stored anywhere.

If you want your parent map to get updated, you might want to look into using getOrElseUpdate instead of relying on withDefaultValue.


Note: As Perseids points out in the comments below, withDefaultValue takes a by-value parameter. This means that every time you get an unset key from your map, it's going to return the same mutable map instance! This is yet another reason you should think about using getOrElseUpdate (which uses a by-name parameter), or at least withDefault, which takes a function. (That's all assuming that you actually want different map instances for each slot in your map...)

DaoWen
  • 32,589
  • 6
  • 74
  • 101
  • How does this temporal map get generated? The outer `withDefaultValue` only gets a reference to a concrete instance of a mutable.Map, not of a factory. – Perseids Oct 09 '13 at 18:14
  • I just played a little with the REPL and your explanation does not match my results. I created an Array `val a = Array[Int] (10)` and put that into withDefaultValue `val bar = collection.mutable.Map[Int,Array[Int]]().withDefaultValue(a)`. Now `bar(0) eq a` evaluates to `true`, i.e. bar returns the same original reference to `a` every time a key is not found and _not_ a copy of `a`. – Perseids Oct 09 '13 at 19:27
  • @Perseids - You're right. I thought `withDefaultValue` had a *by-name* parameter. It won't return a new map each time. I think that's a totally separate issue... I'll update my answer though. – DaoWen Oct 09 '13 at 20:10
1

How does foo(a)(b) += x look without syntactic sugar?

It depends on whether or not the object returned by foo(a)(b) has a method named += or not. If it does, then it's equivalent to:

foo.apply(a).apply(b).+=(x)

If it doesn't, then it's equivalent to:

foo.apply(a).update(b, foo.apply(a).apply(b).+(x))

Except without the duplicate evaluation of foo.apply(a) (I think.)

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653