Shapeless: polymorphic functions

Time ago, our friend Javier Fuentes illustrated us with an introduction to Shapeless.
Some months after that, at Scala Madrid meetup, he offered a pretty interesting speech about structural induction with Shapeless and HLists. We couldn’t avoid it and we got the enthusiasm flu 馃槢

What we want to achieve

Let’s set as our study case what I think more than one has thought before: how can I compose in the same for-comprehension different types. Something like:

import scala.util.Try

for {
  v1 <- List(1,2,3)
  v2 <- Try("hi, person ")
} yield s"$v2 $v1"

which usually comes from the hand of the following frustrating error:

<console>:15: error: type mismatch;
 found   : scala.util.Try[String]
 required: scala.collection.GenTraversableOnce[?]
         v2 <- Try("hi, person ")
            ^

Therefore we need a way to transform these data types (Future, Try) into iterable ‘stuff’ (GenTraversable[T] might work). In our example we won’t have in mind the information that we loose about the error that might happen, for example, if certain Try or Future expression has failed. For have a better understanding about the problem we present, let’s talk about some theory.

53132063

Monomorphism vs polymorphism

We say a method is monomorphic when you can only invoke it with parameters whose concrete type is explicitly declared in the method signature, whilst the polymorphic methods can take parameters of any type while it fits in the signature (in case of Scala language: parameter types). Speaking proper English:

def monomorphic(parameter: Int): String

def polymorphic[T](parameter: T): String

Polimorphism

It’s also good to know that a method can be polymorphic due to parameter types or just to parameter subtyping. E.g.:

def parametricallyPolymorphic[T](parameter: T): String

def subtypedPolymorphic(parameter: Animal): String

subtypedPolymorphic(new Cat)

If we use parameter types and we have NO information at all about those types, we are in front of a parametric polymorphism case.

If we use parameter types but we need any extra view / context bound for that type (T <: Whatever o T:TypeClass), then we are talking about ad-hoc polymorphism.

Problem: Function values

There’s not such a problem when using parametric polymorphism and methods but, what about function values? In Scala, it cannot be achieved and therefore it produces some lack of expressiveness:

val monomorphic: Int => String = _.toString

val anotherMonomorphic: List[Int] => Set[Int] = 
  _.toSet

Please, notice the definition of the function that trasforms a List into a Set. It could be totally independant of the list element type, but Scala syntax doesn’t allow to define something similar. We could try asigning the method to a val (Eta expansion):

def polymorphic[T](l: List[T]): Set[T] = l.toSet

val sadlyMonomorphic = polymorphic _

But the compiler (as clever as usual) wil infer that the list contained type is Nothing: a special one, but concrete as the most.

64331666

Shapeless parametric polymorphism

How does Shapeless solve this problem? If we had to define a transformation function from Option to List in Scala, we’d have the previously mentioned limitation for using function values and we could only achieve it by defining a method:

def toList[T](o: Option[T]): List[T] =
  o.toList

However, Shapeless, using its alchemy, provides us some ways to do so. In category theory, when talking about type constructors transformations, it’s so called natural transformations. First of them has the following notation:

import shapeless.poly._

val polyFunction = new (Option ~> List){
  def apply[T](f: Option[T]): List[T] = f.toList
}

If you have a closer look at it, the parametric polymorphism is moved to the method inside the object. Using this function is as simple as:

val result: List[Int] = polyFunction(Option(2))

The other possible notation consists on defining the function behavior based on cases, in other words, if we want the function to be defined only for Int, String and Boolean, we’ll add a case for each of them.

import shapeless.Poly1

object polymorphic extends Poly1 {

  implicit optionIntCase = 
    at[Option[Int]](_.toList.map(_ + 1))

  implicit optionStringCase = 
    at[Option[String]](_.toList.map(_ + " mutated"))

  implicit optionBooleanCase = 
    at[Option[Boolean]](_.toList.map(!_))

}

As you can see, if we want our function to be defined at the case where the input parameter is an Option[Int], we define that all contained elements in the list are added to 1.

This expression returns a this.Case[Option[Int]], where this refers to polymorphic object that we are defining:

implicit optionIntCase = 
  at[Option[Int]](_.toList.map(_ + 1))

The good part of this? In case we wanted to use the funcion on a input type that doesn’t have a defined case at the function, we’ll get a compile-time error (Awesome, right?):

The result

Applying this last way (based on cases), we get the expected result that we mentioned in the introductory section: to be able to use a for-comprehension for composing different typed values: iterables, Try, Future…

The proposed solution can be found in the following file.

In our function we have a case for GenTraversable, another for Try and Future (this last one needs to have its expression evaluated for being able to iterate over it, so we need a timeout for waiting):

object values extends Poly1 {

  implicit def genTraversableCase[T,C[_]](implicit ev: C[T] => GenTraversable[T]) = 
    at[C[T]](_.toStream)

  implicit def tryCase[T,C[_]](implicit ev: C[T] => Try[T]) = 
    at[C[T]](_.toOption.toStream)

  implicit def futureCase[T,C[_]](implicit ev: C[T] => Future[T], atMost: Duration = Duration.Inf) =
    at[C[T]](f => Try(Await.result(f,atMost)).toOption.toStream)

}

Now we can use it in our controversial code snippet:

import scala.concurrent.ExecutionContext.Implicits.global

case class User(name: String, age: Int)

val result: Stream[_] = for {
  v1 <- values(List(1,2,3))
  v2 <- values(Set("hi","bye"))
  v3 <- values(Option(true))
  v4 <- values(Try(2.0))
  v5 <- values(Future(User("john",15)))
} yield (v1,v2,v3,v4,v5)

The sole solution?

At all! you can always implement it using traditional Scala type classes, though it implies defining a trait that represent the ADT iterable. You can take a look at the example at the following gist content.

Peace out!

Anuncios

Shapeless: funciones polim贸rficas

Hace ya un tiempo, nuestro amigo Javier Fuentes nos ilustr贸 con una introducci贸n a Shapeless.
Unos meseses despu茅s, en el meetup de Scala en Madrid, dio una interesante charla sobre inducci贸n estructural con Shapeless y HLists. No pudimos evitarlo y nos contagiaron el entusiasmo 馃槢

Lo que queremos hacer

Pongamos como caso de estudio lo que yo creo que a m谩s de uno le ha debido pasar: querer mezclar en la misma for-comprehension distintos tipos. Algo del estilo:

import scala.util.Try

for {
  v1 <- List(1,2,3)
  v2 <- Try("hi, person ")
} yield s"$v2 $v1"

con la consiguiente frustraci贸n que produce ver el siguiente error:

<console>:15: error: type mismatch;
 found   : scala.util.Try[String]
 required: scala.collection.GenTraversableOnce[?]
         v2 <- Try("hi, person ")
            ^

Necesitamos por tanto, una manera de transformar estos tipos de datos (Future, Try) en ‘cosas’ iterables (algo GenTraversable[T] nos podr铆a valer). En nuestro ejemplo no tendremos en cuenta la informaci贸n sobre el error si, por ejemplo, un tipo Try o un Future ha fallado e impide seguir evaluando la for-comprehension. Para entender un poco mejor el problema planteado, vamos a ver algunas pinceladas de teor铆a.

53132063

Monomorfismo vs polimorfismo

Se define un m茅todo como monom贸rfico cuando solo se puede aplicar al tipo que indican los argumentos en su signatura, mientas que los m茅todos polim贸rficos pueden aplicarse a argumentos de cualquier tipo (siempre que encajen en la signatura: en el caso de Scala, tipos parametrizados). En cristiano:

def monomorphic(parameter: Int): String

def polymorphic[T](parameter: T): String

Tipos de polimorfismo

Otra cuesti贸n importante es que un m茅todo puede ser polim贸rfico debido a los parameter types o bien por sub-tipado, por ejemplo:

def parametricallyPolymorphic[T](parameter: T): String

def subtypedPolymorphic(parameter: Animal): String

subtypedPolymorphic(new Cat)

Si usamos parameter types, y no tenemos NADA de informaci贸n sobre dichos tipos, nos encontramos ante un caso de polimorfismo param茅trico.

Si usamos parameter types pero tenemos alg煤n view / context bound sobre dicho tipo ( T <: Whatever o T:TypeClass ), entonces hablamos de polimorfismo ad-hoc.

Problema: Function values

Con los m茅todos no hay problema a la hora de usar gen茅ricos pero, 驴qu茅 ocurre con los valores que son funciones? En Scala, el polimorfismo param茅trico no puede expresarse en base a valores que son funciones:

val monomorphic: Int => String = _.toString

val anotherMonomorphic: List[Int] => Set[Int] = 
  _.toSet

N贸tese que la definici贸n de una funci贸n que pasa de List a Set es independiente del tipo de elemento que contiene la lista; pero la sintaxis de Scala no nos permite definir nada parecido. Podr铆amos intentarlo asignando a un val (eta expansion) :

def polymorphic[T](l: List[T]): Set[T] = l.toSet

val sadlyMonomorphic = polymorphic _

Pero el compilador, que es muy listo, inferir谩 que que el tipo de la lista es Nothing: un tipo peculiar, pero concreto al fin y al cabo.

64331666

Polimorfismo param茅trico en Shapeless

驴C贸mo soluciona este problema Shapeless? Si por ejemplo tuvi茅ramos que definir una funci贸n de transformaci贸n de Option a List en Scala, tendr铆amos la limitaci贸n antes citada para usar function values y s贸lo podr铆amos hacerlo definiendo un m茅todo:

def toList[T](o: Option[T]): List[T] =
  o.toList

Sin embargo Shapeless, haciendo gala de toda su alquimia, nos aporta varias formas de tener function values polim贸rficas. Es lo que en teor铆a de categor铆as, cuando hacemos referencia a transformaciones de constructores de tipos, se denomina transformaciones naturales. La primera de ellas tiene la siguiente notaci贸n:

import shapeless.poly._

val polyFunction = new (Option ~> List){
  def apply[T](f: Option[T]): List[T] = f.toList
}

Fijaros que lo que hace es trasladar el polimorfismo param茅trico a la definici贸n del objeto. Para usar posteriormente esta funci贸n basta con:

val result: List[Int] = polyFunction(Option(2))

La otra notaci贸n posible consiste en definir el comportamiento de la funci贸n en base a casos, es decir, si queremos que la funci贸n solo valga para Int, String y Boolean, a帽adir铆amos un caso para cada uno de estos tipos.

import shapeless.Poly1

object polymorphic extends Poly1 {

  implicit optionIntCase = 
    at[Option[Int]](_.toList.map(_ + 1))

  implicit optionStringCase = 
    at[Option[String]](_.toList.map(_ + " mutated"))

  implicit optionBooleanCase = 
    at[Option[Boolean]](_.toList.map(!_))

}

Como pod茅is ver, si queremos que nuestra funci贸n est茅 definida para el caso en que un argumento de entrada sea Option[Int], definimos que a todos los elementos de la lista que se devuelve, por ejemplo, se les sume 1.

Esta expresi贸n devuelve un this.Case[Option[Int]], donde this hace referencia a la funci贸n polymorphic que estamos definiendo:

implicit optionIntCase = 
  at[Option[Int]](_.toList.map(_ + 1))

驴Lo bueno de esto? Que en caso de usar la funci贸n sobre un tipo de entrada que no tiene un caso definido en la funci贸n, obtendremos un error en tiempo de compilaci贸n (Brutal, 驴no?):

El resultado

Aplicando esta 煤ltima forma de expresar funciones polim贸rficas en base a casos, obtenemos el resultado deseado que se planteaba en la introducci贸n: poder usar una for-comprehension sobre valores de distintos tipos: iterables, Try, Future…

Pod茅is ver en detalle la soluci贸n propuesta en el siguiente fichero.

En nuestra funci贸n tenemos un caso para los GenTraversable, el tipo Try y el tipo Future (en este 煤ltimo caso necesitamos disponer del valor del futuro para poder iterar sobre 茅l, de manera que nos hace falta un timeout):

object values extends Poly1 {

  implicit def genTraversableCase[T,C[_]](implicit ev: C[T] => GenTraversable[T]) = 
    at[C[T]](_.toStream)

  implicit def tryCase[T,C[_]](implicit ev: C[T] => Try[T]) = 
    at[C[T]](_.toOption.toStream)

  implicit def futureCase[T,C[_]](implicit ev: C[T] => Future[T], atMost: Duration = Duration.Inf) =
    at[C[T]](f => Try(Await.result(f,atMost)).toOption.toStream)

}

Ahora podremos utilizarlo en nuestro controvertido snippet de c贸digo:

import scala.concurrent.ExecutionContext.Implicits.global

case class User(name: String, age: Int)

val result: Stream[_] = for {
  v1 <- values(List(1,2,3))
  v2 <- values(Set("hi","bye"))
  v3 <- values(Option(true))
  v4 <- values(Try(2.0))
  v5 <- values(Future(User("john",15)))
} yield (v1,v2,v3,v4,v5)

驴脷nica soluci贸n?

隆En absoluto!, siempre se puede implementar usando type classes tradicionales de la huerta de Scala, aunque implique definir un trait que represente el iterable del ADT. Puedes ver el ejemplo en el contenido del siguiente gist.

隆Agur de lim贸n!

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!

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!

Scalera tips: How NOT to change your actor’s state

A common key when working with Akka, is modifying properly our actor’s state. If we jog our memory, this framework paradigm allows us to program modeling the concurrency based on actors and message passing. From that base, we could define an actor as a computing unit that may have state and perform tasks based on messages that it will receive through its mailbox and that will be processed sequentially .

That means that, in order to avoid side effects, the actor’s state modification has to take place when processing a message. So senseful so far. However, it’s a pretty common mistake to do something similar to this:

class MyActor extends Actor {

  var state: Int = 0

  def receive = {

    case "command" => 
      Future(state = 1)

    case "someOtherCommand" => 
      state = 2

  }

}

In this case, we have no more warranty that the state change (whose only responsible of keeping it consistent and thread safe is the actor) might cause side efects given that, in the precise moment where the Future modifies the var, it’s possible that the state is being modified by the actor itself (probably as a reaction to some other received message).

This Future[Unit] might not be a block like that. It could be the result of having asked to some other actor:

class MyActor extends Actor {

  type State = Int

  var state: State = 0

  def receive = {

    case "command" => 
      (service ? "giveMeMyNewState").map{
        case newState: State => state = newState
      }

    case "someOtherCommand" => 
      state = 2
  }

}

Something that probably none of us has ever tried.

giphy

The proper way

If we want to modify the actor’s state as result of having previously asked to some other actor and without breaking the concurrency control of the actor, it could be achieved like this:

class MyActor extends Actor {

  type State = Int

  var state: State = 0

  def receive = {

    case "command" => 
      (service ? "giveMeMyNewState") pipeTo self

    case "someOtherCommand" => 
      state = 2

    case newState: State => 
      state = newState
  }

}

With pipeTo we specify to send to certain actor the result of having evaluated some future when its resolved. This way we’re indicating that, when we get the response of the other actor, it will be sent to our mailbox, so it will be processed like a normal message, sequentially.

bill_murray_gif_1

Easy peasy 馃檪

Scalera tips: Como NO modificar el estado de tu actor

Una cuesti贸n habitual a la hora de trabajar con Akka, es modificar de manera correcta el estado de nuestro actor. Si recordamos la base del paradigma de este framework que nos permite programar modelando la concurrencia en base a actores y el paso de mensajes, es que un actor puede definirse como una unidad computacional que puede tener estado y realiza tareas en base a mensajes que recibe en su mailbox y que procesar谩 de manera secuencial.

Esto significa que, para no tener efectos de lado, es necesario que la modificaci贸n del estado del actor se haga al procesar un mensaje. Hasta aqu铆 todo tiene sentido. No obstante, es un fallo bastante com煤n el hacer algo del siguiente estilo:

class MyActor extends Actor {

  var state: Int = 0

  def receive = {

    case "command" => 
      Future(state = 1)

    case "someOtherCommand" => 
      state = 2

  }

}

En ese caso, ya no tenemos garant铆a de que el cambio de estado (cuyo 煤nico responsable de mantenerlo consistente y thread-safe es el actor) puede generar efectos de lado dado que en el momento en que futuro modifica el var es posible que el estado est茅 siendo modificado por el propio actor, desencadenado por el procesamiento de otro mensaje.

Este Future[Unit] puede no ser un bloque como tal, sino el resultado de haber preguntado a otro actor:

class MyActor extends Actor {

  type State = Int

  var state: State = 0

  def receive = {

    case "command" => 
      (service ? "giveMeMyNewState").map{
        case newState: State => state = newState
      }

    case "someOtherCommand" => 
      state = 2
  }

}

Algo que probablemente nadie de nosotros haya intentado jam谩s.

giphy

La forma correcta

En caso de querer modificar el estado del actor como resultado de dicha consulta a otro actor sin romper el control de concurrencia sobre el estado, se podr铆a hacer como sigue:

class MyActor extends Actor {

  type State = Int

  var state: State = 0

  def receive = {

    case "command" => 
      (service ? "giveMeMyNewState") pipeTo self

    case "someOtherCommand" => 
      state = 2

    case newState: State => 
      state = newState
  }

}

Con pipeTo lo que hacemos es mandar a cierto actor el resultado de evaluar un futuro cuando este se resuelva. De esta manera estamos indicando que, cuando tengamos la respuesta del otro actor, se envie a nuestro mailbox, de manera que se procesar谩 como otro mensaje m谩s, de manera secuencial.

bill_murray_gif_1

Easy peasy 馃檪

Reading the future in Scala

Today we shall explore our mystic skills to try to divine the future. To do so, we will use the type聽Future聽that Scala is giving us.

a7cae161ea16b893de40b46acadedad9b57373e6ebacfe9cd4bb57a32686517a

Wait, wait, what is the type Future?

Scala’s聽Future type is used to compute heavy operations in background so that the main execution thread remains聽unblocked. Within that Future, one or several operations will be evaluated. To do so, a parallel thread will be started.

For instance, if we woke up one day with the need to know which are the first thousand prime numbers, we would have to do something like this:


def getFirstMillionOfPrimes: List[Int] = ???

val f: Future[List[Int]] = Future{ getFirstMillionOfPrimes }

Once the result of the Future is evaluated, there are mainly two ways of getting it:

  • In a blocking way: blocking the execution until the future is completed.
  • In a non blocking way: registering a function that will be executed once the future is completed. What has been traditionally known as a callback.

Let’s see how this would be implemented.

Second class fortune teller mode

One of the options we had was to perform a blocking call on the future. This blocking call will wait until the future is completed and when so, it will return its result.

To do so, the Await.result聽call is to be used:


val f: Future[Int] = Future{
Thread.sleep(10000)
2
}

println(Await.result(f,12.seconds)) 聽//2

As can be appreciated, a timeout must be defined so that the wait is not indefinitely聽long. In case the Future exceeds this timeout when computing the result, a timeout exception will be thrown.

This way of getting the result is not recommended since by blocking the program execution, what we are doing is just what we are trying to avoid by using the type Future. However, this is used sometimes, for example, in testing.

True fortune teller mode (with official certificate)聽

So, how should we work with Futures then? Easy, with callbacks.

Callbacks will allow us to perform one or several actions once the future is solved. But here’s the magic, they will do so in a non blocking way. The callback just has to be defined and then it will be called transparently when the future is completed. In the meantime, the program will continue with its execution.

In order to process the result, the types Success and Failure will be used. This is so because a Try will be used to try to get the value of the future. We already talked about this type in another聽post, but let’s see how this would be applied to futures:

  • If the future can聽perform its duty successfully, the result will be returned encapsulated in a Success type.
  • On the contrary, if something goes wrong, the generated exception will be returned encapsulated in a Failure type.

crystal-ball-failure_300px

And how is a callback defined? Very easily:


f.onComplete( (t: Try[Int]) => println(t) )
//Success(2)

In this case, we are waiting until the future we had previously defined is completed and then, we print its result. As we have seen, that result will be encapsulated in a Try type.

Besides, we can define callbacks that will be only executed if the future goes right or wrong.


f.onSuccess( n => println(n) )
//2

f.onFailure( throwable => println(throwable.getMessage) )
//it will never print an error (because it equals Success(2))

All this callback thing is very useful. However, perhaps the most commonly used operations with Future types are transformations. By means of these transformations, we will be able to change the result of the Future without having to wait explicitly for the computed value to be obtained. Nevertheless, given the huge number聽of transformations that can be applied to a future, I think this issue deserves its own post. So, that’s all for today. See you! 馃檪