views:

74

answers:

1

It looks like -- in Scala 2.8.0 -- if you map() a Map instance to a sequence of 2-tuples that you end up getting a Map back. When this happens, any of the 2-tuples with the same first element are considered duplicates, and you only end up getting the last one. This is different from what happened in 2.7.7. This is easier to understand with an example.

Scala 2.7.7:

scala> val m = Map("a" -> 1, "b" -> 2, "c" -> 3)
m: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1, b -> 2, c -> 3)

scala> m.map { case (k, v) => ("foo", v) }
res5: Iterable[(java.lang.String, Int)] = ArrayBuffer((foo,1), (foo,2), (foo,3))

Scala 2.8.0:

scala> val m = Map("a" -> 1, "b" -> 2, "c" -> 3)
m: scala.collection.immutable.Map[java.lang.String,Int] = Map((a,1), (b,2), (c,3))

scala> m.map { case (k, v) => ("foo", v) }
res16: scala.collection.immutable.Map[java.lang.String,Int] = Map((foo,3))

Is this expected? Is the change documented somewhere? It seems like a reasonable idea, but it has cost me a lot of time upgrading a 2.7.7 app that was relying on the old behaviour.

Update:

As pointed out below by Kris Nuttycombe, reading Migrating from Scala 2.7 might have been a good start before asking this question :) In particular it mentions using the compiler flag -Xmigration, which seems extremely useful when porting.

+7  A: 

Yes, this is the intended behavior; indeed, enabling this sort of thing was a major point of the collections system redesign in 2.8. By default, Scala will choose the most specific type that it can when applying the map function; however, if you need the iterable (for example, because you're concerned about elimination of values by duplicate keys the easiest thing is probably to simply call toSeq on the map:

scala> val iter = m.toSeq.map { case (k, v) => ("foo", v) }                                  
iter: Seq[(java.lang.String, Int)] = List((foo,1), (foo,2), (foo,3))

As far as this being documented, it is discussed extensively here: http://www.scala-lang.org/node/2060 and (even more so) here: http://www.scala-lang.org/docu/files/collections-api/collections.html

Kris Nuttycombe
Thanks for the info, Kris. I ended up doing exactly what you suggested to get past the issue.
overthink
For porting from 2.7.7 you may be able to simplify your effort by having the right implicit CanBuildFrom in scope wherever you invoke `m.map`. Or perhaps the problem is avoiding the *unwanted* CanBuildFrom?
Ben Jackson
It occurred to me there's another way that's uglier, but avoids toSeq: simply make it so that the compiler can't tell that m is a map with a cast: scala> m.asInstanceOf[Iterable[(String, Int)]].map { case (k, v) => ("foo", v) }res0: Iterable[(java.lang.String, Int)] = List((foo,1), (foo,2), (foo,3))
Kris Nuttycombe