I was working through Odersky's ScalaDays 2011 keynote talk, where he constructs a phone number synonym generator in remarkably few lines of code, when I got to this particular line (assigning charCode
):
val mnem: Map[Char, String] = // phone digits to mnemonic chars (e.g. '2' -> "ABC")
val charCode: Map[Char, Char] = for ((digit, str) <- mnem; letter <- str)
yield (letter -> digit) // gives ('A', '2'), ('B', '2') etc
Why is charCode
of type Map
?
When I yield tuples in other examples, I merely obtain a sequence of tuples -- not a map. For example:
scala> for (i <- 1 to 3) yield (i -> (i+1))
res16: scala.collection.immutable.IndexedSeq[(Int, Int)] = Vector((1,2), (2,3), (3,4))
One can easily convert that to a map with toMap()
, like so...
scala> (for (i <- 1 to 3) yield (i -> (i+1))).toMap
res17: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 2 -> 3, 3 -> 4)
... but somehow, Odersky's example avoids this.
What Scala magic, if any, am I overlooking here?
Addendum 1: Implicit conversion? I'd like to add some detail pertaining to Oxbow Lake's comment (NB: my comment there may be partially in error, having misunderstood slightly, perhaps, what he was getting at).
I suspected that some kind of implicit conversion was happening because a map was required. So I tried Odersky's iterator in the interpreter, with no hints as to what it should produce:
scala> val mnem = Map('2' -> "ABC", '3' -> "DEF", '4' -> "GHI") // leaving as a map, still
scala> for ((digit, str) <- mnem; letter <- str) yield (letter, digit)
res18: scala.collection.immutable.Map[Char,Char] = Map(E -> 3, F -> 3, A -> 2, I -> 4, G -> 4, B -> 2, C -> 2, H -> 4, D -> 3)
(Note that I'm leaving mnem
as a map here.)
Likewise, telling the compiler I wanted a map didn't change my own result:
scala> val x: Map[Int,Int] = for (i <- 1 to 3) yield (i -> (i+1))
<console>:7: error: type mismatch;
found : scala.collection.immutable.IndexedSeq[(Int, Int)]
required: Map[Int,Int]
val x: Map[Int,Int] = for (i <- 1 to 3) yield (i -> (i+1))
On the other hand, following Eastsun's hints (and this seems to be what OL was saying, too), the following (dorky) modification does produce a map:
scala> for ((i,j) <- Map(1 -> 2, 2 -> 2)) yield (i -> (i+1))
res20: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 2 -> 3)
So if the iterated value comes from a map, somehow a map is produced?
I expect the answer is to be understood by (a) converting the "for" loop to its longhand equivalents (a call/calls to map
) and (b) understanding what implicits are being magically invoked.
Addendum 2: Uniform Return Type: (huynhjl:) That seems to be it. My first example converts to
(1 to 3).map(i => (i, i+1)) // IndexedSeq[(Int, Int)]
Whereas the second becomes something akin to this:
Map(1 -> 2, 2 -> 2).map(i => (i._1, i._1+1)) // Map[Int,Int]
The type of "Map.map" is, then
def map [B, That] (f: ((A, B)) ⇒ B)(implicit bf: CanBuildFrom[Map[A, B], B, That]): That
Ah, trivial. ;-)
Addendum 3: Well, okay, that was too simple, still. Miles Sabin provides a/the more correct desugaring, below. Even more trivial. ;-)