jueves, 15 de diciembre de 2022

[eBook] Secure, Scale, and Monitor Kubernetes with AWS and F5 NGINX

 

EBOOK

[eBook] Secure, Scale, and Monitor Kubernetes with AWS and F5 NGINX

Hi Emanuel,

In this eBook, discover how F5 NGINX, available in the AWS Marketplace, complements Amazon EKS with advanced networking and security solutions for Kubernetes that are platform agnostic, enable production-grade Kubernetes at scale, and effortlessly handle traffic spikes and security threats without compromising on performance.

In this eBook you will learn:

  • The three main components needed to make Kubernetes production grade
  • Why Kubernetes clusters require “fine-grained” security inside the cluster and three locations where you may need to deploy a WAF
  • How platform-agnostic tools reduce complexity and improve security to create a unified “single pane of glass” view of your infrastructure
  • How NGINX Kubernetes solutions enable automation, security, performance, and insights for easy Kubernetes scalability on Amazon EKS

martes, 13 de diciembre de 2022

Semigroupal, Parallel y Applicative parte 8


Con la introducción de Apply y Applicative, podemos alejarnos y ver toda una familia de clases de tipos que se ocupan de secuenciar cálculos de diferentes maneras. 

Cada clase de tipo en la jerarquía representa un conjunto particular de semántica de secuenciación, presenta un conjunto de métodos característicos y define la funcionalidad de sus supertipos en términos de ellos:

  • toda mónada es un aplicativo;
  • cada aplicativo un semigrupal;
  • y así.

Debido a la naturaleza de las relaciones entre las type class, las relaciones de herencia son constantes en todas las instancias.

Apply define product en términos de ap y map; Monad define product, ap y map, en términos de  pure y flatMap.

Para ilustrar esto, consideremos dos tipos de datos hipotéticos:

• Foo es una mónada. Tiene una instancia de la type class Monad que implementa pure y flatMap y hereda definiciones estándar de product, map y ap;

• Bar es un funtor aplicativo. Tiene una instancia de Applicative que implementa pure y ap y hereda definiciones estándar de product y map.

¿Qué podemos decir sobre estos dos tipos de datos sin saber más sobre su implementación?

Sabemos estrictamente más sobre Foo que sobre Bar: Monad es un subtipo de Applicative, por lo que podemos garantizar propiedades de Foo (a saber, flatMap) que no podemos garantizar con Bar. Por el contrario, sabemos que Bar puede tener una gama más amplia de comportamientos que Foo. Tiene menos leyes que obedecer (sin flatMap), por lo que puede implementar comportamientos que Foo no puede.

Esto demuestra el clásico intercambio de poder (en el sentido matemático) versus restricción. Cuantas más restricciones imponemos a un tipo de datos, más garantías tenemos sobre su comportamiento, pero menos comportamientos podemos modelar.

Las mónadas resultan ser un punto dulce en esta compensación. Son lo suficientemente flexibles para modelar una amplia gama de comportamientos y lo suficientemente restrictivos para dar garantías sólidas sobre esos comportamientos. Sin embargo, hay situaciones en las que las mónadas no son la herramienta adecuada para el trabajo. A veces queremos comida tailandesa y los burritos simplemente no satisfacen.

Mientras que las mónadas imponen una secuencia estricta en los cálculos que modelan, los aplicativos y los semigrupos no imponen tal restricción. Esto los coloca en un punto dulce diferente en la jerarquía. Podemos usarlos para representar clases de cálculos paralelos/independientes que las mónadas no pueden.

Elegimos nuestra semántica eligiendo nuestras estructuras de datos. Si elegimos una mónada, obtenemos una secuencia estricta. Si elegimos un aplicativo, perdemos la capacidad de flatMap. Esta es la compensación impuesta por las leyes de consistencia. ¡Así que elige tus tipos con cuidado!

Si bien las mónadas y los funtores son los tipos de datos de secuenciación más utilizados, los semigrupos y los aplicativos son los más generales.

Estas clases de tipos proporcionan un mecanismo genérico para combinar valores y aplicar funciones dentro de un contexto, a partir del cual podemos crear mónadas y una variedad de otros combinadores.

Semigroupal y Applicative se usan más comúnmente como un medio para combinar valores independientes, como los resultados de las reglas de validación. Cats proporciona el tipo Validated para este propósito específico, junto con la sintaxis de aplicación como una forma conveniente de expresar la combinación de reglas.

lunes, 12 de diciembre de 2022

Semigroupal, Parallel y Applicative parte 7

Los semigrupos no se mencionan con frecuencia en la literatura más amplia de programación funcional. Proporcionan un subconjunto de la funcionalidad de un type class relacionada llamada funtor aplicativo ("aplicativo" para abreviar).

Semigroupal y Applicative proporcionan codificaciones alternativas de la misma noción de unir contextos. Ambas codificaciones se presentan en el mismo artículo de 2008 de Conor McBride y Ross Paterson.

Cats modela applicatives usando dos type classes. El primero, cats.Apply, extiende Semigroupal y Functor y agrega un método ap que aplica un parámetro a una función dentro de un contexto. El segundo, cats.Applicative, extiende Apply y agrega el método  pure. Aquí hay una definición simplificada en el código:


trait Apply[F[_]] extends Semigroupal[F] with Functor[F] {

    def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]

    def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =

         ap(map(fa)(a => (b: B) => (a, b)))(fb)

}

trait Applicative[F[_]] extends Apply[F] { 

     def pure[A](a: A): F[A]

}

Desglosando esto, el método ap aplica un parámetro fa a una función ff dentro de un contexto F[_]. El método product de Semigroupal se define en términos de ap y map.

No se preocupe demasiado por la implementación de product: es difícil de leer y los detalles no son particularmente importantes. El punto principal es que existe una estrecha relación entre product, ap y map que permite definir cualquiera de ellos en términos de los otros dos.

El Applicative también introduce el método pure. Este es el mismo pure que vimos en Monad. Construye una nueva instancia de aplicación a partir de un valor no encapsulado. En este sentido, Applicative está relacionado con Apply como Monoid está relacionado con Semigroup.



viernes, 9 de diciembre de 2022

Forrester: Ahorros y beneficios de Google Kubernetes Engine para las empresas

 

miércoles, 7 de diciembre de 2022

Semigroupal, Parallel y Applicative parte 6

Cuando llamamos a product en un tipo que tiene una instancia de Monad, obtenemos una semántica secuencial. Esto tiene sentido desde el punto de vista de mantener la coherencia con las implementaciones de product en términos de flatMap y map. Sin embargo, no siempre es lo que queremos. La clase de tipo Parallel, y su sintaxis asociada, nos permite acceder a semánticas alternativas para ciertas mónadas.

Hemos visto cómo el método del producto en Either se detiene en el primer error.


import cats.Semigroupal

import cats.instances.either._ // for Semigroupal

type ErrorOr[A] = Either[Vector[String], A]

val error1: ErrorOr[Int] = Left(Vector("Error 1"))

val error2: ErrorOr[Int] = Left(Vector("Error 2"))

Semigroupal[ErrorOr].product(error1, error2)

// res0: ErrorOr[(Int, Int)] = Left(Vector("Error 1"))


También podemos escribir esto usando tupled como atajo.


import cats.syntax.apply._ // for tupled

import cats.instances.vector._ // for Semigroup on Vector

(error1, error2).tupled

// res1: ErrorOr[(Int, Int)] = Left(Vector("Error 1"))


Para recopilar todos los errores simplemente reemplazamos tupled con su versión “paralela” llamada parTupled.


import cats.syntax.parallel._ // for parTupled

(error1, error2).parTupled

// res2: ErrorOr[(Int, Int)] = Left(Vector("Error 1", "Error 2"))


¡Se devuelven ambos errores! Este comportamiento no es especial para usar Vector como tipo de error. Cualquier tipo que tenga una instancia de Semigroup funcionará.

Por ejemplo, aquí usamos List en su lugar.


import cats.instances.list._ // for Semigroup on List

type ErrorOrList[A] = Either[List[String], A]

val errStr1: ErrorOrList[Int] = Left(List("error 1"))

val errStr2: ErrorOrList[Int] = Left(List("error 2"))

(errStr1, errStr2).parTupled

// res3: ErrorOrList[(Int, Int)] = Left(List("error 1", "error 2"))


Hay muchos métodos de sintaxis proporcionados por Parallel para métodos en Semigroupal y tipos relacionados, pero el más utilizado es parMapN.

Aquí hay un ejemplo de parMapN en una situación de manejo de errores.


val success1: ErrorOr[Int] = Right(1)

val success2: ErrorOr[Int] = Right(2)

val addTwo = (x: Int, y: Int) => x + y

(error1, error2).parMapN(addTwo)

// res4: ErrorOr[Int] = Left(Vector("Error 1", "Error 2"))

(success1, success2).parMapN(addTwo)

// res5: ErrorOr[Int] = Right(3)


Profundicemos en cómo funciona Parallel. La siguiente definición es el núcleo de Parallel.


trait Parallel[M[_]] {

type F[_]

def applicative: Applicative[F]

def monad: Monad[M]

def parallel: ~>[M, F]

}


Esto nos dice si hay una instancia paralela para algún constructor de tipo M, entonces:

• debe haber una instancia de Monad para M;

• hay un constructor de tipo relacionado F que tiene una instancia Aplicativa; y

• podemos convertir M a F.

No hemos visto ~> antes. Es un alias de tipo para FunctionK y es lo que realiza la conversión de M a F. Una función normal A => B convierte valores de tipo A a valores de tipo B. Recordemos que M y F no son tipos; son constructores de tipos. Una FunciónK M ~> F es una función de un valor con tipo M[A] a un valor con tipo F[A]. Veamos un ejemplo rápido definiendo una FunciónK que convierte un Option en una Lista.

import cats.arrow.FunctionK

object optionToList extends FunctionK[Option, List] {

def apply[A](fa: Option[A]): List[A] =

    fa match {

        case None => List.empty[A]

        case Some(a) => List(a)

    }

}

optionToList(Some(1))

// res6: List[Int] = List(1)

optionToList(None)

// res7: List[Nothing] = List()


Como el parámetro de tipo A es genérico, una función K no puede inspeccionar ningún valor contenido con el constructor de tipo M. La conversión debe realizarse únicamente en términos de la estructura de los constructores de tipo M y F. Podemos en optionToList arriba, este es el caso.

Entonces, en resumen, Parallel nos permite tomar un tipo que tiene una instancia de mónada y convertirlo en algún tipo relacionado que en su lugar tenga una instancia aplicativa (o semigrupal). Este tipo relacionado tendrá algunas semánticas alternativas útiles.

Hemos visto el caso anterior donde el aplicativo relacionado para "O" permite la acumulación de errores en lugar de una semántica rápida.

Ahora que hemos visto Parallel, es hora de aprender finalmente sobre Applicative.

viernes, 2 de diciembre de 2022

Cuando utilizar estructuras, registro o clases en C#?


Que sé yo... Un lenguaje cuantas más cosas trae, a mi entender más complijidad contrae y más dificil es aclarar ciertos puntos... 

Y para colmo se pueden hacer más cosas de diferentes formas, haciendo que se pierda la claridad del mejor camino para resolver las cosas.  Puff ... Y sin duda C#, java, scala, kotlin, etc... son muy completos y por ende muy complejos de entender cuando usar que. 

Luego de esta introducción/opinión, vamos al tema del post. Cuando utilizar estructuras, registro o clases en C#?

¿Puede el tipo de datos ser un tipo de valor? entonces es un estructura. ¿No? ¿Su tipo describe un estado similar a un valor, preferiblemente inmutable? entonces registro. Y sino clase pero podemos usar registro para:  

  • DTO si el flujo unidireccional.
  • Immutable request
  • SearchParameters

Veamos esto con más detalle. Una estructura, una clase y un registro son tipos de datos de usuario.

Las estructuras son tipos de valor. Las clases son tipos de referencia. Los registros son por defecto tipos de referencia inmutables.

Cuando necesita algún tipo de jerarquía para describir sus tipos de datos como herencia o una estructura que apunta a otra estructura o básicamente cosas que apuntan a otras cosas, necesita un tipo de referencia.

Los registros resuelven el problema cuando desea que su tipo esté orientado a valores de forma predeterminada. Los registros son tipos de referencia pero con la semántica orientada al valor.

En conclusión, 

Estructura si: 

  • Representa lógicamente un solo valor, similar a los tipos primitivos (int, double, etc.).
  • Tiene un tamaño de instancia inferior a 16 bytes.
  • Es inmutable
  • No tendrá que ser referenciado con frecuencia.

¿No? Debe ser algún tipo de referencia (clase o registro).

¿El tipo de datos encapsula algún tipo de valor complejo? ¿El valor es inmutable? ¿Lo usa en flujo unidireccional (una vía)? Vamos con registro.

¿No? Vamos con clase.

Por cierto: no te olvides que habrá registros anónimos en C# 10.0, como para ponerle más complejidad.

Otra cosa, una instancia de registro puede ser mutable si la convierte en mutable.


class Program

{

    static void Main()

    {

        var test = new Foo("a");

        Console.WriteLine(test.MutableProperty);

        test.MutableProperty = 15;

        Console.WriteLine(test.MutableProperty);

        //test.Bar = "new string"; // will not compile

    }

}


public record Foo(string Bar)

{

    public double MutableProperty { get; set; } = 10.0;

}


Una asignación de un registro es una copia superficial del registro. Una copia con expresión de un registro no es ni superficial ni profunda. La copia se crea mediante un método de clonación especial emitido por el compilador de C#. Los miembros de tipo de valor se copian y encuadran. Los miembros de tipo de referencia apuntan a la misma referencia. Puede hacer una copia profunda de un registro si y solo si el registro solo tiene propiedades de tipo de valor. Cualquier propiedad de miembro de tipo de referencia de un registro se copia como una copia superficial.

Veamos este ejemplo (usando la función de nivel superior en C# 9.0):


using System.Collections.Generic;

using static System.Console;


var foo = new SomeRecord(new List<string>());

var fooAsShallowCopy = foo;

var fooAsWithCopy = foo with { }; // A syntactic sugar for new SomeRecord(foo.List);

var fooWithDifferentList = foo with { List = new List<string>() { "a", "b" } };

var differentFooWithSameList = new SomeRecord(foo.List); // This is the same like foo with { };

foo.List.Add("a");


WriteLine($"Count in foo: {foo.List.Count}"); // 1

WriteLine($"Count in fooAsShallowCopy: {fooAsShallowCopy.List.Count}"); // 1

WriteLine($"Count in fooWithDifferentList: {fooWithDifferentList.List.Count}"); // 2

WriteLine($"Count in differentFooWithSameList: {differentFooWithSameList.List.Count}"); // 1

WriteLine($"Count in fooAsWithCopy: {fooAsWithCopy.List.Count}"); // 1

WriteLine("");


WriteLine($"Equals (foo & fooAsShallowCopy): {Equals(foo, fooAsShallowCopy)}"); // True. The lists inside are the same.

WriteLine($"Equals (foo & fooWithDifferentList): {Equals(foo, fooWithDifferentList)}"); // False. The lists are different

WriteLine($"Equals (foo & differentFooWithSameList): {Equals(foo, differentFooWithSameList)}"); // True. The list are the same.

WriteLine($"Equals (foo & fooAsWithCopy): {Equals(foo, fooAsWithCopy)}"); // True. The list are the same, see below.

WriteLine($"ReferenceEquals (foo.List & fooAsShallowCopy.List): {ReferenceEquals(foo.List, fooAsShallowCopy.List)}"); // True. The records property points to the same reference.

WriteLine($"ReferenceEquals (foo.List & fooWithDifferentList.List): {ReferenceEquals(foo.List, fooWithDifferentList.List)}"); // False. The list are different instances.

WriteLine($"ReferenceEquals (foo.List & differentFooWithSameList.List): {ReferenceEquals(foo.List, differentFooWithSameList.List)}"); // True. The records property points to the same reference.

WriteLine($"ReferenceEquals (foo.List & fooAsWithCopy.List): {ReferenceEquals(foo.List, fooAsWithCopy.List)}"); // True. The records property points to the same reference.

WriteLine("");


WriteLine($"ReferenceEquals (foo & fooAsShallowCopy): {ReferenceEquals(foo, fooAsShallowCopy)}"); // True. !!! fooAsCopy is pure shallow copy of foo. !!!

WriteLine($"ReferenceEquals (foo & fooWithDifferentList): {ReferenceEquals(foo, fooWithDifferentList)}"); // False. These records are two different reference variables.

WriteLine($"ReferenceEquals (foo & differentFooWithSameList): {ReferenceEquals(foo, differentFooWithSameList)}"); // False. These records are two different reference variables and reference type property hold by these records does not matter in ReferenceEqual.

WriteLine($"ReferenceEquals (foo & fooAsWithCopy): {ReferenceEquals(foo, fooAsWithCopy)}"); // False. The same story as differentFooWithSameList.

WriteLine("");


var bar = new RecordOnlyWithValueNonMutableProperty(0);

var barAsShallowCopy = bar;

var differentBarDifferentProperty = bar with { NonMutableProperty = 1 };

var barAsWithCopy = bar with { };


WriteLine($"Equals (bar & barAsShallowCopy): {Equals(bar, barAsShallowCopy)}"); // True.

WriteLine($"Equals (bar & differentBarDifferentProperty): {Equals(bar, differentBarDifferentProperty)}"); // False. Remember, the value equality is used.

WriteLine($"Equals (bar & barAsWithCopy): {Equals(bar, barAsWithCopy)}"); // True. Remember, the value equality is used.

WriteLine($"ReferenceEquals (bar & barAsShallowCopy): {ReferenceEquals(bar, barAsShallowCopy)}"); // True. The shallow copy.

WriteLine($"ReferenceEquals (bar & differentBarDifferentProperty): {ReferenceEquals(bar, differentBarDifferentProperty)}"); // False. Operator with creates a new reference variable.

WriteLine($"ReferenceEquals (bar & barAsWithCopy): {ReferenceEquals(bar, barAsWithCopy)}"); // False. Operator with creates a new reference variable.

WriteLine("");


var fooBar = new RecordOnlyWithValueMutableProperty();

var fooBarAsShallowCopy = fooBar; // A shallow copy, the reference to bar is assigned to barAsCopy

var fooBarAsWithCopy = fooBar with { }; // A deep copy by coincidence because fooBar has only one value property which is copied into barAsDeepCopy.


WriteLine($"Equals (fooBar & fooBarAsShallowCopy): {Equals(fooBar, fooBarAsShallowCopy)}"); // True.

WriteLine($"Equals (fooBar & fooBarAsWithCopy): {Equals(fooBar, fooBarAsWithCopy)}"); // True. Remember, the value equality is used.

WriteLine($"ReferenceEquals (fooBar & fooBarAsShallowCopy): {ReferenceEquals(fooBar, fooBarAsShallowCopy)}"); // True. The shallow copy.

WriteLine($"ReferenceEquals (fooBar & fooBarAsWithCopy): {ReferenceEquals(fooBar, fooBarAsWithCopy)}"); // False. Operator with creates a new reference variable.

WriteLine("");


fooBar.MutableProperty = 2;

fooBarAsShallowCopy.MutableProperty = 3;

fooBarAsWithCopy.MutableProperty = 3;

WriteLine($"fooBar.MutableProperty = {fooBar.MutableProperty} | fooBarAsShallowCopy.MutableProperty = {fooBarAsShallowCopy.MutableProperty} | fooBarAsWithCopy.MutableProperty = {fooBarAsWithCopy.MutableProperty}"); // fooBar.MutableProperty = 3 | fooBarAsShallowCopy.MutableProperty = 3 | fooBarAsWithCopy.MutableProperty = 3

WriteLine($"Equals (fooBar & fooBarAsShallowCopy): {Equals(fooBar, fooBarAsShallowCopy)}"); // True.

WriteLine($"Equals (fooBar & fooBarAsWithCopy): {Equals(fooBar, fooBarAsWithCopy)}"); // True. Remember, the value equality is used. 3 != 4

WriteLine($"ReferenceEquals (fooBar & fooBarAsShallowCopy): {ReferenceEquals(fooBar, fooBarAsShallowCopy)}"); // True. The shallow copy.

WriteLine($"ReferenceEquals (fooBar & fooBarAsWithCopy): {ReferenceEquals(fooBar, fooBarAsWithCopy)}"); // False. Operator with creates a new reference variable.

WriteLine("");


fooBarAsWithCopy.MutableProperty = 4;

WriteLine($"fooBar.MutableProperty = {fooBar.MutableProperty} | fooBarAsShallowCopy.MutableProperty = {fooBarAsShallowCopy.MutableProperty} | fooBarAsWithCopy.MutableProperty = {fooBarAsWithCopy.MutableProperty}"); // fooBar.MutableProperty = 3 | fooBarAsShallowCopy.MutableProperty = 3 | fooBarAsWithCopy.MutableProperty = 4

WriteLine($"Equals (fooBar & fooBarAsWithCopy): {Equals(fooBar, fooBarAsWithCopy)}"); // False. Remember, the value equality is used. 3 != 4

WriteLine("");


var venom = new MixedRecord(new List<string>(), 0); // Reference/Value property, mutable non-mutable.

var eddieBrock = venom;

var carnage = venom with { };

venom.List.Add("I'm a predator.");

carnage.List.Add("All I ever wanted in this world is a carnage.");

WriteLine($"Count in venom: {venom.List.Count}"); // 2

WriteLine($"Count in eddieBrock: {eddieBrock.List.Count}"); // 2

WriteLine($"Count in carnage: {carnage.List.Count}"); // 2

WriteLine($"Equals (venom & eddieBrock): {Equals(venom, eddieBrock)}"); // True.

WriteLine($"Equals (venom & carnage): {Equals(venom, carnage)}"); // True. Value properties has the same values, the List property points to the same reference.

WriteLine($"ReferenceEquals (venom & eddieBrock): {ReferenceEquals(venom, eddieBrock)}"); // True. The shallow copy.

WriteLine($"ReferenceEquals (venom & carnage): {ReferenceEquals(venom, carnage)}"); // False. Operator with creates a new reference variable.

WriteLine("");


eddieBrock.MutableList = new List<string>();

eddieBrock.MutableProperty = 3;

WriteLine($"Equals (venom & eddieBrock): {Equals(venom, eddieBrock)}"); // True. Reference or value type does not matter. Still a shallow copy of venom, still true.

WriteLine($"Equals (venom & carnage): {Equals(venom, carnage)}"); // False. the venom.List property does not points to the same reference like in carnage.List anymore.

WriteLine($"ReferenceEquals (venom & eddieBrock): {ReferenceEquals(venom, eddieBrock)}"); // True. The shallow copy.

WriteLine($"ReferenceEquals (venom & carnage): {ReferenceEquals(venom, carnage)}"); // False. Operator with creates a new reference variable.

WriteLine($"ReferenceEquals (venom.List & carnage.List): {ReferenceEquals(venom.List, carnage.List)}"); // True. Non mutable reference type.

WriteLine($"ReferenceEquals (venom.MutableList & carnage.MutableList): {ReferenceEquals(venom.MutableList, carnage.MutableList)}"); // False. This is why Equals(venom, carnage) returns false.

WriteLine("");



public record SomeRecord(List<string> List);


public record RecordOnlyWithValueNonMutableProperty(int NonMutableProperty);


public record RecordOnlyWithValueMutableProperty

{

    public int MutableProperty { get; set; } = 1; // this property gets boxed

}


public record MixedRecord(List<string> List, int NonMutableProperty)

{

    public List<string> MutableList { get; set; } = new();

    public int MutableProperty { get; set; } = 1; // this property gets boxed

}


La penalización de rendimiento es obvia aquí. A mayor cantidad de datos para copiar en una instancia de registro que tenga, mayor penalización de rendimiento obtendrá. En general, debe crear clases pequeñas y livianas y esta regla también se aplica a los registros.

Si su aplicación usa una base de datos o un sistema de archivos, no me preocuparía mucho por esta penalización. Las operaciones de la base de datos/sistema de archivos son generalmente más lentas.


Ebook - Zero Trust Architecture in Kubernetes

 

EBOOK

[O'Reilly eBook] Zero Trust Architecture in Kubernetes

Hi Emanuel,

In this eBook from O’Reilly Media, available for free download courtesy of F5 NGINX, cybersecurity researcher and writer Kim Crawley discusses how the Zero Trust model improves the security posture of your Kubernetes infrastructure and prevents security incidents from damaging your organization.

In this eBook you will learn:

  • The key concepts and principles of the Zero Trust security model
  • How Zero Trust improves the security posture of your organization
  • The technical requirements for Zero Trust in Kubernetes
  • How to apply Zero Trust best practices in your Kubernetes environment

jueves, 1 de diciembre de 2022

Semigroupal, Parallel y Applicative parte 5

La razón de los resultados sorprendentes de List y de Either es que ambas son mónadas. Si tenemos una mónada, podemos implementar el producto de la siguiente manera.


import cats.Monad

import cats.syntax.functor._ // for map

import cats.syntax.flatMap._ // for flatmap

def product[F[_]: Monad, A, B](fa: F[A], fb: F[B]): F[(A,B)] = 

fa.flatMap(a =>

    fb.map(b =>

        (a, b)

    ) 


Sería muy extraño si tuviéramos diferentes semánticas para el producto dependiendo de cómo lo implementemos. Para garantizar una semántica coherente, Cats' Monad (que amplía Semigroupal) proporciona una definición estándar de producto en términos de map y flatmap, como mostramos anteriormente.

Incluso nuestros resultados para Future son un truco de la luz. flatMap proporciona un orden secuencial, por lo que el producto proporciona lo mismo. La ejecución paralela que observamos ocurre porque nuestros futuros constituyentes comienzan a correr antes de que llamemos al producto. Esto es equivalente al patrón clásico create-then-flatMap:


val a = Future("Future 1")

val b = Future("Future 2")

for {

    x <- a

    y <- b

} yield (x, y)


Entonces, ¿por qué molestarse con Semigroupal? La respuesta es que podemos crear tipos de datos útiles que tengan instancias de Semigroupal (y Applicative) pero no Monad. Esto nos libera para implementar el producto de diferentes maneras. 

Semigroupal, Parallel y Applicative parte 4

Semigroupal no siempre proporciona el comportamiento que esperamos, particularmente para los tipos que también tienen instancias de Monad. Hemos visto el comportamiento del Semigroupal para Option. Veamos algunos ejemplos para otros tipos.

La semántica de Future proporciona ejecución paralela en lugar de secuencial:


import cats.Semigroupal

import cats.instances.future._ // for Semigroupal

import scala.concurrent._

import scala.concurrent.duration._

import scala.concurrent.ExecutionContext.Implicits.global

val futurePair = Semigroupal[Future].

product(Future("Hello"), Future(123))

Await.result(futurePair, 1.second)

// res0: (String, Int) = ("Hello", 123)


Los dos futuros comienzan a ejecutarse en el momento en que los creamos, por lo que ya están calculando los resultados en el momento en que llamamos producto. Podemos usar la sintaxis de aplicación para comprimir números fijos de futuros:


import cats.syntax.apply._ // for mapN

case class Cat(

    name: String,

    yearOfBirth: Int,

    favoriteFoods: List[String]

)

val futureCat = (

    Future("Garfield"),

    Future(1978),

    Future(List("Lasagne"))

).mapN(Cat.apply)

Await.result(futureCat, 1.second)

// res1: Cat = Cat("Garfield", 1978, List("Lasagne"))


La combinación de Listas con Semigroupal produce algunos resultados potencialmente inesperados. Podríamos esperar un código como el siguiente para comprimir las listas, pero en realidad obtenemos el producto cartesiano de sus elementos:


import cats.Semigroupal

import cats.instances.list._ // for Semigroupal

Semigroupal[List].product(List(1, 2), List(3, 4))

// res2: List[(Int, Int)] = List((1, 3), (1, 4), (2, 3), (2, 4))


Esto es quizás sorprendente. Comprimir listas tiende a ser una operación más común. 

Podríamos esperar que el producto aplicado a O acumule errores en lugar de fallar rápidamente. Nuevamente, tal vez sorprendentemente, encontramos que el producto implementa el mismo comportamiento de falla rápida que flatMap:

import cats.instances.either._ // for Semigroupal

type ErrorOr[A] = Either[Vector[String], A]

Semigroupal[ErrorOr].product(

Left(Vector("Error 1")),

Left(Vector("Error 2"))

)

// res3: ErrorOr[Tuple2[Nothing, Nothing]] = Left(Vector("Error 1"))

En este ejemplo, el producto ve la primera falla y se detiene, aunque es posible examinar el segundo parámetro y ver que también es una falla.




miércoles, 30 de noviembre de 2022

Semigroupal, Parallel y Applicative parte 3

Cats proporciona una sintaxis de aplicación simplificada. Importamos cats.syntax.apply.


import cats.instances.option._ // for Semigroupal

import cats.syntax.apply._ // for tupled and mapN


El método tuplado se agrega implícitamente a la tupla de Opciones. Utiliza el Semigroupal para Option para comprimir los valores dentro de las Opciones, creando una sola Opción de una tupla:


(Option(123), Option("abc")).tupled

// res8: Option[(Int, String)] = Some((123, "abc"))


Podemos usar el mismo truco en tuplas de hasta 22 valores. Cats define un método tuplado separado para cada aridad:


(Option(123), Option("abc"), Option(true)).tupled

// res9: Option[(Int, String, Boolean)] = Some((123, "abc", true))


Además de tuplado, la sintaxis de aplicación de Cats proporciona un método llamado mapN que acepta un Funtor implícito y una función de la aridad correcta para combinar los valores.


final case class Cat(name: String, born: Int, color: String)

(

Option("Garfield"),

Option(1978),

Option("Orange & black")

).mapN(Cat.apply)

// res10: Option[Cat] = Some(Cat("Garfield", 1978, "Orange & black"))


De todos los métodos mencionados aquí, el más común es usar mapN. Internamente, mapN usa Semigroupal para extraer los valores de Option y Functor para aplicar los valores a la función.

Es bueno ver que esta sintaxis está marcada. Si proporcionamos una función que acepta el número o tipo de parámetros incorrectos, obtenemos un error de compilación:


val add: (Int, Int) => Int = (a, b) => a + b

// add: (Int, Int) => Int = <function2>

(Option(1), Option(2), Option(3)).mapN(add)

// error: ':' expected but '(' found.

// Option("Garfield"),

// ^

(Option("cats"), Option(true)).mapN(add)

/ error: ':' expected but '(' found.

// Option("Garfield"),

// ^


Apply  también tiene métodos contramapN e imapN que aceptan funtores contravariantes e invariantes. Por ejemplo, podemos combinar Monoids usando Invariant. Aquí hay un ejemplo:

import cats.Monoid

import cats.instances.int._// for Monoid

import cats.instances.invariant._// for Semigroupal

import cats.instances.list._// for Monoid

import cats.instances.string._// for Monoid

import cats.syntax.apply._// for imapN


final case class Cat(

   name: String,

   yearOfBirth: Int,

   favoriteFoods: List[String]

)


val tupleToCat: (String, Int, List[String]) => Cat = Cat.apply _

val catToTuple: Cat => (String, Int, List[String]) = cat => (cat.name, cat.yearOfBirth, cat.favoriteFoods)

implicit val catMonoid: Monoid[Cat] = (

    Monoid[String],

    Monoid[Int],

    Monoid[List[String]]

).imapN(tupleToCat)(catToTuple)


Nuestro Monoid nos permite crear Cats "vacíos" y agregar Cats usando la sintaxis :


import cats.syntax.semigroup._ // for |+|

val garfield= Cat("Garfield", 1978, List("Lasagne"))

val heathcliff = Cat("Heathcliff", 1988, List("Junk Food"))

garfield |+| heathcliff

// res14: Cat = Cat("GarfieldHeathcliff", 3966, List("Lasagne", "Junk

Food"))