Transforming the Future

A few weeks ago we talked about the type Future and its use to create asynchronous calls.

We saw how to work with blocking calls to obtain the value of the future. We also used callbacks in order to obtain the result of the future asynchronously. However, there are some issues that were left unsaid. And by that I’m referring to transforming the Future without blocking the execution.

Future transformations

In order to transform futures, as with other Scala basic types, mainly two methods are used: map and flatmap.

Map method

Map method allows us to change the content of a future by applying a function. For instance, if we have a method to get the first million prime numbers but we want to transform it to return just the first hundred ones, we can apply the map method in the following way:

def getFirstMillionOfPrimes(): Future[List[Int]] = ???

getFirstMillionOfPrimes().map(
  (list: List[Int]) => list.take(100)
)

This way we will be transforming the inside of the future without breaking the asynchrony.

pi2band2bi

FlatMap method

On the other hand, the flatMap method allows us to apply a function to the content of the future and returning a future in turn. After that, a flatten operation is applied to convert the Future[Future[A]] into a simple Future[A]. What the f…? Better explained with an example.

Imagine we want to concatenate the first million prime numbers in a string. To do so, we’ll use a new method:

def concatenate(l: List[Int]): Future[String] = ???

and now we perform a  flatMap

getFirstMillionOfPrimes().flatMap(
  (list: List[Int]) => concatenate(list)
) //Future[String]

And how can we do all this in a more simple way?

Easy question. For comprehension to the rescue! With a spoonful of syntactic sugar we can write a much more readable code.

for {
  primes <- getFirstMillionOfPrimes()
  primesString <- concatenate(primes)
} yield primesString

This way, the concatenation operation won’t be applied until the prime numbers are obtained with the method getFirstMillionPrimes.

This allows us to keep an order when composing asynchronous calls. Besides, if the first asynchronous call fails, the second won’t be conducted.

And that’s all for today. Now you know how to change the future. What a shame not to be able to change the past 😦

doesnt-go-into-girls-shower

See you soon!

Anuncios

Transformando el futuro

Hace ya unas cuantas semanas estuvimos hablando sobre el tipo Future para crear llamadas asíncronas.
Vimos como trabajar con llamadas bloqueantes para obtener el valor del futuro. También utilizamos callbacks para obtener el resultado del futuro de forma asíncrona. Sin embargo se quedaron algunos puntos en el tintero. Me refiero a transformar los Future sin bloquear la ejecución.

Transformaciones de Futuros

Para transformar los futuros, al igual que con otros tipos básicos de Scala, se usa principalmente los métodos map y flatmap.

El método map

El método map nos permite cambiar el contenido de un futuro aplicando una función. Por ejemplo, si tenemos un método que nos permite obtener el primer millón de números primos, pero queremos transformarlo para que solo nos devuelva los cien primeros, podemos aplicar el método map de la siguiente manera:

def getFirstMillionOfPrimes(): Future[List[Int]] = ???

getFirstMillionOfPrimes().map(
  (list: List[Int]) => list.take(100)
)

De esta forma estamos transformando el interior del Futuro sin romper la asincronía.

pi2band2bi

El método flatMap

Por otro lado, el método flatMap nos permite aplicar una función al contenido del futuro, que devuelve un Futuro a su vez. Después, aplica una operación de flatten para convertir el Future[Future[A]] en un simple Future[A]. What the f…? Se entiende mejor con un ejemplo.

Imaginemos que queremos concatenar en una cadena de texto el primer millón de números primos. Para ello utilizamos un nuevo método:

def concatenate(l: List[Int]): Future[String] = ???

y ahora realizamos un flatMap

getFirstMillionOfPrimes().flatMap(
  (list: List[Int]) => concatenate(list)
) //Future[String]

¿Y como podemos componer hacer todo esto de una forma más sencilla?

Pues muy sencillo. ¡For comprehenssion al rescate! Aplicando un poco de syntactic sugar podemos tener un código mucho más legible.
Basta con hacer lo siguiente:

for {
  primes <- getFirstMillionOfPrimes()
  primesString <- concatenate(primes)
} yield primesString

De esta manera, no se aplicará la operación de concatenación hasta que no se hayan obtenido los números primos mediante el método getFirstMillionPrimes.
Esta permite guardar un cierto orden a la hora de hacer composición de llamadas asíncronas. Además, si la primera llamada asíncrona falla, no se efectuará la segunda.

Y esto es todo por hoy. Ahora ya sabes como cambiar el Futuro. Una lástima no poder cambiar el pasado 😦

doesnt-go-into-girls-shower

¡Hasta la próxima!

For comprehension and importance of beauty

There are many existential questions in the modern world we live in. But there is one question that surpasses them all. What does a for comprehension really do?

The answer to this intricate matter is… nothing new. For comprehension structures are just syntactic sugar. Underneath, and depending on how the block is built, there will be calls to map, flatMap, withFilter or foreach.

For comprehension structures will have two key parts:

  • The for block, by which nested queries are performed
  • The yield block, where previous queries are grouped and treated

In order to understand these concepts, let’s take a look at a small example:

for {
  x <- List(1, 2, 3)
  y <- List(true, false) 
} yield (x, y)

The result of this expression is the combination in tuples of the first list with the second:

List(
  (1, true),
  (1, false),
  (2, true),
  (2, false),
  (3, true),
  (3, false)
)

What this for comprehension is really doing is performing the following operations:

List(1, 2, 3).flatMap(x =>
  List(true, false).map( y => (x, y))
)

As can be seen, with for blocks everything becomes much more readable. All that needs to be done is to join several flatMaps to conclude with a map. Let’s see another example:

for {
  x <- List(1, 2)
  y <- List(true, false)
  z <- List("a", "b") 
} yield (x, y, z)

which is equivalent to:

List(1, 2, 3).flatMap(x =>
  List(true, false).flatMap( y =>
    List("a", "b").map(z => (x, y, z))
  )
)

and the result produced is:

List(
  (1, true, "a"),
  (1, true, "b"),
  (1, false, "a"),
  (1, false, "b"),
  (2, true, "a"),
  (2, true, "b"),
  (2, false, "a"),
  (2, false, "b")
)

Moreover, we can also apply filters by using embedded if statements:

for {
  x <- List(1, 2, 3, 4, 5, 6, 7)
  if x < 3
  y <- List("a", "b") 
} yield (x, y)

//result: List((1,a), (1,b), (2,a), (2,b))

whose equivalent is:

List(1, 2, 3, 4, 5, 6, 7)
  .withFilter(_ < 3)
  .flatMap( x =>
    List("a", "b").map(
      y => (x, y)
    )
  )

It may happen that not only we want to create a new collection, but also we want some operations to be applied to each one of the elements. In such a situation, we will not use the yield block. This will change the translation as we don’t want to make changes to a collection and thus, instead of being a map method, it will be the foreach method. This makes perfect sense since, in this case, we only want to perform actions for each generated event and the type of the final result will be Unit.

for {
  x <- List(1, 2, 3)
  y <- List(true, false)
} println(s"($x , $y)") 

List(1, 2, 3).foreach(x =>
  List(true, false).foreach( y =>
    println(s"($x , $y)")
  )
)

After all these examples, we hope that you use the for comprehension to make your code more readable whenever possible. Although we all know that the magic lies within.

35dunb

For comprehension y la importancia de la belleza

Existen multitud de dudas existenciales en el mundo moderno en el que vivimos. Pero hay una duda que supera a todas ellas. ¿Qué hace realmente una for comprehension?

La respuesta a esta intrincada cuestión es….nada nuevo. Las estructuras for comprehension son simplemente syntactic sugar. Por debajo, en función de como construyamos el bloque, habrá llamadas a map, flatmap, withFilter o foreach.

Las for comprehension contarán con dos partes clave:

  • El bloque for mediante el cual se realizan consultas anidadas
  • El bloque yield donde se agrupan y tratan las consultas realizadas anteriormente

Para entender estos conceptos vamos a ver un pequeño ejemplo:

for {
  x <- List(1, 2, 3)
  y <- List(true, false) 
} yield (x, y)

El resultado de esta expresión será la combinación mediante tuplas de la primera lista con la segunda:

List(
  (1, true),
  (1, false),
  (2, true),
  (2, false),
  (3, true),
  (3, false)
)

Esta for comprehension realmente realizará las siguientes acciones:

List(1, 2, 3).flatMap(x =>
  List(true, false).map( y => (x, y))
)

Como se puede observar, mediante los bloques for todo se vuelve mucho más legible. Lo único que hacemos es hilar varios flatmap para culminar con un map. Vamos a ver otro ejemplo:

for {
  x <- List(1, 2)
  y <- List(true, false)
  z <- List("a", "b") 
} yield (x, y, z)

cuyo equivalente será:

List(1, 2).flatMap(x =>
  List(true, false).flatMap( y =>
    List("a", "b").map(z => (x, y, z))
  )
)

y el resultado producido será:

List(
  (1, true, "a"),
  (1, true, "b"),
  (1, false, "a"),
  (1, false, "b"),
  (2, true, "a"),
  (2, true, "b"),
  (2, false, "a"),
  (2, false, "b")
)

Además, también podemos utilizar filtros mediante sentencias if embebidas:

for {
  x <- List(1, 2, 3, 4, 5, 6, 7)
  if x < 3
  y <- List("a", "b") 
} yield (x, y)

//result: List((1,a), (1,b), (2,a), (2,b))

cuyo equivalente es:

List(1, 2, 3, 4, 5, 6, 7).withFilter(_ < 3).flatMap( x => 
  List("a", "b").map(
    y => (x, y)
  )
)

Existe la posibilidad de que no solo queramos crear una nueva colección, sino realizar una acción con cada uno de los elementos. En ese caso no utilizaremos el bloque yield. Esto provocará que la traducción, al no querer realizar transformaciones a una colección, en vez de ser un método map, sea mediante el método foreach. Tiene bastante sentido ya que en este caso solo queremos realizar acciones por cada evento generado y el resultado final será de tipo Unit.

for {
  x <- List(1, 2, 3)
  y <- List(true, false) } println(s"($x , $y)") List(1, 2, 3).foreach(x =>
  List(true, false).foreach( y => 
    println(s"($x , $y)")
  )
)

Después de todos estos ejemplos, esperamos que utilicéis las for comprehension para hacer vuestro código más legible siempre que sea posible. Aunque todos sepamos que la magia se crea en el interior.

35dunb

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

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