Map versus FlatMap on String

29,286

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 mapreturns.

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?
Because flatMap treats a String as a sequence of Char, it flattens the resulting list of strings into a sequence of characters (Seq[Char]).
flatMap is a combination of map and flatten, so it first runs map on the sequence, then runs flatten, 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.)

Share:
29,286

Related videos on Youtube

Kevin Meredith
Author by

Kevin Meredith

Scala developer Haskell student https://www.linkedin.com/pub/kevin-meredith/11/22a/334

Updated on July 25, 2020

Comments

  • Kevin Meredith
    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 than flatmap for a List[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 calling map on the String?

    • om-nom-nom
      om-nom-nom over 10 years
      Because you can't put two characters in single Char type? And character + character is two characters that produce String type.
  • Explorer
    Explorer over 7 years
    Hi @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
    VonC over 7 years
    @Novice Interesting. That would be best as a question on its own for others to test it out.
  • Jas
    Jas about 7 years
    how am i supposed to guess without looking at implementation or trying it out that string map converts it to IndexSeq[String] and that flatten converts it back to String. it sounds to me like every time I will apply map or flatMap 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
    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
    VonC about 7 years
    @Jas I agree. safaribooksonline.com/library/view/learning-scala/9781449368‌​814/… 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."