18

What is the idiomatic way of a getOrElseUpdate for immutable.Map instances?. I use the snippet below, but it seems verbose and inefficient

var map = Map[Key, Value]()

def foo(key: Key) = {
  val value = map.getOrElse(key, new Value)
  map += key -> value
  value
}
Eugene Yokota
  • 94,654
  • 45
  • 215
  • 319
IttayD
  • 28,271
  • 28
  • 124
  • 178
  • 2
    `map += key -> value` --> Perhaps you mean `mutable.Map` map? – Vasil Remeniuk Dec 08 '10 at 09:46
  • 1
    I think you mean mutable.Map!? You are using += in your code which does not work for immutable.Map. For mutable.Map there is getOrElseUpdate(). – michid Dec 08 '10 at 09:51
  • 3
    @Vasil Remeniuk, @michid: Sure it works. When there's no '+=' method the compiler converts the expression to 'map = map + key -> value'. I updated the question to make it clear map is a var – IttayD Dec 08 '10 at 13:14

4 Answers4

15

I would probably implement a getOrElseUpdated method like this:

def getOrElseUpdated[K, V](m: Map[K, V], key: K, op: => V): (Map[K, V], V) =
  m.get(key) match {
    case Some(value) => (m, value)
    case None => val newval = op; (m.updated(key, newval), newval)
  }

which either returns the original map if m has a mapping for key or another map with the mapping key -> op added. The definition of this method is similar to getOrElseUpdate of mutable.Map.

Frank S. Thomas
  • 4,725
  • 2
  • 28
  • 47
9

Let me summarise your problem:

  • You want to call a method on a immutable data structure
  • You want it to return some value and reassign a var
  • Because the data structure is immutable, you’ll need to
    • return a new immutable data structure, or
    • do the assignment inside the method, using a supplied closure

So, either your signature has to look like

def getOrElseUpdate(key: K): Tuple2[V, Map[K,V]]
//... use it like
val (v, m2) = getOrElseUpdate(k)
map = m2

or

def getOrElseUpdate(key: K, setter: (Map[K,V]) => Unit): V
//... use it like
val v = getOrElseUpdate(k, map = _)

If you can live with one of these solutions, you could add your own version with an implicit conversion but judging by the signatures alone, i wouldn’t think any of these is in the standard library.

Debilski
  • 66,976
  • 12
  • 110
  • 133
  • I guess from all the answers it means there's no idiomatic way (other than @Daniel's suggestion of working with immutable objects all the way) – IttayD Dec 08 '10 at 21:47
  • The problem is that you want to reassign your variable while doing something else. There is no way to do that from inside another method without help (e.g. it gets ugly). You’d need proper support for pass-by-reference for that (which is only possible with some hacks in Scala). – Debilski Dec 08 '10 at 22:28
  • 1
    It's actually pretty easy to get pass-by-reference semantics in Scala: `case class Var[T](var ref: T) { def apply() = ref; def update(v: T) = ref = v }`. Idiomatic? No. – Aaron Novstrup Dec 09 '10 at 00:01
  • 1
    @Aaron Novstrup: But this requires the user to actively apply some wrapper (and unwrap it afterwards), hence it cannot be done without help inside some method. In most cases, supplying a setter closure (`m = _`) will be the better solution for the stuff you’d need pass-by-reference for. – Debilski Dec 09 '10 at 12:03
  • Wrapping/unwrapping is fairly transparent: e.g. `val v = new Var(1); v() = 2; println(v())`, but I agree that a closure is a simpler solution. – Aaron Novstrup Dec 09 '10 at 16:43
8

There's no such way - map mutation (update), when you're getting a map value, is a side effect (which contradicts to immutability/functional style of programming).

When you want to make a new immutable map with the default value, if another value for the specified key doesn't exist, you can do the following:

map + (key -> map.getOrElse(key, new Value)) 
Vasil Remeniuk
  • 20,519
  • 6
  • 71
  • 81
2

Why not use withDefault or withDefaultValue if you have an immutable map?

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • 1
    withDefault and withDefaultValue will return a default value, but will not update the map. – IttayD Dec 08 '10 at 14:40
  • @IttayD And what difference does that make? – Daniel C. Sobral Dec 08 '10 at 16:51
  • 2
    @Daniel `withDefault` and `withDefaultValue` will not give the same semantics as @IttayD's code above with respect to object identity. `withDefault` would create new, potentially unequal values on multiple retrievals of a given key. `withDefaultValue` would return a particular value on retrievals of different keys. – Aaron Novstrup Dec 08 '10 at 18:35
  • @Aaron True, which may or may not be of relevance. That's my point here: is there any particular reason why it cannot work? One should not discard it just because it was not the solution one has first envisioned. – Daniel C. Sobral Dec 08 '10 at 20:10
  • @Daniel, the difference is that if Value is mutable, I want the same reference that was returned to be returned the next call. Even if Value is immutable, I don't want to create it each call. – IttayD Dec 08 '10 at 21:50
  • @IttayD Values are not created anew by withDefaultValue, and withDefault can easily use a memoizing function. – Daniel C. Sobral Dec 09 '10 at 10:34
  • @Daniel so in withDefault I'd need to use a map, bringing me back to square one – IttayD Dec 10 '10 at 04:04
  • 1
    @IttayD But in that map you could just use `withDefault` with a memoizing function! ;-) – Aaron Novstrup Dec 11 '10 at 23:33