chevybow
chevybow

Reputation: 11968

What does flatMap do exactly?

For a coding assignment for school I have to do stuff with flatmap but I have absolutely no idea what it does and I've read a couple pages online and read in my textbook but I still have no true understanding of what it does. I know what map does but for some reason it's hard for me to wrap my head around flatmap. Can anyone help? Thanks.

Just to add some more information- when I look at examples online I DO see how flatmap returns something different from map. But what is flatmap actually doing when its being called on something? How does flatmap actually work? What is it doing before it returns the result?

Upvotes: 3

Views: 1515

Answers (3)

Luong Ba Linh
Luong Ba Linh

Reputation: 802

In reactive programming, you often come into the situation in which you need to use flatMap to convert Future[Future[List]] to Future[List]. For example, you have two functions: get Users from database and process retrieved Users; and both return Future[List[User]]. If you apply map to get and process, the result will be Future[Future[List[User]]] which does not make any sense. Instead, you should use flatMap:

def main(): Future[List[User]] = getUsers flatMap processUsers    
def getUsers: Future[List[User]]
def processUsers(users: List[User]): Future[List[User]]

Upvotes: 0

Lee
Lee

Reputation: 144206

Functors define map which have type

trait Functor[F[_]] {
    def map[A, B](f: A => B)(v: F[A]): F[B]
}

Monads are functors which support two additional operations:

trait Monad[M[_]] extends Functor[M] {
    def pure[A](v: A): M[A]
    def join[A](m: M[M[A]]): M[A]
}

Join flattens nested values e.g. if m is List then join has type

def joinList[A](l: List[List[A]]): List[A]

If you have a monad m and you map over it, what happens if b is the same monadic type? For example:

def replicate[A](i: Int, value: A): List[A] = ???
val f = new Functor[List] {
    def map[A, B](f: A => B)(v: List[A]) = v.map(f)
}

then

f.map(x => replicate(x, x))(List(1,2,3)) == List(List(1), List(2,2), List(3,3,3))

This has type List[List[Int]] while the input is a List[Int]. It's fairly common with a chain of operations to want each step to return the same input type. Since List can also be made into a monad, you can easily create such a list using join:

listMonad.join(List(List(1), List(2,2), List(3,3,3))) == List(1,2,2,3,3,3)

Now you might want to write a function to combine these two operations into one:

trait Monad[M] {
   def flatMap[A, B](f: A => M[B])(m: M[A]): M[B] = join(map(f)(m))
}

then you can simply do:

listMonad.flatMap(List(1,2,3), x => replicate(x, x)) == List(1,2,2,3,3,3)

Exactly what flatMap does depends on the monad type constructor M (List in this example) since it depends on map and join.

Upvotes: 5

Synesso
Synesso

Reputation: 39018

Here's an analogy.

Imagine you have a big bag filled with shopping vouchers for cartons of eggs. If you have a function which is "use the voucher to buy a carton of eggs" and you called bigBagOfVouchers.map(buyCartonOfEggs), you'd have a bag of cartons of eggs.

However, if you called bigBagOfVouchers.flatMap(buyCartonOfEggs), you'd have a bag of eggs - without any cartons.

flatMap flattens the result by one level. What might have been Bag[Carton[Egg]] is now Bag[Egg].

Upvotes: 6

Related Questions