Patrón Fodable en Cats

En la programación funcional uno de los conceptos base son los tipos de datos algebráicos (ADT) Los ADT son estructuras de datos basadas en las matemáticas cuyas operaciones se realizan mediante morfismos; y, los mosfirmos, se realizan mediante la función fold y sus derivados: foldRight y foldLeft. En la entrada de hoy, Patrón Foldable en Cats, realizaré la descripción de los morfismos utilizando la type class Foldable de la librería Cats.

1.- Definición de un ADT de tipo List

Un ADT es aquel tipo de dato con el que podemos realizar unas operaciones, como por ejemplo: la operación suma y producto; y, además, cumple unas propiedades  matemáticas como pueden ser la propiedad asociativa, distributiva, o bien, de identidad.

En el siguiente ejemplo, se muestra la definición del ADT de tipo MyList, el cual equivale al ADT de tipo List.

sealed trait MyList[+A]
case object Nil extends MyList[Nothing]
case class Cons[+A](elem: A, lista: MyList[A]) extends MyList[A]

La operación suma es aquella operación que, a nivel de programación, se corresponde con las relaciones de herencia entre la clase Cons y el objeto Nil con el trait MyList. La operación producto es aquella operación que, a nivel de programación, se corresponde con los parámetros de la clase Cons: elem y lista.

Una vez definido el ADT una de las formas de manipular dicha estructura es utilizando morfismos, función fold y sus derivados. La función fold equivale a la función foldRight. La definición de la función foldRight y foldLeft con el ADT MyList son los siguientes:

  • Morfismo foldRight para el ADT MyList.
def foldRight[A, B](lista: MyList[A], elem: B)(f: (A, B) => B): B = lista match {
  case Nil => elem
  case Cons(head, tail) => f(head, foldRight(tail, elem)(f))
}
  • Morfismo foldLeft para el ADT MyList.
@annotation.tailrec
def foldLeft[A, B](lista: MyList[A], elem: B)(f: (B, A) => B): B = lista match {
  case Nil => elem
  case Cons(head, tail) => foldLeft(tail, f(elem, head))(f)
}

fold, foldRight, foldLeft

En los siguientes apartados, realizaremos la descripción de ejemplos de uso de las operaciones fold con el type class que proporciona la librería Cats y con la librería estándar.

2.- Ejemplos de morfismos con el tipo List

En el presente apartado, realizaré la descripción de ejemplos con la función fold del ADT List de la librería estándar.

  • Ejemplos básicos de morfismo foldRight.- Definición de una construcción de una lista y suma de sus elementos con una lista de tipos de enteros y foldRight.
println(s"1.- foldRight=${ List(1,2,3).foldRight(List.empty[Int])( (e, acc) => e :: acc) }")
println(s"2.- foldRight=${ List(1,2,3).foldRight(0)( (e, acc) => e + acc ) }")
println(s"Suma con foldRight=${List(1, 2, 3, 4).foldRight(0)(_ + _)}")

La salida por consola es la siguiente:

1.- foldRight=List(1, 2, 3)
2.- foldRight=6
Suma con foldRight=10
  • Ejemplos básicos de morfismo foldLeft.- Definición de una construcción de una lista y suma de sus elementos con una lista de tipos de enteros y foldLeft.
println(s"1.- foldLeft=${ List(1,2,3).foldLeft(List.empty[Int])((acc, e) => e :: acc) }")
println(s"2.- foldLeft=${ List(1,2,3).foldLeft(0)( (acc, e) => acc + e ) }")

La salida por consola es la siguiente:

1.- foldLeft=List(3, 2, 1)
2.- foldLeft=6
  • FoldRight y el tipo Numeric.- Definición de una función suma empleando una lista de enteros y el tipo Numeric.
import scala.math.Numeric
def sumaConNumeric[A](list:List[A])(implicit numeric: Numeric[A]): A =
list.foldRight(numeric.zero)(numeric.plus)
println(s"Suma con Numeric=${sumaConNumeric(List(1, 2, 3, 4))}")
println

La salida por consola es la siguiente:

Suma con Numeric=10
  • FoldRight y monoides.- Definición de la operación suma sobre una lista de enteros empleando monoides.
import cats.Monoid
import cats.instances.int._ // for Monoid
def sumaConMonoid[A](list:List[A])(implicit monoid: Monoid[A]): A =
list.foldRight(monoid.empty)(monoid.combine)
println(s"Suma con Momoid=${sumaConMonoid(List(1, 2, 3, 4))}")

La salida por consola es la siguiente:

Suma con Momoid=10
  • FoldRight y definición de filtros.- Definición de unos filtros sobre una lista de enteros
val elemFilter1: Int = 3
println(s"List(1, 2, 3, 4) existe el 3?=${List(1, 2, 3, 4).foldRight(false)( (elem, resul) => resul || elem.equals(elemFilter1))}")
val elemFilter2: Int = 5
println(s"List(1, 2, 3, 4) existe el 5?=${List(1, 2, 3, 4).foldRight(false)( (elem, resul) => resul || elem.equals(elemFilter2))}")
def myfilter[A](list: List[A])(func: A => Boolean): List[A] =
list.foldRight(List.empty[A]) { (item, accum) => if(func(item)) item :: accum else accum }
println(s"List(1, 2, 3, 4) filtra los pares.=${ myfilter(List(1, 2, 3, 4))(_%2==0) }")

La salida por consola es la siguiente:

List(1, 2, 3, 4) existe el 3?=true
List(1, 2, 3, 4) existe el 5?=false
List(1, 2, 3, 4) filtra los pares.=List(2, 4)
  • FoldRight y definición de función map.- Definición de una función map con foldRight.
def myMap[A,B](list: List[A])(f: A => B): List[B] = list.foldRight(List.empty[B])( (elem, result) => f(elem) :: result )
println(s"List(1, 2, 3) map to String=${List(1, 2, 3).foldRight(List.empty[String])( (elem, resul) => s"-${elem.toString}-" :: resul)}")
println(s"List(1, 2, 3) map to String=${ myMap(List(1, 2, 3))( (elem:Int) => s"*${elem.toString}*" ) }")
println

La salida pos consola es la siguiente:

List(1, 2, 3) map to String=List(-1-, -2-, -3-)
List(1, 2, 3) map to String=List(*1*, *2*, *3*)
  • FoldRight y definición de función flatMap.- Definición de una función flatMap con foldRight.
def flatMap[A, B](list: List[A])(func: A => List[B]): List[B] =
list.foldRight(List.empty[B]) { (item, accum) => func(item) ::: accum }
println(s"-->>${flatMap(List(1, 2, 3))(a => List(a, a * 10, a * 100))}")
println

La salida por consola es la siguiente:

-->>List(1, 10, 100, 2, 20, 200, 3, 30, 300)

3.- Ejemplos con Foldable de cats.

Para poder operar con el tipo Foldable es necesario, al menos, realizar la importación de los siguientes tipos:

import cats.Foldable
import cats.instances.all._
  • Ejemplo de Foldable con función foldLeft con los tipos List, Vector, Stream y Option.
println(s"Suma List(1, 2, 3)=${Foldable[List].foldLeft(List(1, 2, 3), 0)( _ + _ )}")
println(s"Suma Vector(1, 2, 3)=${Foldable[Vector].foldLeft(Vector(1, 2, 3), 0)( _ + _ )}")
println(s"Suma Stream(1, 2, 3)=${Foldable[Stream].foldLeft(Stream(1, 2, 3), 0)( _ + _ )}")
println(s"Suma Option(10) + 5=${Foldable[Option].foldLeft(Option(10), 0)( (acc, elem) => elem + 5 )}")

La salida por consola es la siguiente:

Suma List(1, 2, 3)=6
Suma Vector(1, 2, 3)=6
Suma Stream(1, 2, 3)=6
Suma Option(10) + 5=15
  • StackOverflowError con función foldRight.

Supongamos que queramos realizar la suma de una estructura de tipo Stream de 100000 elementos, la definición de la solución sería la siguiente:

val lista = (1 to 100000).toStream
println(s"Suma (1 to 100000).toStream->${ lista.foldRight(0L)(_ + _) }")

El resultado de la ejecución del snippet anterior es errónea porque se produce un desbordamiento de la pila del sistema y nos aparece en consola un error de tipo StackOverflowError. Una solución a este problema utilizando la función foldRight es utilizando la mónada Eval. Si el lector está interesado en la mónada Eval, pude ir a las siguientes enlace.El snippet es el siguiente:

import cats.Eval
val resultEvalStream = Foldable[Stream].foldRight(lista, Eval.now(0L)) ((num, acc) => acc.map( _ + num))
println(s"Suma (1 to 100000).toStream->${ resultEvalStream.value }")

La salida por consola es la siguiente:

Suma (1 to 100000).toStream->5000050000
  • Ejemplos de funciones básicos de Foldable con tipo Option.
println(s"Foldable[Option].nonEmpty(Option(42))=${Foldable[Option].nonEmpty(Option(42))}" )
println(s"Foldable[Option].isEmpty(Option(42))=${Foldable[Option].isEmpty(Option(42))}" )
println(s"Foldable[Option].size(Option(42))=${Foldable[Option].size(Option(42))}" )
println(s"Foldable[Option].get(Option(42))(0)=${Foldable[Option].get(Option(42))(0) }" )
println(s"Foldable[Option].find(Option(42))( elem => elem>30)=${Foldable[Option].find(Option(42))( elem => elem>30) }" )
println

La salida por consola es la siguiente:

Foldable[Option].nonEmpty(Option(42))=true
Foldable[Option].isEmpty(Option(42))=false
Foldable[Option].size(Option(42))=1
Foldable[Option].get(Option(42))(0)=Some(42)
Foldable[Option].find(Option(42))( elem => elem>30)=Some(42)
  • Ejemplos de funciones básicas de Foldable con tipo List.
println(s"Foldable[Option].nonEmpty(List(1, 2, 3)=${Foldable[List].nonEmpty(List(1, 2, 3))}" )
println(s"Foldable[Option].isEmpty(List(1, 2, 3))=${Foldable[List].isEmpty(List(1, 2, 3))}" )
println(s"Foldable[Option].size(List(1, 2, 3)=${Foldable[List].size(List(1, 2, 3))}" )
println(s"Foldable[Option].get(List(1, 2, 3)(0)=${Foldable[List].get(List(1, 2, 3))(0)}" )
println(s"Foldable[Option].get(List(1, 2, 3)(1)=${Foldable[List].get(List(1, 2, 3))(1)}" )
println(s"Foldable[Option].get(List(1, 2, 3)(4)=${Foldable[List].get(List(1, 2, 3))(4)}" )
println(s"Foldable[Option].find(List(1, 2, 3)(4)=${Foldable[List].find(List(1, 2, 3))( elem => (elem%2==0) )}" )
println(s"Foldable[Option].find(List(1, 2, 3)(4)=${Foldable[List].find(List(1, 2, 3))( elem => (elem%2!=0) )}" )
println

La salida por consola es la siguiente:

Foldable[Option].nonEmpty(List(1, 2, 3)=true
Foldable[Option].isEmpty(List(1, 2, 3))=false
Foldable[Option].size(List(1, 2, 3)=3
Foldable[Option].get(List(1, 2, 3)(0)=Some(1)
Foldable[Option].get(List(1, 2, 3)(1)=Some(2)
Foldable[Option].get(List(1, 2, 3)(4)=None
Foldable[Option].find(List(1, 2, 3)(4)=Some(2)
Foldable[Option].find(List(1, 2, 3)(4)=Some(1)
  • Ejemplo de Foldable con monoides. El tipo Foldable define operaciones con monoides.
import cats.instances.all._
println(s"Foldable[Option].combineAll(List(1, 2, 3))=${Foldable[List].combineAll(List(1, 2, 3))}" )
println

La salida por consola es la siguiente:

Foldable[Option].combineAll(List(1, 2, 3))=6
  • Ejemplo de Foldable con función map. El tipo Foldable define la función foldMap para definir funciones con la funcionalidad de fold y la función map.
import cats.instances.all._
println(s"Foldable[List].foldMap(List(1, 2, 3))( elem => elem + 20) =${Foldable[List].foldMap(List(1, 2, 3))( elem => elem + 20) }")
println

La salida por consola es la siguiente:

Foldable[List].foldMap(List(1, 2, 3))( elem => elem + 20) =66

El entendimiento y el uso de los  morfismos facilita y simplifica el código; y, la utilización de Foldable, permite una versatilidad para cualquier operación.

Scala Future con Ejemplos, continuación

En la entrada anterior, «Scala Future con Ejemplos», realicé una descripción de cómo utilizar la entidad Future en Scala con ejemplos. En la presente entrada, «Scala Future con Ejemplos, continuación», realizaré una ampliación de Future y, además, describiré ejemplos de utilización de Future con Actores.

El modelo de actores, segun wikepedia, es un modelo matemático de computación simultánea que trata a los actores como los primitivos universales de la computación concurrete. La implementación que utilizaremos es la que proporciona Akka y voy a suponer que el lector tiene unos conocimientos mínimos del modelo de actores y de Akka. La definición de dependencia de la librería Akka en sbt es la siguiente:

libraryDependencies += "com.typesafe.akka" %% "akka-actor" % akkaVersion,

Para la realización de los siguientes ejemplos, es necesario definir un actor con una mínima funcionalidad. La funcionalidad es la siguiente: si el actor recibe un mensaje de tipo String (msg), el actor responde con la concatenación del contenido de msg con el texto «Recibido en Actor»; si el actor recibe cualquier otro mensaje, el actor responde con el mensaje «Hola Mundo.» La implementación del actor es la siguiente:

import akka.actor.{Actor, Props}
import scala.concurrent.Future
object ActorEjemplo {
  def props() = Props(new ActorEjemplo())
}
class ActorEjemplo extends Actor {
  import context.dispatcher
  def receive = {
    case msg:String =>{
      val respuesta = msg + " Recibido en Actor"
      println(respuesta)
      sender() ! respuesta
    }
    case _ => {
      val respuesta = "Hola Mundo"
      println(respuesta)
      sender() ! respuesta
    }
  }
}

La lista de ejemplos que se muestran son los siguientes:

  • Ejemplo 1: ejemplo de Futures con tratamiento funcional
  • Ejemplo 2: ejemplo de Futures con tratamiento funcional
  • Ejemplo 3: ejemplo de Futures con tratamiento funcional.
  • Ejemplo 4: ejecución de un Actor con tiempo de espera.
  • Ejemplo 5: ejecución de un Actor sin tiempo de espera.
  • Ejemplo 6: composición de Futures.
  • Ejemplo 7: composición de Furures con for comprehension.
  • Ejemplo 8: conversión de List[Future[A]] -> Future[List[A]]
  • Ejemplo 9: conversión de Future[List[A]] -> List[Future[A]]
  • Ejemplo 10: morfismos con Future, función fold.
  • Ejemplo 11: morfismos con Future, función reduce
  • Ejemplo 12: el primero que termine, se ejecuta; función firstCompletedOf

Ejemplo 1: ejemplo de Futures con tratamiento funcional

Sea un Future future1 cuyo resultado sea el texto «Hello world!»; sea el Future future2 cuyo resultado es el valor entero 3; y, por último, sea el Future future3 que define una secuencia de operaciones con futuro1 y future2 para realizar un cálculo numérico a partir de la ejecución secuencial de future1 y future2.

El snippet del código con las ejecución de las tres futuros  la solución es la siguiente:

def ejemplo(): Unit = {
  val future1 = Future {
    "Hello world!"
  }
  val future2 = Future.successful(3)
  val future3 = future1 map { elemf1 =>
    future2 map { elemf2 =>
      elemf1.length * elemf2
    }
  }
  future3 onComplete {
    case Success(resultado) => println(s"resultado ejemplo3=${resultado}")
    case Failure(error) => println(s"error ejemplo3=${error}")
  }
}

La salida por consola de la ejecución del código anterior es la siguiente:

resultado ejemplo3=Success(36)

Ejemplo 2: ejemplo de Futures con tratamiento funcional

El siguiente ejemplo es el mismo que el caso anterior pero empleando la función foreach.

def ejemplo(): Unit = {
  val future1 = Future {
    "Hello world!"
  }
  val future2 = Future.successful(3)
  val future3 = future1 flatMap { elemf1 =>
    future2 map { elemf2 =>
      elemf1.length * elemf2
    }
  }
  future3 foreach { elem => println(s"Resultado ejemplo4=${elem}") }
}

La salida por consola de la ejecución del código anterior es la siguiente:

Resultado ejemplo4=36

Ejemplo 3: ejemplo de Futures con tratamiento funcional

Sean tres Futures cuyo resultado individual son tres operaciones matemáticas simples y, el último Future, define un filtro. El resultado será la multiplicación del segundo por el tercero.

El snippet del código con la solución es la siguiente:

def ejemplo5(): Unit = {
  val resultado = for {
    a <- Future(10 / 2)
    b <- Future(a + 1)
    c <- Future(a - 1)
    if c > 3
  } yield {
    b * c
  }
  resultado foreach { elem => println(s"Resultado ejemplo5=${elem}") }
}

La salida por consola de la ejecución del código anterior es la siguiente:

Resultado ejemplo5=24

Ejemplo 4: ejecución de un Actor con tiempo de espera

El ejemplo siguiente realizará la creación de un actor, envío de un mensaje y el tratamiento de la respuesta del actor con un tiempo de espera; para finalizar, se realizará la eliminación del actor del ejemplo enviándole el mensaje «PoisonPill». La definición del actor es la definida al principio de la entrada y el sistema de actores es el identificado con el identificador «Ejem».

El código del ejemplo es el siguiente:

import scala.concurrent._
import ExecutionContext.Implicits.global
implicit val system = ActorSystem("Ejem")
implicit val timeout = Timeout(2 seconds)
def ejemplo1(): Unit = {
  val actorEjemplo1 = system.actorOf(ActorEjemplo.props(), "ActorEjemplo1")
  val future: Future[Any] = actorEjemplo1 ? "Mensaje"
  val result: String = Await.result(future, timeout.duration).asInstanceOf[String]
  println(s"\nresultado ejemplo1=${result}")
  actorEjemplo1 ! PoisonPill
}

La salida por consola de la ejecución del código anterior es la siguiente:

Mensaje Recibido en Actor
resultado ejemplo1=Mensaje Recibido en Actor

La creación del actor se realiza con la función actorOf del sistema de actores identificado  como system; el envío de un mensaje a un actor se utiliza la función «?»; y, su resultado, es gestionado por el componente Await el cual opera con la respuesta de tipos Future que retorna el actor.

Ejemplo 5: ejecución de un Actor sin tiempo de espera

El siguiente ejemplo es idéntico que el anterior pero se espera la respuesta del actor de tipo Future sin tiempo de espera. La lógica es la misma que el ejemplo anterior pero se emplea la función onComplete para gestionar el resultado de ejecución. El código del ejemplo es el siguiente:

def ejemplo2(): Unit = {
  val actorEjemplo2 = system.actorOf(ActorEjemplo.props(), "ActorEjemplo2")
  // La función mapTo retorna un nuevo Future con la respuesta si es Success; en otro caso, ClassCastException.
  val future: Future[String] = ask(actorEjemplo2, "mensaje").mapTo[String]
  future onComplete {
    case Success(resultado) => println(s"\nresultado ejemplo2=${resultado}\n")
    case Failure(error) => println(s"\nerror ejemplo2=${error}\n")
  }
  actorEjemplo2 ! PoisonPill
}

Ejemplo 6: composición de Futures.

Supongamos que necesitamos ejecutar dos futuros y, su resultado, es la entrada de un tercer futuro. Una posible solución con un tiempo de espera determinado es la siguiente:

def ejemplo6(): Unit = {
  val actorEjemplo6_1 = system.actorOf(ActorEjemplo.props(), "ActorEjemplo6-1")
  val actorEjemplo6_2 = system.actorOf(ActorEjemplo.props(), "ActorEjemplo6-2")
  val actorEjemplo6_3 = system.actorOf(ActorEjemplo.props(), "ActorEjemplo6-3")

  val future1 = actorEjemplo6_1 ask ("Mensaje a Actor6_1")
  val future2 = actorEjemplo6_2 ask ("Mensaje a Actor6_2")

  val respuestaFuture1 = Await.result(future1, 3 seconds).asInstanceOf[String]
  val respuestaFuture2 = Await.result(future2, 3 seconds).asInstanceOf[String]
  val future3 = actorEjemplo6_3 ask ("##" + respuestaFuture1 + "&" + respuestaFuture2 + "##")
  val respuestaFuture3 = Await.result(future3, 3 seconds).asInstanceOf[String]
  println(s"resultado ejemplo6=${respuestaFuture3}")
}

La salida por consola de la ejecución del código anterior es la siguiente:

Mensaje a Actor6_1 Recibido en Actor
Mensaje a Actor6_2 Recibido en Actor
##Mensaje a Actor6_1 Recibido en Actor&Mensaje a Actor6_2 Recibido en Actor## Recibido en Actor
resultado ejemplo6=##Mensaje a Actor6_1 Recibido en Actor&Mensaje a Actor6_2 Recibido en Actor## Recibido en Actor

Ejemplo 7: composición de Furures con for comprehension

El siguiente ejemplo es el mismo que el caso anterior pero se emplea for comprehensión.

def ejemplo7(): Unit = {
  val actorEjemplo7_1 = system.actorOf(ActorEjemplo.props(), "ActorEjemplo7-1")
  val actorEjemplo7_2 = system.actorOf(ActorEjemplo.props(), "ActorEjemplo7-2")
  val actorEjemplo7_3 = system.actorOf(ActorEjemplo.props(), "ActorEjemplo7-3")
  val future1 = actorEjemplo7_1 ask ("Mensaje a Actor7_1")
  val future2 = actorEjemplo7_2 ask ("Mensaje a Actor7_2")
  val future3 = for {
    f1 <- future1.mapTo[String]
    f2 <- future2.mapTo[String]
    c <- ask(actorEjemplo7_3, "##" + f1 + "&" + f2 + "##").mapTo[String]
  } yield {
    c
  }
  future3 foreach { resultado => println(s"Resultado ejemplo7=${resultado}") }
}

La salida por consola de la ejecución del código anterior es la siguiente:

Mensaje a Actor7_2 Recibido en Actor
Mensaje a Actor7_1 Recibido en Actor
##Mensaje a Actor7_1 Recibido en Actor&Mensaje a Actor7_2 Recibido en Actor## Recibido en Actor
Resultado ejemplo7=##Mensaje a Actor7_1 Recibido en Actor&Mensaje a Actor7_2 Recibido en Actor## Recibido en Actor

Ejemplo 8: conversión de List[Future[A]] -> Future[List[A]]

Supongamos que tenemos una lista de operaciones Futuras del mismo tipo, en nuestro caso, Futuros cuya respuesta son números enteros; y, su procesamiento, puede ser transformado un un único Future. La solución consiste en emplear la función sequence la cual es la encargada de transformar muchos Future en uno único. Así, la solución se define de la siguiente forma:

def ejemplo8(): Unit = {
  val listaFutures: List[Future[Int]] = List(Future(1), Future(3), Future(5), Future(7))
  // Convertimos un List[Future[Int]] en Future[List[Int]] para trabajar con el Future.
  // Así, podemos trabajar con la List[Int]
  val futureList: Future[List[Int]] = Future.sequence(listaFutures)
  println(s"futureList=${futureList}")
  val suma: Future[Int] = futureList.map(_.sum)
  suma foreach{elem => println(s"Resultado ejemplo8=${suma}")}
}

La salida por consola de la ejecución del código anterior es la siguiente:

Resultado ejemplo8=Success(16)

Ejemplo 9: conversión de Future[List[A]] -> List[Future[A]]

Supongamos que tenemos un único Future y una lista de datos de un determinado tipo  que procesar y, su procesamiento, puede ser ejecutado por N Futuros. La solución consiste en emplear la función traverse. El código del ejemplo es el siguiente:

def ejemplo9(): Unit = {
  // Convertimos un Future[List[Int]] en List[Future[Int]] para trabajar con el Future.
  val futureList1 = Future.traverse((1 to 10).toList)(x => Future(x * 2 - 1))
  val resultadoFutureList1 = futureList1.map(_.sum)
  resultadoFutureList1 foreach {elem => println(s"Resultado1 de ejemplo9=${elem}")}
  val futureList2 = Future.traverse(List(1,3,5))(x => Future(x * 2 - 1))
  val resultadoFutureList2 = futureList2.map(_.sum)
  resultadoFutureList2 foreach {elem => println(s"Resultado2 de ejemplo9=${elem}")}
}

La salida por consola de la ejecución del código anterior es la siguiente:

Resultado2 de ejemplo9=15
Resultado1 de ejemplo9=100

Ejemplo 10: morfismos con Future, función fold

Supongamos que tenemos un ADT de tipo lista de futuros no bloqueantes y queremos realizar un morfismo sobre dicho ADT; el mecanismos para realizarlo, es utilizar la función fold. El siguiente snippet muestra un ejemplo representativo:

def ejemplo10(): Unit = {
  val listaFutures: List[Future[Int]] = List(Future(1), Future(3), Future(5), Future(7))
  val sumaListaFutures = Future.fold(listaFutures)(0)(_+_)
  sumaListaFutures onComplete{
    case Success(resultado) => println(s"Resultado ejemplo10=${resultado}")
    case Failure(error) => println(s"Error ejemplo10=${error}")
  }
}

Ejemplo 11: morfismos con Future, función reduce

La función reduce es como la función fold pero sin valor inicial.

def ejemplo11(): Unit = {
  val listaFutures: List[Future[Int]] = List(Future(1), Future(3), Future(5), Future(7))
  val futureSum: Future[Int] = Future.reduce(listaFutures)(_ + _)
  futureSum foreach { elem => println(s"Resultado ejemplo11=${futureSum}") }
}

La salida por consola de la ejecución del código anterior es la siguiente:

Resultado ejemplo11=Success(16)

Ejemplo 12: el primero que termine, se ejecuta; función firstCompletedOf

Supongamos que tenemos una lista de futuros en un ADT de tipo lista; y, a nivel de negocio, solo nos interesa el resultado del primer future que termina; en estos casos,  se emplea la función firstCompletedOf. El siguiente ejemplo muestra un ejemplo representativo.

def ejemplo12(): Unit = {
  val listaFutures: List[Future[Int]] = List(Future(1), Future(3), Future(5), Future(7))
  val futureFirst: Future[Int] = Future.firstCompletedOf(listaFutures)
  futureFirst foreach { elem => println(s"Resultado ejemplo12=${futureFirst} elem=${elem}") }
}

Una de las posibles salidas por consola de la ejecución del código anterior es la siguiente:

Resultado ejemplo12=Success(3) elem=3

Estos son los ejemplos que presento, si al lector interesado se le ocurre plantear otro ejemplo, o bien, plantear cualquier otra alternativa, estaré encantado de compartirlo.