Traversable ops – Map + Flatten = Flatmap *

One of the most frequently asked questions and that usually leads to confusion among those that are new to Scala is:

What’s the difference between map and flatMap?

Map

Map function is the prime converter function. It transforms the elements that compose the Traversable into other elements of the same or different type. Its notation is, for a given T[A]:

def map[B](f: A => B):T[B]

If we put an example,

val myList: List[Int] = List(1,2,3)
val anotherList: List[String] =
  myList.map((n:Int) => n.toString) //List("1","2","3")

we have that, given a list of integers, the transformation function (n:Int) => n.toString is applied to every member in the list, generating a new list whose type is the one that results from the transformation function, that is, String type.

Though this is not unique for map function, we should remember that there are shorter ways to define the transformation function:

val f: Int => String = (n: Int) => n.toString
val f: Int => String = n => n.toString
val f: Int => String = _.toString

flatMap

Well, map is now ok, understood, but then… what the hell is flatMap? In order to understand it, we need to make a short detour and get to know our helping function flatten.

flatten

This operation, with the following signature:

def flatten[B](implicit asTraversable: (A) => GenTraversableOnce[B]): Traversable[B]

allows us to flatten a traversable of traversables. That is to say, we flatten the collections that compose a collection. For instance:

val l: List[List[Int]] = List(List(1,2,3),List(),List(4),List(5))
require(l.flatten==List(1,2,3,4,5)

The contained type doesn’t have to be the same as the container’s List[List[Int]]). It also applies to some other really interesting traversables, such as Option[T]:

val l: List[Option[Int]] = List(Option(1),None,None,Option(2),Option(3))
require(l.flatten==List(1,2,3)

flatMap (now seriously…)

Then, what is a flatMap? By now you might have gotten an idea 😉
It’s just that, the application of a map operation followed by a flatten.

Let’s put a practical example:

We have a function that gets a JSON object and a function that deserializes it, converting it into a map. It is possible that some fields in the JSON object have null value. How can we return a list with the non-null values?

A first approach could be…

type KeyValueMap = Map[String,String]
type ValueList = List[String]
def fieldValues(obj: String, fieldDeser: String => KeyValueMap): ValueList = {
  fieldDeser(obj).filter{
    case (key,value) => value != null
  }.values.toList
}

But, as David said, using nulls is not an option, and instead of that, we could write

type KeyValueMap = Map[String,String]
type ValueList = List[String]
def fieldValues(obj: String, fieldDeser: String => KeyValueMap): ValueList = {
  fieldDeser(obj).values.flatMap(Option(_)).toList
}

What has happened here? In detail, if we do the same operation but in two steps and simulate the input, we have tha

val it: List[String] =
  List("value1",null,"value3")//Simulating 'fieldDeser(ob).values'
val mapped: Iterable[Option[String]] =
  it.map(Option(_)) //Remember Option(null)==None
require(mapped==List(Option("value1"),None,Option("value3")))
require(mapped.flatten==List("value1","value3"))

Now, I think we’ve finally answered to the million-dollar question.

1zltyts

Anuncios

One thought on “Traversable ops – Map + Flatten = Flatmap *

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s