|
|
Ya demostramos la sintaxis de map y flatMap de Cats al escribir un método que abstraía sobre diferentes mónadas:
import cats.Monad
import cats.syntax.functor._ // for map
import cats.syntax.flatMap._ // for flatMap
def sumSquare[F[_]: Monad](a: F[Int], b: F[Int]): F[Int] =
for {
x <- a
y <- b
} yield x*x + y*y
La sintaxis de las mónadas proviene de tres lugares:
Cats proporciona instancias para todas las mónadas en la biblioteca estándar (Option, List, Vector, etc.) a través de cats.instances:
import cats.instances.option._ // for Monad
Monad[Option].flatMap(Option(1))(a => Option(a*2))
// res0: Option[Int] = Some(2)
import cats.instances.list._ // for Monad
Monad[List].flatMap(List(1, 2, 3))(a => List(a, a*10))
// res1: List[Int] = List(1, 10, 2, 20, 3, 30)
import cats.instances.vector._ // for Monad
Monad[Vector].flatMap(Vector(1, 2, 3))(a => Vector(a, a*10))
// res2: Vector[Int] = Vector(1, 10, 2, 20, 3, 30)
La clase de tipo de mónada es cats.Monad. Monad extiende otras dos clases de tipos:
FlatMap, que proporciona el método flatMap, y Applicative, que proporciona pure. Applicative también extiende Functor, que le da a cada Monad un método de map.
Aquí hay algunos ejemplos que usan pure y flatMap, y map:
import cats.Monad
import cats.instances.option._ // for Monad
import cats.instances.list._ // for Monad
val opt1 = Monad[Option].pure(3)
// opt1: Option[Int] = Some(3)
val opt2 = Monad[Option].flatMap(opt1)(a => Some(a + 2))
// opt2: Option[Int] = Some(5)
val opt3 = Monad[Option].map(opt2)(a => 100 * a)
// opt3: Option[Int] = Some(500)
val list1 = Monad[List].pure(3)
// list1: List[Int] = List(3)
val list2 = Monad[List].flatMap(List(1, 2, 3))(a => List(a, a*10))
Si bien solo hemos hablado de flatMap anteriormente, el comportamiento monádico se captura formalmente en dos operaciones:
La primera operación proporciona una forma de crear un nuevo contexto monádico a partir de un valor simple. flatMap proporciona el paso de secuenciación que ya hemos discutido, extrayendo el valor de un contexto y generando el siguiente contexto en la secuencia. Aquí hay una versión simplificada de la clase de tipos Monad en Cats:
trait Monad[F[_]] {
def pure[A](value: A): F[A]
def flatMap[A, B](value: F[A])(func: A => F[B]): F[B]
}
pure y flatMap deben obedecer un conjunto de leyes que nos permiten secuenciar operaciones libremente sin problemas técnicos ni efectos secundarios no deseados:
Identidad izquierda: llamar a pure y a flatMap con func es lo mismo que llamar a func:
pure(a).flatMap(func) == func(a)
JDK |
PROVIDER |
AVAILABLE VERSIONS |
MISCELLANEOUS |
---|---|---|---|
Oracle |
Builds for different Java projects under development:
|
||
Eclipse |
|
|
|
Alibaba |
According to their own words: Optimized for online e-commerce, financial, logistics applications running on 100,000+ servers
|
||
Amazon |
|
||
Azul |
|
|
|
BellSoft |
|
||
|
|
||
SAP |
|
Future es una mónada que secuencia los cálculos sin preocuparse de que puedan ser asíncronos:
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
def doSomethingLongRunning: Future[Int] = ???
def doSomethingElseLongRunning: Future[Int] = ???
def doSomethingVeryLongRunning: Future[Int] =
for {
result1 <- doSomethingLongRunning
result2 <- doSomethingElseLongRunning
} yield result1 + result2
Me llego este mail y quiero compartir :
Monitoring and managing API traffic can be a complicated undertaking. Most companies today must deal with securing API traffic from outside their organizational boundaries as well as optimizing traffic between services running behind the company firewall. In this ebook, Mike Amundsen introduces the basic concepts and challenges of monitoring and managing API traffic, and examines how good traffic monitoring, reporting, and analysis can help achieve business goals. Download the ebook and learn how to:
|
|
Cuando nos encontramos por primera vez con flatMap como desarrolladores en ciernes de Scala, tendemos a pensar en él como un patrón para iterar sobre Listas. Esto se ve reforzado por la sintaxis de las comprensiones for, que se parecen mucho a los bucles for imperativos:
for {
x <- (1 to 3).toList
y <- (4 to 5).toList
} yield (x, y)
// res5: List[(Int, Int)] = List(
// (1, 4),
// (1, 5),
// (2, 4),
// (2, 5),
// (3, 4),
// (3, 5)
// )
Pero que es una monada? Esta es la pregunta que se ha planteado en mil publicaciones de blog, con explicaciones y analogías que involucran conceptos tan diversos como gatos, comida mexicana, trajes espaciales llenos de desechos tóxicos y monoides en la categoría de endofuntores (lo que sea que eso signifique). Vamos a resolver el problema de explicar las mónadas de una vez por todas afirmando de manera muy simple: una mónada es un mecanismo para secuenciar cálculos. ¡Eso fue fácil! Problema resuelto, ¿verdad? Pero, dijimos que los funtores eran un mecanismo de control para exactamente lo mismo. Ok, tal vez necesitemos más discusión...
Los funtores nos permiten secuenciar cálculos ignorando alguna complicación. Sin embargo, los funtores están limitados porque solo permiten que esta complicación ocurra una vez al comienzo de la secuencia. No tienen en cuenta más complicaciones en cada paso de la secuencia.
Aquí es donde entran las mónadas. El método flatMap de una mónada nos permite especificar qué sucede a continuación, teniendo en cuenta una complicación intermedia. El método flatMap de Option tiene en cuenta las opciones intermedias. El método flatMap de List maneja listas intermedias. Y así. En cada caso, la función que se pasa a flatMap especifica la parte específica de la aplicación del cómputo, y flatMap se encarga de la complicación permitiéndonos flatMap nuevamente. Aterricemos las cosas mirando algunos ejemplos.
Option nos permite secuenciar cálculos que pueden o no devolver valores. Aquí hay unos ejemplos:
def parseInt(str: String): Option[Int] = scala.util.Try(str.toInt).toOption
def divide(a: Int, b: Int): Option[Int] = if(b == 0) None else Some(a / b)
Cada uno de estos métodos puede "fallar" al devolver None. El método flatMap nos permite ignorar esto cuando secuenciamos operaciones:
def stringDivideBy(aStr: String, bStr: String): Option[Int] =
parseInt(aStr).flatMap { aNum =>
parseInt(bStr).flatMap { bNum =>
divide(aNum, bNum)
}
}
La semántica es:
En cada paso, flatMap elige si llamar a nuestra función, y nuestra función genera el siguiente cálculo en la secuencia.
El resultado del cálculo es una Option, que nos permite volver a llamar a flatMap y así continúa la secuencia. Esto da como resultado el comportamiento de manejo de errores rápido que conocemos y amamos, donde un None en cualquier paso da como resultado un None en general:
Currying es una técnica de transformación de una función con múltiples argumentos de tal manera que se puede llamar como una cadena de funciones cada uno con un único argumento.
Como puedo hacer esto en kotlin? la respuesta es kotlin no soporta currying. Y ya esta :(
Pero aún podemos crear una función de argumento único que devuelva otra función de argumento único.
Por ejemplo :
val curriedAdd: (Int) -> (Int) -> Int = { x -> { y -> x + y } }
val add2 : (Int) -> Int = curriedAdd(2)
println(add2(3)) //imprime 5
Como vemos, la primera invocación de curriedAdd devuelve otra función con un parámetro. Lo que hicimos arriba se llama aplicación parcial. Aplicamos solo el primer argumento y preparamos la función para aplicar el segundo.
Eso es básicamente lo que queríamos lograr. Nuestro código puede ser puro, funcional y fácil de probar.
Además, Kotlin no admite curry tal como es. No podemos aplicar solo un argumento a cualquier función. Tenemos que hacer la función con aplicaciones parciales. Pero escribir todas las lambdas como una secuencia de lambdas puede ser molesto.
Y esto es lo que la comunidad hizo por nosotros: una gran biblioteca funcional para Kotlin: funKTionale. El módulo donde viene curry y uncurry es funktionale-currying.
Volviendo a nuestro problema: ¿cómo crear funciones add3, add5, add100 sin generar una gran cantidad de código repetitivo?
Primero, agregue la biblioteca a su script de compilación. En mi caso es gradle:
implementation "org.funktionale:funktionale-currying:1.2"
Y luego podemos hacer :
val add = { x: Int, y: Int -> x + y}.curried()
val add3 = add(3)
val add5 = add(5)
val add100 = add(100)
println(add3(3)) // 6
println(add100(-10)) // 90
No me gusto, pero le voy a dar tiempo. Por muchas cosas no me gusto.
1. La falta de poder personalizarlo, tuve que instalar un software para lograr mejor personalización de la apariencia.
2. Problemas de instalación que no he tenido en otras distros, tener que por comando agregar un extra para encontrar software.
3. Falta de organización del software...
Dejo unas pantallas :
En fin, tal vez no estoy siendo muy parcial. Pero le voy a dar tiempo...
Hay situaciones en las que la eliminación de izquierda a derecha no es la opción correcta. Un ejemplo es el tipo O en Scalactic, que es un equivalente convencionalmente sesgado a la izquierda de O bien:
type PossibleResult = ActualResult Or Error
Bueno, porque es realmente útil, solo importando cats.syntax.all._ tiene acceso a un montón de métodos de extensión que resuelven múltiples problemas comunes en una o un par de líneas; por ejemplo:
import cats.syntax.all._
// Combinar 2 mapas
m1 |+| m2
// Sumar el largo de todos los string de una lista
strings.foldMap(str => str.length)
// Aplicar una función, que devuelve una opción, a todos los elementos de una lista
// y devuelve el primero que se define.
ints.foldMapK(foo)
// Aplicar una función, que devuelve una opción, a todos los elementos de una lista
// y volver a recopilar todos los resultados si todos eran Some
ints.traverse(bar)
// Aplicar múltiples validaciones independientes
// y usamos los resultados para crear un type class,
// de lo contrario, acumula todos los errores en una colección no vacía.
(
validationA(x).toEitherNec,
validationB(y).toEitherNec,
validationC(z).toEitherNec
).parMapN(Data.apply)
// Une dos listas de diferentes tamaños:
l1.aligWith(l2) {
case Ior.both(a, b) => ???
case Ior.left(a) => ???
case Ior.right(b) => ???
}
Lo bueno de eso es que esas operaciones son muy generales, por lo que funcionan con múltiples tipos; también suelen componer, por lo que puedes usar las mismas operaciones para tipos más complejos.
Ahora, uno puede argumentar que puede implementarlos usando las funciones stdlib y generalmente puede hacerlo, de la misma manera que puede implementar stdlib usted mismo; el hecho de que esas operaciones ya existan es bueno.
Bueno, también puedes ver a cats como una biblioteca fundamental para construir aún más abstracciones, eso es básicamente lo que hacen bibliotecas como cats-effect y fs2; amplían las abstracciones de cats, además de otras cosas.
Entonces, puede escribir su propia función auxiliar que funcione para cualquier Mónada y estar seguro de que debería funcionar debido a las leyes y todo eso.
Pero, para ser justos, rara vez harás eso, así que no te preocupes demasiado por eso. La mayoría del uso de cats es simplemente importar cats.syntax.all._ y disfrutar de todos los métodos de extensión útiles, o usar estructuras de datos como cats.data.NonEmptychain
En conclusión, cats implementa un framework conceptual para la Programación Funcional, para clasificar combinadores básicos y derivados en una jerarquía de complejidad.
Dejo link : https://typelevel.org/cats/
Vimos una instancia de functor para Function1.
import cats.Functor
import cats.instances.function._ // for Functor
import cats.syntax.functor._
// for map
val func1 = (x: Int) => x.toDouble
val func2 = (y: Double) => y * 2
val func3 = func1.map(func2)
// func3: Int => Double = scala.Function1$$Lambda$7919/0