Translate

domingo, 22 de mayo de 2022

Mónadas en Cats parte 7

 La sintaxis de las mónadas proviene de tres lugares:

  • cats.syntax.flatMap proporciona sintaxis para flatMap;
  • cats.syntax.functor proporciona sintaxis para el map;
  • cats.syntax.applicative proporciona sintaxis para pure.
En la práctica, a menudo es más fácil importar todo de una sola vez desde cats.implicits. Sin embargo, usaremos las importaciones individuales aquí para mayor claridad.

Podemos usar pure para construir instancias de una mónada. A menudo necesitaremos especificar el parámetro de tipo para eliminar la ambigüedad de la instancia particular que queremos.

import cats.instances.option._  // for Monad
import cats.instances.list._ // for Monad
import cats.syntax.applicative._ // for pure

1.pure[Option]
// res5: Option[Int] = Some(1)
1.pure[List]
// res6: List[Int] = List(1)

Es difícil demostrar los métodos flatMap y map directamente en las mónadas de Scala como Option y List, porque definen sus propias versiones explícitas de esos métodos. En su lugar, escribiremos una función genérica que realice un cálculo en los parámetros que vienen envueltos en una mónada a elección del usuario:

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] = a.flatMap(x => b.map(y => x*x + y*y))

import cats.instances.option._ // for Monad
import cats.instances.list._  // for Monad

sumSquare(Option(3), Option(4))
// res7: Option[Int] = Some(25)

sumSquare(List(1, 2, 3), List(4, 5))
// res8: List[Int] = List(17, 26, 20, 29, 25, 34)

Podemos reescribir este código usando comprensiones. El compilador "hará lo correcto" reescribiendo nuestra comprensión en términos de flatMap y map e insertando las conversiones implícitas correctas para usar nuestra Monad:

def sumSquare[F[_]: Monad](a: F[Int], b: F[Int]): F[Int] =
for {
    x <- a
    y <- b
} yield x*x + y*y

sumSquare(Option(3), Option(4))
// res10: Option[Int] = Some(25)

sumSquare(List(1, 2, 3), List(4, 5))
// res11: List[Int] = List(17, 26, 20, 29, 25, 34)

Eso es más o menos todo lo que necesitamos saber sobre las generalidades de las mónadas en Cats. 

sábado, 21 de mayo de 2022

Mónadas en Cats parte 6

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)

Cats también proporciona una mónada para Future. A diferencia de los métodos de la propia clase Future, los métodos pure y flatMap de la mónada no pueden aceptar parámetros ExecutionContext implícitos (porque los parámetros no forman parte de las definiciones de la característica Monad). Para evitar esto, Cats requiere que tengamos un ExecutionContext en el alcance cuando invocamos una Monad para Future: 

import cats.instances.future._ // for Monad
import scala.concurrent._
import scala.concurrent.duration._

val fm = Monad[Future]
// error: Could not find an instance of Monad for scala.concurrent.
Future
// val fm = Monad[Future]
//
 ^^^^^^^^^^^^^

Poner el ExecutionContext en el alcance corrige la resolución implícita requerida para invocar la instancia:

import scala.concurrent.ExecutionContext.Implicits.global

val fm = Monad[Future]
// fm: Monad[Future] = cats.instances.FutureInstances$$anon$1@7ba44cd6

La instancia de Monad usa el ExecutionContext capturado para llamadas posteriores a pure y flatMap:

val future = fm.flatMap(fm.pure(1))(x => fm.pure(x + 2))
Await.result(future, 1.second)
// res4: Int = 3

Además de lo anterior, Cats proporciona una gran cantidad de nuevas mónadas que no tenemos en la biblioteca estándar. Nos familiarizaremos con algunos de estos en un momento.



jueves, 19 de mayo de 2022

Mónadas en Cats parte 5

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

// list2: List[Int] = List(1, 10, 2, 20, 3, 30)

val list3 = Monad[List].map(list2)(a => a + 123)

// list3: List[Int] = List(124, 133, 125, 143, 126, 153)


Monad proporciona muchos otros métodos, incluidos todos los métodos de Functor.

lunes, 16 de mayo de 2022

Mónadas en Cats parte 4

Si bien solo hemos hablado de flatMap anteriormente, el comportamiento monádico se captura formalmente en dos operaciones:

  • una operación de tipo A => F[A];
  • flatMap, de tipo (F[A], A => F[B]) => F[B].

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)

Identidad derecha: pasar pure a flatMap es lo mismo que no hacer nada:

m.flatMap(pure) == m

Asociatividad: flatMap sobre dos funciones f y g es lo mismo que flatMap sobre f y luego flatMap sobre g:

m.flatMap(f).flatMap(g) == m.flatMap(x => f(x).flatMap(g))

Toda mónada es también un funtor. Podemos definir map de la misma manera para cada mónada usando los métodos existentes, flatMap y pure:

trait Monad[F[_]] {
    def pure[A](a: A): F[A]
    def flatMap[A, B](value: F[A])(func: A => F[B]): F[B]
    def map[A, B](value: F[A])(func: A => B): F[B] = flatMap(value)(a => pure(func(a)))
}




domingo, 15 de mayo de 2022

¿Cuantas jdks usables existen?


Me pregunte cuantas Jdks existen, normalmente trato de usar la de oracle, solo por costumbre o adoptOpenJDK. Solo por costumbre, no por ser fanático, y me pregunte cuantas o cuales podría utilizar y me encontré con esta amplia lista : 

JDK

PROVIDER

AVAILABLE VERSIONS

MISCELLANEOUS

Oracle JDK

Oracle

Builds for different Java projects under development:

Adoptium

Eclipse

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

Dragonwell

Alibaba

According to their own words:

Optimized for online e-commerce, financial, logistics applications running on 100,000+ servers
  • Supports Linux/x86_64 platform only

 Corretto

Amazon


Zulu

Azul

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16-ea

Liberica

BellSoft

Red Hat build of OpenJDK

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15


Sap Machine

SAP

  • 11

  • 15

  • 16-ea

Features contributed by SAP


Y ojo que falta en esta lista GraalVM que lo he usado para unas pruebas y anda joya. 

Mónadas en Cats parte 3

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

Una vez más, especificamos el código que se ejecutará en cada paso, y flatMap se ocupa de todas las terribles complejidades subyacentes de los grupos de subprocesos y los programadores.

Si ha hecho un uso extensivo de Future, sabrá que el código anterior ejecuta cada operación en secuencia. Esto se vuelve más claro si expandimos la comprensión para mostrar las llamadas anidadas a flatMap:

def doSomethingVeryLongRunning: Future[Int] =
    doSomethingLongRunning.flatMap { result1 =>
        doSomethingElseLongRunning.map { result2 =>
            result1 + result2

Cada Futuro en nuestra secuencia es creado por una función que recibe el resultado de un Futuro anterior. En otras palabras, cada paso en nuestro cálculo solo puede comenzar una vez que finaliza el paso anterior.



Podemos ejecutar futuros en paralelo, por supuesto, pero esa es otra historia y se contará en otro momento. Las mónadas tienen que ver con la secuenciación.

From Monitoring to Managing and Beyond

 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:

  • Examine north-south and east-west models for handling API traffic
  • Monitor the health of your API system using traffic metrics and formulas
  • Maintain network reliability and resilience to provide a healthy and scalable infrastructure for your API platform
  • Diagnose and automate your traffic management approach
 
 
 
twitterIcon.pngyoutubeIcon.pnglinkedinIcon.png
nginx-favicon.png
Contact us
nginx-inquiries@nginx.com
1-800-915-9122
© 2022 F5, Inc. ⋅ 795 Folsom Street, San Francisco, CA 94107
Manage your subscription preferences or unsubscribe.

viernes, 13 de mayo de 2022

Mónadas en Cats parte 2

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)

// )

Sin embargo, hay otro modelo mental que podemos aplicar que destaca el comportamiento monádico de List. Si pensamos en las listas como conjuntos de resultados intermedios, flatMap se convierte en una construcción que calcula permutaciones y combinaciones.

Por ejemplo, en la comprensión anterior hay tres valores posibles de x y dos valores posibles de y. Esto significa que hay seis valores posibles de (x, y). flatMap está generando estas combinaciones a partir de nuestro código, que establece la secuencia de operaciones:

• obtener x
• obtener y
• crear una tupla (x, y)

jueves, 12 de mayo de 2022

Mónadas en Cats


Las mónadas son una de las abstracciones más comunes en Scala. Muchos programadores de Scala rápidamente se familiarizan intuitivamente con las mónadas, incluso si no las conocemos por su nombre. Informalmente, una mónada es cualquier cosa con un constructor y un método flatMap. Todos los funtores también son mónadas, incluidos Option, List y Future. Incluso tenemos una sintaxis especial para admitir mónadas: para comprensiones. Sin embargo, a pesar de la ubicuidad del concepto, la biblioteca estándar de Scala carece de un tipo concreto para abarcar "cosas que se pueden mapear planas". Esta clase de tipo es uno de los beneficios que nos brinda Cats. 

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:

  • la primera llamada a parseInt devuelve None o Some;
  • si devuelve un Some, el método flatMap llama a nuestra función y nos pasa el entero aNum;
  • la segunda llamada a parseInt devuelve None o Some;
  • si devuelve Some, el método flatMap llama a nuestra función y nos pasa bNum;
  • la llamada a dividir devuelve Ninguno o Algunos, que es nuestro resultado.

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:

stringDivideBy("6", "2")
// res0: Option[Int] = Some(3)
stringDivideBy("6", "0")
// res1: Option[Int] = None
stringDivideBy("6", "foo")
// res2: Option[Int] = None
stringDivideBy("bar", "2")
// res3: Option[Int] = None

Cada mónada también es un funtor, por lo que podemos confiar tanto en flatMap como en map para secuenciar cálculos que introducen y no introducen una nueva mónada. Además, si tenemos tanto flatMap como map, podemos usar las comprensiones para aclarar el comportamiento de la secuencia:

def stringDivideBy(aStr: String, bStr: String): Option[Int] =
    for {
        aNum <- parseInt(aStr)
        bNum <- parseInt(bStr)
        ans <- divide(aNum, bNum)
    } yield ans


martes, 10 de mayo de 2022

Currying en Kotlin


Ya hable un monton de currying y no tengo ganas de explicarlo de nuevo, pero ahí va :

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

lunes, 9 de mayo de 2022

Probando Ubuntu Jammy


Hace mucho que no pruebo un linux, la verdad es que estoy muy contento con mi fedora con kde. Pero obligado por la necesidad de salir de la zona de confort he decidido instalar Ubuntu Jammy. 

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... 

viernes, 6 de mayo de 2022

Funtores en Cats parte 9

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


Otro ejemplo es el funtor contravariante para Function1.
Mientras que el funtor Function1 implementa la composición de función de izquierda a derecha, el funtor Contravariant implementa la composición de derecha a izquierda con estilo de composición. En otras palabras, las siguientes expresiones son todas equivalentes:

val func3a: Int => Double = a => func2(func1(a))
val func3b: Int => Double = func2.compose(func1)

// Hypothetical example. This won't actually compile:
val func3c: Int => Double = func2.contramap(func1)

Sin embargo, si intentamos esto de verdad, nuestro código no se compilará:

import cats.syntax.contravariant._ // for contramap
val func3c = func2.contramap(func1)
// error: value contramap is not a member of Double => Double
// val func3c = func2.contramap(func1)
//
 ^^^^^^^^^^^^^^^

El problema aquí es que la contravariante para la Function1 corrige el tipo de retorno y deja que el tipo de parámetro varíe, lo que requiere que el compilador elimine los parámetros de tipo de derecha a izquierda, como se muestra a continuación

type F[A] = A => Double

El compilador falla simplemente debido a su sesgo de izquierda a derecha. Podemos probar esto creando un alias de tipo que cambia los parámetros en la Función1:

type <=[B, A] = A => B
type F[A] = Double <= A

Si volvemos a escribir func2 como una instancia de <=, restablecemos el orden de eliminación requerido y podemos llamar a contramap como deseemos:

val func2b: Double <= Double = func2
val func3c = func2b.contramap(func1)
// func3c: Int => Double = scala.Function1$$Lambda$7919/0
x00000008424d3040@50061a2d

La diferencia entre func2 y func2b es puramente sintáctica: ambos se refieren al mismo valor y, por lo demás, los alias de tipo son completamente compatibles. Increíblemente, sin embargo, esta simple reformulación es suficiente para darle al compilador la pista que necesita para resolver el problema.
Es raro que tengamos que hacer este tipo de eliminación de derecha a izquierda. La mayoría de los constructores de tipos de parámetros múltiples están diseñados para tener un sesgo hacia la derecha, lo que requiere la eliminación de izquierda a derecha que es compatible con el compilador listo para usar. Sin embargo, es útil conocer esta peculiaridad del orden de eliminación en caso de que alguna vez te encuentres con un escenario extraño como el anterior.


lunes, 2 de mayo de 2022

¿Qué gano aprendiendo Cats?


Aprender Cats no es tan difícil, pero ¿por qué querría hacer eso?

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/

domingo, 1 de mayo de 2022

Funtores en Cats parte 8

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


Function1 tiene dos parámetros de tipo (el argumento de la función y el tipo de resultado):

trait Function1[-A, +B] {
  def apply(arg: A): B
}

Sin embargo, Functor acepta un constructor de tipos con un parámetro:

trait Functor[F[_]] {
  def map[A, B](fa: F[A])(func: A => B): F[B]
}

El compilador tiene que corregir uno de los dos parámetros de Function1 para crear un constructor de tipos del tipo correcto para pasar a Functor. Tiene dos opciones a elegir:

type F[A] = Int => A
type F[A] = A => Double

Sabemos que la primera de ellas es la elección correcta. Sin embargo, el compilador no entiende lo que significa el código. En cambio, se basa en una regla simple, implementando lo que se llama "unificación parcial".

La unificación parcial en el compilador de Scala funciona fijando los parámetros de tipo de izquierda a derecha. En el ejemplo anterior, el compilador corrige Int en Int => Double y busca un Functor para funciones de tipo Int => ?:

type F[A] = Int => A
val functor = Functor[F]

Esta eliminación de izquierda a derecha funciona para una amplia variedad de escenarios comunes, incluidos Functors para tipos como Function1 y Either:

val either: Either[String, Int] = Right(123)
// either: Either[String, Int] = Right(123)
either.map(_ + 1)
// res0: Either[String, Int] = Right(124)


sábado, 30 de abril de 2022

Funtores en Cats parte 7

Entre otros tipos, Cats proporciona una instancia de Invariant para Monoid. 

Imagina que queremos producir un monoide para el tipo símbolo de Scala. Cats no proporciona un Monoide para Símbolo, pero sí proporciona un Monoide para un tipo similar: Cadena o String. Podemos escribir nuestro nuevo semigrupo con un método vacío que se basa en la cadena vacía y un método de combinación que funciona de la siguiente manera:

  1. aceptar dos Símbolos como parámetros;
  2. convertir los Símbolos a Cadenas;
  3. combine las cadenas usando Monoid[String];
  4. convertir el resultado de nuevo en un Símbolo.

Podemos implementar combine usando imap, pasando funciones de tipo String =>Symbol y Symbol => String como parámetros. Aquí está el código, escrito usando el método de extensión imap proporcionado por cats.syntax.invariant:

import cats.Monoid
import cats.instances.string._ // for Monoid
import cats.syntax.invariant._ // for imap
import cats.syntax.semigroup._ // for |+|

implicit val symbolMonoid: Monoid[Symbol] = Monoid[String].imap(Symbol.apply)(_.name) 

Monoid[Symbol].empty
// res3: Symbol = '

Symbol("a") |+| Symbol("few") |+| Symbol("words")
// res4: Symbol = 'afewwords