I have been looking into FP languages (off and on) for some time and have played with Scala, Haskell, F#, and some others. I like what I see and understand some of the fundamental concepts of FP (with absolutely no background in Category Theory - so don't talk Math, please).
So, given a type M[A]
we have map
which takes a function A=>B
and returns a M[B]
. But we also have flatMap
which takes a function A=>M[B]
and returns a M[B]
. We also have flatten
which takes a M[M[A]]
and returns a M[A]
.
In addition, many of the sources I have read describe flatMap
as map
followed by flatten
.
So, given that flatMap
seems to be equivalent to flatten compose map
, what is its purpose? Please don't say it is to support 'for comprehensions' as this question really isn't Scala-specific. And I am less concerned with the syntactic sugar than I am in the concept behind it. The same question arises with Haskell's bind operator (>>=
). I believe they both are related to some Category Theory concept but I don't speak that language.
I have watched Brian Beckman's great video Don't Fear the Monad more than once and I think I see that flatMap
is the monadic composition operator but I have never really seen it used the way he describes this operator. Does it perform this function? If so, how do I map that concept to flatMap
?
BTW, I had a long writeup on this question with lots of listings showing experiments I ran trying to get to the bottom of the meaning of flatMap
and then ran into this question which answered some of my questions. Sometimes I hate Scala implicits. They can really muddy the waters. :)
flatMap() can be used where we have to flatten or transform out the string, as we cannot flatten our string using map().
The flatMap() method returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.
bind (or flatMap ) and unit (the constructor) are all it takes to be considered a monad. From those two, we can also build map . map lets us transform the wrapped object without having to rewrap it ourselves at the end of the function. This is particularly useful for arrays and other collection types.
flatMap() method is method of TraversableLike trait, it takes a predicate, applies it to each element of the collection and returns a new collection of elements returned by the predicate.
FlatMap, known as "bind" in some other languages, is as you said yourself for function composition.
Imagine for a moment that you have some functions like these:
def foo(x: Int): Option[Int] = Some(x + 2)
def bar(x: Int): Option[Int] = Some(x * 3)
The functions work great, calling foo(3)
returns Some(5)
, and calling bar(3)
returns Some(9)
, and we're all happy.
But now you've run into the situation that requires you to do the operation more than once.
foo(3).map(x => foo(x)) // or just foo(3).map(foo) for short
Job done, right?
Except not really. The output of the expression above is Some(Some(7))
, not Some(7)
, and if you now want to chain another map on the end you can't because foo
and bar
take an Int
, and not an Option[Int]
.
Enter flatMap
foo(3).flatMap(foo)
Will return Some(7)
, and
foo(3).flatMap(foo).flatMap(bar)
Returns Some(15)
.
This is great! Using flatMap
lets you chain functions of the shape A => M[B]
to oblivion (in the previous example A
and B
are Int
, and M
is Option
).
More technically speaking; flatMap
and bind
have the signature M[A] => (A => M[B]) => M[B]
, meaning they take a "wrapped" value, such as Some(3)
, Right('foo)
, or List(1,2,3)
and shove it through a function that would normally take an unwrapped value, such as the aforementioned foo
and bar
. It does this by first "unwrapping" the value, and then passing it through the function.
I've seen the box analogy being used for this, so observe my expertly drawn MSPaint illustration:
This unwrapping and re-wrapping behavior means that if I were to introduce a third function that doesn't return an Option[Int]
and tried to flatMap
it to the sequence, it wouldn't work because flatMap
expects you to return a monad (in this case an Option
)
def baz(x: Int): String = x + " is a number"
foo(3).flatMap(foo).flatMap(bar).flatMap(baz) // <<< ERROR
To get around this, if your function doesn't return a monad, you'd just have to use the regular map
function
foo(3).flatMap(foo).flatMap(bar).map(baz)
Which would then return Some("15 is a number")
It's the same reason you provide more than one way to do anything: it's a common enough operation that you may want to wrap it.
You could ask the opposite question: why have map
and flatten
when you already have flatMap
and a way to store a single element inside your collection? That is,
x map f
x filter p
can be replaced by
x flatMap ( xi => x.take(0) :+ f(xi) )
x flatMap ( xi => if (p(xi)) x.take(0) :+ xi else x.take(0) )
so why bother with map
and filter
?
In fact, there are various minimal sets of operations you need to reconstruct many of the others (flatMap
is a good choice because of its flexibility).
Pragmatically, it's better to have the tool you need. Same reason why there are non-adjustable wrenches.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With