Map versus FlatMap on String
Solution 1
The reason for this behavior is that, in order to apply "map" to a String, Scala treats the string as a sequence of chars (IndexedSeq[String]
). This is what you get as a result of the map invocation, where for each element of said sequence, the operation is applied. Since Scala treated the string as a sequence to apply map
, that is what map
returns.
flatMap
then simply invokes flatten
on that sequence afterwards, which then "converts" it back to a String
Solution 2
You also have an interesting "collection of Scala flatMap examples", the first of which illustrates that difference between flatMap
and map
:
scala> val fruits = Seq("apple", "banana", "orange")
fruits: Seq[java.lang.String] = List(apple, banana, orange)
scala> fruits.map(_.toUpperCase)
res0: Seq[java.lang.String] = List(APPLE, BANANA, ORANGE)
scala> fruits.flatMap(_.toUpperCase)
res1: Seq[Char] = List(A, P, P, L, E, B, A, N, A, N, A, O, R, A, N, G, E)
Quite a difference, right?
BecauseflatMap
treats aString
as a sequence ofChar
, it flattens the resulting list of strings into a sequence of characters (Seq[Char]
).
flatMap
is a combination ofmap
andflatten
, so it first runsmap
on the sequence, then runsflatten
, giving the result shown.You can see this by running map and then flatten yourself:
scala> val mapResult = fruits.map(_.toUpperCase)
mapResult: Seq[String] = List(APPLE, BANANA, ORANGE)
scala> val flattenResult = mapResult.flatten
flattenResult: Seq[Char] = List(A, P, P, L, E, B, A, N, A, N, A, O, R, A, N, G, E)
Solution 3
Your map function c => ("." + c)
takes a char and returns a String. It's like taking a List and returning a List of Lists. flatMap flattens that back.
If you would return a char instead of a String you wouldn't need the result flattened, e.g. "abc".map(c => (c + 1).toChar)
returns "bcd".
Solution 4
With map
you are taking a list of characters and turning it into a list of strings. That's the result you see. A map
never changes the length of a list – the list of strings has as many elements as the original string has characters.
With flatMap
you are taking a list of characters and turning it into a list of strings and then you mush those strings together into a single string again. flatMap
is useful when you want to turn one element in a list into multiple elements, without creating a list of lists. (This of course also means that the resulting list can have any length, including 0 – this is not possible with map
unless you start out with the empty list.)
Related videos on Youtube
Kevin Meredith
Scala developer Haskell student https://www.linkedin.com/pub/kevin-meredith/11/22a/334
Updated on July 25, 2020Comments
-
Kevin Meredith almost 4 years
Listening to the Collections lecture from Functional Programming Principles in Scala, I saw this example:
scala> val s = "Hello World" scala> s.flatMap(c => ("." + c)) // prepend each element with a period res5: String = .H.e.l.l.o. .W.o.r.l.d
Then, I was curious why Mr. Odersky didn't use a
map
here. But, when I tried map, I got a different result than I expected.scala> s.map(c => ("." + c)) res8: scala.collection.immutable.IndexedSeq[String] = Vector(.H, .e, .l, .l, .o, ". ", .W, .o, .r, .l,
I expected that above call to return a String, since I'm
map
-ing, i.e. applying a function to each item in the "sequence," and then returning a new "sequence."However, I could perform a
map
rather thanflatmap
for aList[String]
:scala> val sList = s.toList sList: List[Char] = List(H, e, l, l, o, , W, o, r, l, d) scala> sList.map(c => "." + c) res9: List[String] = List(.H, .e, .l, .l, .o, ". ", .W, .o, .r, .l, .d)
Why was a
IndexedSeq[String]
the return type of callingmap
on the String?-
om-nom-nom over 10 yearsBecause you can't put two characters in single Char type? And character + character is two characters that produce String type.
-
-
Explorer over 7 yearsHi @VonC I was trying examples that are their in the link that you shared. For below examples as shown on his blog he is able to get results for the below examples but when I am trying to run it I am getting Number Format Exception for String "foo" when I am trying to apply
toInt
function.scala> val strings = Seq("1", "2", "foo", "3", "bar")
strings: Seq[java.lang.String] = List(1, 2, foo, 3, bar)
scala> strings.map(toInt)
res0: Seq[Option[Int]] = List(Some(1), Some(2), None, Some(3), None)
scala> strings.flatMap(toInt)
res1: Seq[Int] = List(1, 2, 3)
-
VonC over 7 years@Novice Interesting. That would be best as a question on its own for others to test it out.
-
Jas about 7 yearshow am i supposed to guess without looking at implementation or trying it out that
string
map converts it toIndexSeq[String]
and thatflatten
converts it back toString
. it sounds to me like every time I will applymap
orflatMap
to some data structure in scala I'll need to try it out or look at it's implementation to understand it's behaviour, is that the case? if so is this a good design (clear code wise)? -
Jas about 7 years
Because flatMap treats a String as a sequence of Char
how am i supposed to guess that? by trying it out? by looking at it's implementation? -
VonC about 7 years@Jas I agree. safaribooksonline.com/library/view/learning-scala/9781449368814/… gives a clue, describing the Sequence types, and listing String(!) in it: "In Scala String is a valid collection just like the others. A “string” derives its name, after all, from being a string of characters, in this case a sequence of Char elements. The String type is an immutable collection, extending Iterable and supporting its operations, while also serving as a wrapper for Java strings and supporting such java.lang.String operations as split and trim."