Translate

domingo, 23 de octubre de 2022

Funciones en Go parte 2


Seguimos con funciones en Go. 

Se puede pasar la dirección de una variable como parámetro de una función con esta técnica, podemos modificar el valor este parámetro. A la vez, esto permite que golang no tenga la necesidad de copiar este parámetro. Cosa muy útil cuando tenemos que pasar estructuras grandes o vectores. 

package main

import "fmt"


func update(a *int, t *string) {

*a = *a + 5      // defrencing pointer address

*t = *t + " Doe" // defrencing pointer address

return

}


func main() {

var age = 20

var text = "John"

fmt.Println("Before:", text, age)

update(&age, &text)

fmt.Println("After :", text, age)

}


Una función anónima es una función que se declaró sin ningún identificador con nombre para hacer referencia a ella. Las funciones anónimas pueden aceptar entradas y devolver salidas, tal como lo hacen las funciones estándar.

package main


import "fmt"


func main() {

func(l int, b int) {

fmt.Println(l * b)

}(20, 30)

}


Las clausuras son un caso especial de funciones anónimas., son funciones anónimas que acceden a las variables definidas fuera del cuerpo de la función.


package main

import "fmt"


func main() {

for i := 10.0; i < 100; i += 10.0 {

rad := func() float64 {

return i * 39.370

}()

fmt.Printf("%.2f Meter = %.2f Inch\n", i, rad)

}

}


Una función de orden superior es una función que recibe una función como argumento o devuelve la función como salida.

Las funciones de orden superior son funciones que operan sobre otras funciones, ya sea tomándolas como argumentos o devolviéndolas.


package main

import "fmt"


func sum(x, y int) int {

return x + y

}

func partialSum(x int) func(int) int {

return func(y int) int {

return sum(x, y)

}

}

func main() {

partial := partialSum(3)

fmt.Println(partial(7))

}


En el programa anterior, la función de suma parcial devuelve una función de suma que toma dos argumentos int y devuelve un argumento int.


package main

import "fmt"


func squareSum(x int) func(int) func(int) int {

return func(y int) func(int) int {

return func(z int) int {

return x*x + y*y + z*z

}

}

}

func main() {

// 5*5 + 6*6 + 7*7

fmt.Println(squareSum(5)(6)(7))

}


En el programa anterior, la firma de la función squareSum que especifica esa función devuelve dos funciones y un valor entero.

Golang también admite definir nuestros propios tipos de funciones.

La versión modificada del programa anterior con tipos de funciones como se muestra a continuación:


package main

import "fmt"


type First func(int) int

type Second func(int) First


func squareSum(x int) Second {

return func(y int) First {

return func(z int) int {

return x*x + y*y + z*z

}

}

}


func main() {

// 5*5 + 6*6 + 7*7

fmt.Println(squareSum(5)(6)(7))

}



Google Carbon el remplazo de C++


Google anuncio el nuevo lenguaje que remplazará a C++. Según google, es muy difícil que Rust remplace a C++, por lo tanto él invento Carbon.  Por ahí, se olvida que hace varios años, google había dicho que Go remplazaría a C++, pero bueno. A mi entender, estas cosas suceden no se pueden planear, es decir, veo muy difícil que de forma planeada, un lenguaje remplace a otro, porque esto depende mucho de las personas que usan el lenguaje y no del lenguaje en sí. De esta manera, un lenguaje toma el lugar de otro, no porque una empresa lo diga, sino porque programador a programador deciden migrar. 

Si van a el github de Carbon, no vamos a encontrar mucha información. Solo algunos ejemplos, veamos uno : 


Como objetivos del lenguaje, el principal es remplazar a C++. C++ sigue siendo el lenguaje de programación dominante para el software de rendimiento crítico, con bases de código e inversiones masivas y crecientes. Sin embargo, está luchando por mejorar y satisfacer las necesidades de los desarrolladores, en gran parte debido a la acumulación de décadas de deuda técnica. Mejorar gradualmente C++ es extremadamente difícil, tanto por la deuda técnica en sí misma como por los desafíos con su proceso de evolución. La mejor manera de abordar estos problemas es evitar heredar el legado de C o C++ directamente y, en su lugar, comenzar con bases de lenguaje sólidas, como un sistema genérico moderno, una organización de código modular y una sintaxis simple y coherente.

Los lenguajes modernos existentes ya brindan una excelente experiencia de desarrollador: Go, Swift, Kotlin, Rust y muchos más. Los desarrolladores que pueden usar uno de estos lenguajes existentes deberían hacerlo. Desafortunadamente, los diseños de estos lenguajes presentan barreras significativas para la adopción y migración desde C++. Estas barreras van desde cambios en el diseño idiomático del software hasta gastos generales de rendimiento.

Carbon es fundamentalmente un enfoque de lenguaje sucesor, en lugar de un intento de evolucionar gradualmente a C++. Está diseñado en torno a la interoperabilidad con C++, así como a la adopción y migración a gran escala para las bases de código y los desarrolladores de C++ existentes. Un lenguaje sucesor de C++ requiere:

  • Coincidencia de rendimiento con C++, una propiedad esencial para nuestros desarrolladores.
  • Interoperabilidad bidireccional transparente con C++, de modo que una biblioteca en cualquier parte de una pila de C++ existente puede adoptar Carbon sin portar el resto.
  • Una curva de aprendizaje suave con una familiaridad razonable para los desarrolladores de C++.
  • Expresividad comparable y compatibilidad con el diseño y la arquitectura del software existente.
  • Migración escalable, con cierto nivel de traducción de fuente a fuente para código C++ idiomático.

Funciones en Go


Una función es un grupo de declaraciones que existen dentro de un programa con el propósito de realizar una tarea específica. En un nivel alto, una función toma una entrada y devuelve una salida.

La función permite extraer bloques de código de uso común en un solo componente.

La función más popular en Go es main(), que se usa en todos los programas y es el punto de entrada de la ejecución. 

Una declaración de función comienza con la palabra clave func, seguida del nombre de la función, un par de paréntesis () que son los parámetros y luego un bloque que contiene el código de la función.

El siguiente ejemplo tiene una función con el nombre SimpleFunction. No toma ningún parámetro y no devuelve valores.


package main

import "fmt"

// SimpleFunction prints a message

func SimpleFunction() {

fmt.Println("Hello World")

}

func main() {

SimpleFunction()

}


Los argumentos o parámetros se especifican después del nombre de la función, entre paréntesis. Puede agregar tantos argumentos como se necesite, simplemente hay que separarlos con una coma.

El siguiente ejemplo tiene una función con dos argumentos de tipo int. Cuando se llama a la función add(), pasamos dos valores enteros (por ejemplo, 20,30).


package main

import "fmt"


// Function accepting arguments

func add(x int, y int) {

total := 0

total = x + y

fmt.Println(total)

}


func main() {

// Passing arguments

add(20, 30)

}


Si las funciones con nombres que comienzan con una letra mayúscula se exportarán a otros paquetes. Si el nombre de la función comienza con una letra minúscula, no se exportará a otros paquetes, pero puede llamar a esta función dentro del mismo paquete.

En este ejemplo, la función add() toma la entrada de dos números enteros y devuelve un valor entero con el nombre de total.

La declaración de devolución es necesaria cuando se declara un valor de devolución como parte de la firma de la función. Es decir, si digo que va devolver un tipo tengo que poner "return" y retornar un valor del tipo declarado. 


package main

import "fmt"


// Function with int as return type

func add(x int, y int) int {

total := 0

total = x + y

return total

}


func main() {

// Accepting return value in varaible

sum := add(20, 30)

fmt.Println(sum)

}


Los tipos de entrada y valor de retorno deben coincidir con la firma de la función. Si modificamos el programa anterior y pasamos algún valor de cadena en el argumento, el programa arrojará una excepción.

Golang permite nombrar los valores de retorno de una función. En este ejemplo nombramos la variable de retorno como area: 


package main

import "fmt"


func rectangle(l int, b int) (area int) {

var parameter int

parameter = 2 * (l + b)

fmt.Println("Parameter: ", parameter)

area = l * b

return // Return statement without specify variable name

}


func main() {

fmt.Println("Area: ", rectangle(20, 30))

}


Dado que la función se declara para devolver un valor de tipo int, la última declaración lógica en el flujo de ejecución debe ser una declaración de devolución que devuelva un valor del tipo declarado.

Las funciones en Golang pueden devolver múltiples valores, lo cual es una característica útil en muchos escenarios prácticos.

Este ejemplo declara una función con dos valores de retorno y la llama desde una función principal.

package main


import "fmt"


func rectangle(l int, b int) (area int, parameter int) {

parameter = 2 * (l + b)

area = l * b

return // Return statement without specify variable name

}


func main() {

var a, p int

a, p = rectangle(20, 30)

fmt.Println("Area:", a)

fmt.Println("Parameter:", p)

}

Dejo link: https://gobyexample.com/functions

sábado, 22 de octubre de 2022

Go by example


Quiero recomendarles esta pagina, que creo que ya la recomendé anteriormente,  pero como no me acuerdo, lo vuelvo a hacer. 

Los que estamos aprendiendo go, nos vienen bien siempre unos ejemplos. 

https://gobyexample.com/

viernes, 21 de octubre de 2022

Transformadores de mónadas en cats parte 5


El uso generalizado de transformadores de mónadas a veces es difícil porque fusionan las mónadas de formas predefinidas. Sin una reflexión cuidadosa, podemos terminar teniendo que desempaquetar y volver a empaquetar mónadas en diferentes configuraciones para operar con ellas en diferentes contextos.

Podemos hacer frente a esto de múltiples maneras. Un enfoque consiste en crear una sola "súper pila" y adherirse a ella en toda nuestra base de código. Esto funciona si el código es simple y en gran medida de naturaleza uniforme. Por ejemplo, en una aplicación web, podríamos decidir que todos los controladores de solicitudes son asíncronos y todos pueden fallar con el mismo conjunto de códigos de error HTTP. Podríamos diseñar un ADT personalizado que represente los errores y usar una fusión Future y Either en todas partes de nuestro código:

sealed abstract class HttpError

final case class NotFound(item: String) extends HttpError

final case class BadRequest(msg: String) extends HttpError

// etc...

type FutureEither[A] = EitherT[Future, HttpError, A]


El enfoque de "súper pila" comienza a fallar en bases de código más grandes y heterogéneas donde las diferentes pilas tienen sentido en diferentes contextos. Otro patrón de diseño que tiene más sentido en estos contextos utiliza transformadores de mónadas como "código de unión" local. Exponemos las pilas no transformadas en los límites del módulo, las transformamos para operarlas localmente y las destransformamos antes de pasarlas. Esto permite que cada módulo de código tome sus propias decisiones sobre qué transformadores usar:


import cats.data.Writer

type Logged[A] = Writer[List[String], A]

// Methods generally return untransformed stacks:

def parseNumber(str: String): Logged[Option[Int]] = util.Try(str.toInt).toOption match {

     case Some(num) => Writer(List(s"Read $str"), Some(num))

     case None => Writer(List(s"Failed on $str"), None)

}

// Consumers use monad transformers locally to simplify composition:

def addAll(a: String, b: String, c: String): Logged[Option[Int]] = {

    import cats.data.OptionT

    val result = for {

        a <- OptionT(parseNumber(a))

        b <- OptionT(parseNumber(b))

        c <- OptionT(parseNumber(c))

    } yield a + b + c

    result.value

}

// This approach doesn't force OptionT on other users' code:

val result1 = addAll("1", "2", "3")

// result1: Logged[Option[Int]] = WriterT(

//    (List("Read 1", "Read 2", "Read 3"), Some(6))

// )

val result2 = addAll("1", "a", "3")

// result2: Logged[Option[Int]] = WriterT(

//    (List("Read 1", "Failed on a"), None)

// )


Desafortunadamente, no existen enfoques únicos para trabajar con transformadores de mónadas. El mejor enfoque para usted puede depender de muchos factores: el tamaño y la experiencia de su equipo, la complejidad de su código base, etc. Es posible que deba experimentar y recopilar comentarios de colegas para determinar si los transformadores de mónadas son una buena opción.

martes, 18 de octubre de 2022

Transformadores de mónadas en cats parte 4

Muchas mónadas en Cats se definen usando el transformador correspondiente y la mónada Id. Esto es tranquilizador ya que confirma que las API para mónadas y transformadores son idénticas. Reader, Writer, y State se definen de esta manera:

type Reader[E, A] = ReaderT[Id, E, A] 

type Writer[W, A] = WriterT[Id, W, A]

type State[S, A] = StateT[Id, S, A]

En otros casos, los transformadores de mónadas se definen por separado de sus correspondientes mónadas. En estos casos, los métodos del transformador tienden a reflejar los métodos de la mónada. Por ejemplo, OptionT define getOrElse, y EitherT define fold, bimap, swap y otros métodos útiles.

domingo, 16 de octubre de 2022

Transformadores de mónadas en cats parte 3


Como vimos anteriormente, podemos crear pilas de mónadas transformadas utilizando el método de apply del transformador  o la sintaxis pure :

 

// Create using apply:

val errorStack1 = OptionT[ErrorOr, Int](Right(Some(10)))

// errorStack1: OptionT[ErrorOr, Int] = OptionT(Right(Some(10)))

// Create using pure:

val errorStack2 = 32.pure[ErrorOrOption]

// errorStack2: ErrorOrOption[Int] = OptionT(Right(Some(32)))


Una vez que hayamos terminado con una pila de transformadores de mónadas, podemos desempaquetarla usando el método value. Esto devuelve la pila sin transformar. Entonces podemos manipular las mónadas individuales de la manera habitual:


// Extracting the untransformed monad stack:

errorStack1.value

// res4: ErrorOr[Option[Int]] = Right(Some(10))

// Mapping over the Either in the stack:

errorStack2.value.map(_.getOrElse(-1))

// res5: Either[String, Int] = Right(32)


Cada llamada a value desempaqueta un solo transformador de mónada. Es posible que necesitemos más de una llamada para descomprimir por completo una gran pila. Por ejemplo:


futureEitherOr

// res6: FutureEitherOption[Int] = OptionT(

// EitherT(Future(Success(Right(Some(42))))))

val intermediate = futureEitherOr.value

// intermediate: FutureEither[Option[Int]] = EitherT(Future(Success(Right(Some(42)))))

val stack = intermediate.value

// stack: Future[Either[String, Option[Int]]] = Future(Success(Right(Some(42))))

Await.result(stack, 1.second)

// res7: Either[String, Option[Int]] = Right(Some(42))

sábado, 15 de octubre de 2022

Transformadores de mónadas en cats parte 2


Por convención, en Cats una mónada Foo tendrá una clase transformadora llamada FooT. De hecho, muchas mónadas en Cats se definen combinando un transformador de mónadas con la mónada Id. Concretamente, algunas de las instancias disponibles son:

  • cats.data.OptionT for Option;
  • cats.data.EitherT for Either;
  • cats.data.ReaderT for Reader;
  • cats.data.WriterT for Writer;
  • cats.data.StateT for State;
  • cats.data.IdT for the Id monad.

Todos estos transformadores de mónadas siguen la misma convención. El transformador en sí representa la mónada interna en una pila, mientras que el primer parámetro de tipo especifica la mónada externa. Los parámetros de tipo restantes son los tipos que hemos usado para formar las mónadas correspondientes.

Por ejemplo, nuestro tipo ListOption del ejemplo anterior es un alias para OptionT[List, A] pero el resultado es efectivamente una List[Option[A]]. En otras palabras, construimos pilas de mónadas de adentro hacia afuera:

type ListOption[A] = OptionT[List, A]

Muchas mónadas y todos los transformadores tienen al menos dos parámetros de tipo, por lo que a menudo tenemos que definir alias de tipo para etapas intermedias. Por ejemplo, supongamos que queremos envolver Either alrededor de Option. Option es el tipo más interno, por lo que queremos usar el transformador de mónada OptionT. Necesitamos usar Either como el primer parámetro de tipo. Sin embargo, Either dos parámetros de tipo y las mónadas solo tienen uno. Necesitamos un alias de tipo para convertir el constructor de tipo a la forma correcta:

// Alias Either to a type constructor with one parameter:
type ErrorOr[A] = Either[String, A]

// Build our final monad stack using OptionT:
type ErrorOrOption[A] = OptionT[ErrorOr, A]

ErrorOrOption es una mónada, al igual que ListOption. Podemos usar pure, map y flatMap como de costumbre para crear y transformar instancias:

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

val a = 10.pure[ErrorOrOption]
// a: ErrorOrOption[Int] = OptionT(Right(Some(10)))

val b = 32.pure[ErrorOrOption]
// b: ErrorOrOption[Int] = OptionT(Right(Some(32)))

val c = a.flatMap(x => b.map(y => x + y))
// c: OptionT[ErrorOr, Int] = OptionT(Right(Some(42)))

Las cosas se vuelven aún más confusas cuando queremos apilar tres o más mónadas. Por ejemplo, vamos a crear un Future de Either de Option. Una vez más, construimos esto de adentro hacia afuera con una OptionT de una EitherT de Future. Sin embargo, no podemos definir esto en una sola línea porque EitherT tiene tres parámetros de tipo:

case class EitherT[F[_], E, A](stack: F[Either[E, A]]) {
    // etc...
}

Los tres parámetros de tipo son los siguientes:
  • F[_] es la mónada externa en la pila (Either es la interna);
  • E es el tipo de error para el Either;
  • A es el tipo de resultado para Either.
Esta vez creamos un alias para EitherT que corrige Future y Error y permite que A varíe:

import scala.concurrent.Future
import cats.data.{EitherT, OptionT}

type FutureEither[A] = EitherT[Future, String, A]
type FutureEitherOption[A] = OptionT[FutureEither, A]

Nuestra gigantesca pila ahora compone tres mónadas y nuestros métodos map y flatMap atraviesan tres capas de abstracción:

import cats.instances.future._ // for Monad
import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._

val futureEitherOr: FutureEitherOption[Int] =
    for {
        a <- 10.pure[FutureEitherOption]
        b <- 32.pure[FutureEitherOption]
    } yield a + b

viernes, 14 de octubre de 2022

[eBook] Microservices: From Design to Deployment

 

EBOOK

Ebook: Microservices: From Design to Deployment | Learn about building & deploying microservices

You probably already know that developing your apps using a microservices approach makes them more flexible, more stable, and faster. This eBook provides a useful set of resources to get started in your transition to microservices.

In this free eBook you will learn:

  • When and why it makes sense to adopt microservices
  • How to implement an API gateway to route traffic to microservices
  • Pros and cons of different service discovery patterns
  • Different strategies for refactoring a monolith to microservices

Learning go


Dejo este link que es una recopilación de recursos sobre go

https://go.dev/doc/#learning

martes, 11 de octubre de 2022

Transformadores de mónadas en cats


Las mónadas son como los burritos, lo que significa que una vez que adquieres un sabor, te encontrarás regresando a ellas una y otra vez. Esto no está exento de problemas. Así como los burritos pueden inflar la cintura, las mónadas pueden inflar la base del código a través de comprensiones anidadas. Imagina que estamos interactuando con una base de datos. Queremos buscar un registro de usuario. El usuario puede estar presente o no, por lo que devolvemos un Option[User]. Nuestra comunicación con la base de datos podría fallar por muchas razones (problemas de red, problemas de autenticación, etc.), por lo que este resultado se envuelve en un Either, dándonos un resultado final de Either[Error, Option[User]]. Para usar este valor, debemos anidar llamadas flatMap (o de manera equivalente, para comprensiones):

def lookupUserName(id: Long): Either[Error, Option[String]] =

    for {

        optUser <- lookupUser(id)

    } yield {

        for { user <- optUser } yield user.name

   }


Cada transformador de mónadas es un tipo de datos, definido en cats.data, que nos permite envolver pilas de mónadas para producir nuevas mónadas. Usamos las mónadas que hemos construido a través de la clase de tipo Monad. Los principales conceptos que tenemos que cubrir para entender los transformadores de mónadas son:

  • las clases de transformadores disponibles;
  • cómo construir pilas de mónadas usando transformadores;
  • cómo construir instancias de una pila de mónadas; y
  • cómo separar una pila para acceder a las mónadas envueltas.
Continuará... 

lunes, 10 de octubre de 2022

Probando tus test con stryker4s


Professor X: For someone who hates mutants... you certainly keep some strange company.

William Stryker: Oh, they serve their purpose... as long as they can be controlled.


Si tenemos que probar los test de mi aplicación entramos en una recursividad que nos puede mantener haciendo test de test hasta los fines de los tiempos. 

Pero pero esta copado, saber si realmente tus test prueban, lo que uno desea probar y no esta bueno darlo por sentado y tampoco es productivo, ir debugeando test por test para ver si realmente prueba. 

Por ende una herramienta como stryker4s esta buena, lo que hace esto es mutar tu código un toque y espera que los test fallen, si los test no fallan esa parte del código que muto no esta siendo testeada. 

La idea, me parece de lo más lógica, y no entiendo bien como no se nos ocurrio antes :( 

Para utilizarlo agregamos la siguiente dependencia en nuestro proyecto sbt : 

addSbtPlugin("io.stryker-mutator" % "sbt-stryker4s" % stryker4sVersion)

Después de agregar el plugin, se puede usar Stryker4s ejecutando sbt stryker en la raíz del proyecto y la magia comienza. 

No solo este framework existe para scala, tambien lo podemos usar en C# o javascript. 

Dejo link : 

https://stryker-mutator.io/

https://stryker-mutator.io/docs/stryker4s/getting-started/

https://github.com/stryker-mutator/stryker4s

viernes, 7 de octubre de 2022

Definir monadas con Cats

Podemos definir una Monad para un tipo personalizado proporcionando implementaciones de tres métodos: flatMap, pure y un método llamado tailRecM. Aquí hay una implementación de Monad para Option como ejemplo:


import cats.Monad

import scala.annotation.tailrec


val optionMonad = new Monad[Option] {

def flatMap[A, B](opt: Option[A]) (fn: A => Option[B]): Option[B] = opt flatMap fn

def pure[A](opt: A): Option[A] = Some(opt)


@tailrec

def tailRecM[A, B](a: A) (fn: A => Option[Either[A, B]]): Option[B] =

     fn(a) match {

          case None => None

          case Some(Left(a1)) => tailRecM(a1)(fn)

          case Some(Right(b)) => Some(b)

     }

}

El método tailRecM es una optimización utilizada en Cats para limitar la cantidad de espacio de pila que consumen las llamadas anidadas a flatMap. viene la técnica de un artículo de 2015 del creador de PureScript, Phil Freeman. El método se llama recursivamente a sí mismo hasta que el resultado de fn devuelve un Right.

Supongamos que queremos escribir un método que llama a una función hasta que la función indica que debe detenerse. los devolverá una instancia de mónada porque, como sabemos, las mónadas representan secuenciación y muchas mónadas tienen alguna noción de fin de ejecución.

Podemos escribir este método en términos de flatMap.

import cats.syntax.flatMap._ // For flatMap

def retry[F[_]: Monad, A](start: A)(f: A => F[A]): F[A] =

     f(start).flatMap{ a =>

          retry(a)(f)

}


Desafortunadamente, no es seguro. Funciona para entradas pequeñas.


import cats.instances.option._

retry(100)(a => if(a == 0) None else Some(a - 1))

// res1: Option[Int] = None


pero si intentamos una entrada grande, obtenemos un StackOverflowError.


retry(100000)(a => if(a == 0) None else Some(a - 1))


En su lugar, podemos reescribir este método usando tailRecM.


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

def retryTailRecM[F[_]: Monad, A](start: A)(f: A => F[A]): F[A] =

Monad[F].tailRecM(start){ a =>

     f(a).map(a2 => Left(a2))

}


Ahora se ejecuta con éxito sin importar cuántas veces recurramos.


retryTailRecM(100000)(a => if(a == 0) None else Some(a - 1))

// res2: Option[Int] = None


Es importante tener en cuenta que tenemos que llamar explícitamente a tailRecM. No hay una transformación de código que convierta el código recursivo sin cola en código recursivo de cola que use tailRecM. Sin embargo, la clase de tipos Monad proporciona varias utilidades que facilitan la escritura de este tipo de métodos.

Por ejemplo, podemos reescribir retry en términos de iterateWhileM y no tenemos que llamar explícitamente a tailRecM.


import cats.syntax.monad._ // for iterateWhileM

def retryM[F[_]: Monad, A](start: A)(f: A => F[A]): F[A] = start.iterateWhileM(f)(a => true)

retryM(100000)(a => if(a == 0) None else Some(a - 1))

// res3: Option[Int] = None


Todas las mónadas incorporadas en Cats tienen implementaciones recursivas de cola de tailRecM, aunque escribir una para mónadas personalizadas puede ser un desafío... 

lunes, 3 de octubre de 2022

Cambio de licencia de Akka


Les cuento, hace unas semanas me puse a estudiar Akka, que es un conjunto de herramientas para escribir aplicaciones distribuidas concurrentes basadas en el modelo actor, fue creado hace trece años por Jonas Bonér, fundador y director ejecutivo de Lightbend. La compañía ha anunciado recientemente un nuevo modelo de licencia Akka que ha cambiado de Apache 2.0 de código abierto a Business Source License (BSL) 1.1.

Esto significa que el código base de Akka solo es de uso gratuito para el desarrollo y en sistemas que no son de producción. La licencia BSL 1.1 permite que los proyectos elijan otra licencia después de un período de tiempo específico que reemplazará la licencia BSL 1.1. Lightbend decidió revertir la licencia BSL 1.1 a una licencia Apache 2.0 después de tres años. Varios otros proyectos de código abierto, como Couchbase, Sentry y MariaBD, se han movido a la licencia BSL 1.1 en los últimos años.

La nueva licencia afecta a todas las empresas con ingresos superiores a 25 millones de dólares que deseen utilizar la última versión de Akka. Las versiones actuales aún se pueden usar libremente, pero solo se publican actualizaciones y parches de seguridad críticos para la versión 2.6.x bajo la licencia actual de Apache 2.0 hasta septiembre de 2023. Los proyectos de código abierto pueden comunicarse con Lightbend para obtener una subvención de uso adicional.

En resumen, me parece que indicios de que el producto no va por buen camino y desean exprimirlo. 

Que piensan de estos cambios de licencia?

Dejo link: https://www.infoq.com/news/2022/09/akka-no-longer-open-source/#:~:text=The%20company%20has%20recently%20announced,and%20on%20non%2Dproduction%20systems.

sábado, 1 de octubre de 2022

Primeros pasos con Akka


Ante de empezar veamos este link, para ponernos en contexto : 
https://emanuelpeg.blogspot.com/2022/09/por-que-usar-akka.html

Implementamos el trait Actor en una clase simple llamada Contador. 

import akka.actor.Actor


class Counter extends Actor {

  var count = 0

  def receive = {

    case "incr" ⇒ count += 1

    case "get"  ⇒ sender() ! count

  }

}


Este tiene un campo count y debe implementar el método de receive del trait Actor y debe devolver una función parcial. Esa es la función parcial con una declaración de pattern matching con el mensaje entrante. 

Entonces, si recibimos un mensaje con una cadena incr, entonces queremos incrementar el contador. Lo que hacemos aquí. Si queremos que tenga estado, queremos permitir que otros actores averigüen cuál es el valor del contador. Y por lo tanto los actores pueden enviar mensajes a direcciones que conocen. Las direcciones en Akka están modeladas por el tipo ActorRef. Entonces, escribimos otro caso, y si obtenemos una tupla con la cadena get, y algo con tipo ActorRef, que llamamos cliente, entonces podemos enviar al cliente, el conteo como un mensaje. 

El operador de signo de exclamación que se usa para enviar mensajes y se pronuncia tell en Akka. Cada actor conoce su propia dirección. Es un ActorRef llamado self. Y está implícitamente disponible. ActorRef es una cláusula abstracta que tiene un método llamado tell o signo de exclamación. Y si usa el signo de exclamación que es la buena sintaxis de Scala, tell que es más para Java. Esto requiere un argumento implícito. 

El argumento implícito de tipo ActorRef se recoge del entorno. Entonces, si usa el operador Tell dentro de un actor, implícitamente seleccionará al remitente como la referencia propia de ese actor. Dentro del actor receptor, este valor está disponible como remitente, que devuelve la referencia del actor que ha enviado el mensaje que se está procesando actualmente.

La referencia junto con el mensaje al actor receptor debe hacerse explícitamente en otras implementaciones de actor como erlang. Pero como es un patrón tan común, existe el metodo sender()  que es la referencia al que me envia el mensaje.

Un actor puede hacer más cosas que simplemente enviar mensajes. Puede crear otros actores y puede cambiar su comportamiento. Para acceder a estas funciones, necesitamos conocer el contexto del actor. 

El tipo de actor en sí solo tiene el método de recepción, por lo que solo describe el comportamiento del actor. La maquinaria de ejecución la proporciona el contexto del actor. 

Se tienes acceso al contexto dentro del actor simplemente diciendo contexto. Ahora veamos esto en acción. Podemos reformular nuestro contador en uno que no tenga barra. Será un objeto con estado sin una variable explícita. 

Primero, necesitamos definir un método, que nos dé un comportamiento. Este método toma un argumento, cuál es el estado actual del contador. Empezamos con el contador en cero. Si recibimos un mensaje incr aquí, cambiamos nuestro comportamiento para que sea el de un contador de n más 1.  Dentro de este comportamiento del contador, en cualquier momento podemos obtener una solicitud, donde solo respondemos con el valor actual de los contadores. 

import akka.actor.Actor


class Counter extends Actor {

  def counter(n: Int): Receive = {

    case "incr" ⇒ context.became(counter(n + 1))

    case "get"  ⇒ sender() ! n

  }

  def receive = counter(0)

}

Puede ver aquí que se parece un poco a una función recursiva de cola, porque la llamamos dentro de sí misma, pero es asíncrona, porque context.become evalúa lo que se proporciona aquí solo cuando se procesa el siguiente mensaje. 

Esto es funcionalmente equivalente a la versión anterior del contador, pero tiene algunas ventajas. En primer lugar, solo hay un lugar donde se cambia el estado, y ese es el llamado a convertirse, y esto es muy explícito. La otra es que el estado se limita al comportamiento actual, por lo que no hay ninguna variable que podamos dejar en algún lugar en un estado desconocido. Siempre tenemos el estado actual aquí. 

Lo último que uno puede hacer fundamentalmente con los actores es crearlos y detenerlos. El contexto del actor tiene métodos para eso. Primero, actorOf, una descripción sobre cómo crear un actor y un nombre, y devuelve un ActorRef. Además, cada actor puede detener a otros actores. 

Una cosa a tener en cuenta aquí es que los actores siempre son creados por actores. Esto significa que naturalmente forman una jerarquía porque un actor es creado exactamente por otro actor. Y otra cosa a tener en cuenta es que detener aquí a menudo se aplica a la auto referencia, lo que significa que el actor decide que quiere terminar. Probemos todo esto en una aplicación ejecutable. 


import akka.actor.Actor

import akka.actor.Props


class CounterMain extends Actor {

  val counter = context.actorOf(Props[Counter], "counter")


  counter ! "incr"

  counter ! "incr"

  counter ! "get"


  def receive = {

    case count: Int ⇒

      println(s"count was $count")

      context.stop(self)

  }

}


Por supuesto, una aplicación también se modela como un actor en este modelo. Entonces lo llamamos CounterMain. Este es un actor y usa el contexto actorOf para crear un contador. Podemos decir Props Counter para crear un actor que no tome argumentos de constructor. Y como todas las cosas buenas. Necesita un nombre, lo llamamos contador en este caso. 

Luego enviamos mensajes a este contador, en este caso lo enviamos incr tres veces y luego lo enviamos get. Recuerde que enviar con el operador de tell dentro de un actor seleccionará la referencia propia como remitente. Y como el contador responderá, lo recibiremos en este actor. Aquí en nuestro comportamiento, recibimos la cuenta, que es de tipo int. Lo imprimiremos y luego nos detendremos. 

Para ejecutar esto, debemos decirle a Eclipse cómo ejecutar las aplicaciones de actor porque actualmente aún no lo sabe. Entonces creamos una configuración de ejecución. Utiliza la clase principal akka.Main que espera como primer argumento, el nombre de la clase, el nombre completo de la clase de actor que se va a instanciar. Entonces, si ejecutamos esto. Veremos que la cuenta imprime tres. Esta impresión se hizo con esta línea, así que recibimos el conteo del contador y recibimos tres porque incrementamos el contador tres veces.

Ahora hemos visto todas las cosas básicas que pueden hacer los actores. Cuando reciben un mensaje, pueden enviar mensajes, crear actores. Y pueden cambiar su comportamiento para el próximo mensaje,  este es el modelo actor de computación.

Dejo link : https://akka.io/