Traversable ops – Map + Flatten = Flatmap

Una de las dudas más comunes, y que, por lo general, suele llevar a confusión a la gente que se inicia en Scala es:

¿Qué diferencia hay entre map y flatMap?

Map

La función map es el conversor por excelencia. Se encarga de transformar los elementos que componen el Traversable en otros elementos del mismo o distinto tipo.
Su notación es, para un T[A] :

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

Si lo vemos con un ejemplo,

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

tenemos que dada una lista de enteros, se aplica la función de transformación (n:Int) => n.toString a cada uno de los miembros de la lista, generando una nueva lista pero del tipo resultante de la función de transformación, es decir, de tipo String.

Aunque no atañe exclusivamente a la función map, cabe recordar que hay otras formas abreviadas para definir la función de transformación:

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

flatMap

Vale, el map es asumible, se puede entender, pero entonces….¿qué demonios es un flatMap? Para entenderlo es necesario que nos desviemos un tanto para conocer a nuestra función amiga flatten.

flatten

Esta operación, con la siguiente signatura:

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

nos permite aplanar un traversable de traversables. Es decir, aplanamos las colecciones que componen esta colección. Por ejemplo:

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

El tipo contenido no tiene por qué ser del mismo que el tipo contenedor (List[List[Int]]), también aplica sobre otros traversables muy interesantes, como el tipo Option[T]:

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

flatMap (ahora enserio…)

Entonces, ¿qué es un flatMap? Ahora os podréis hacer una idea 😉
Es justamente eso, aplicar una operación de map y posteriormente una de flatten.

Pongamos un ejemplo práctico:

Tenemos una función que recibe un cierto objeto en JSON y una función que lo deserializa convirtiéndolo en un mapa. Es posible, que en el objeto JSON vengan algunos campos con valor null. ¿Como devolvemos una lista con los valores obviando los que son nulos?

Una primera aproximación, podría ser…

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
}

Pero como dijo David, usar nulls no es una Option, de manera que podríamos cambiarlo por

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

¿Qué ha pasado aquí? Para verlo en detalle, si la misma operación la realizamos en dos pasos simulando el input, tenemos que:

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"))

Ahora sí, creo que respondimos a la pregunta del millón

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