EBOOK | |
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.
| |
EBOOK | |
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.
| |
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:
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 :
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...
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?
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/
EBOOK | |
Companies that begin working with containerized applications often aren’t prepared for the challenge of container networking. In this new eBook, Michael Hausenblas, a member of Red Hat’s OpenShift team, provides a detailed look at the many challenges of container networking, container orchestration, and service discovery, and shares several available solutions. Along the way, you’ll learn the capabilities of many open source tools, including Kubernetes.
| |
El uso de Concurnas ayuda a las organizaciones a aprovechar al máximo el hardware moderno de GPU y CPU multinúcleo al facilitar la programación concurrente y en paralela.
Concurnas es interoperable con Java (y otros lenguajes JVM): las organizaciones pueden aprovechar el software Java existente en un entorno concurrente.
En comparación con otros lenguajes, con Concurnas los desarrolladores necesitan escribir menos líneas de código. Ese código es más seguro y más fácil de probar, lo que aumenta la calidad y aumenta la productividad de los desarrolladores.
Entre las caracteristicas de Corcunas podemos nombrar:
Fácil de aprender: Inspirado en lenguajes como Python y Java, Concurnas es un lenguaje de tipado estatico que utiliza la inferencia de tipos con una sintaxis fácil de aprender.
Alto rendimiento: Concurnas es un lenguaje compilado que se ejecuta en la máquina virtual de Java y, como tal, tiene acceso al increíble rendimiento que ofrece la JVM.
Fácil de escalar: Concurnas hace posible usar el mismo código desde un pequeño prototipo de investigación hasta una solución de producción distribuida a escala planetaria
Open Source: Concurnas es una tecnología de código abierto, libre de usar y modificar. Con desarrollo continuo y soporte comercial proporcionado por Concurnas Ltd.
La verdad que quiero poner un ejemplo pero hay tantos en la pagina y tan interesantes que sería incompleto, igual tiro uno para que tengan idea:
//factorial
def factorial(i int) int {
match(i){
0 => 1
n => n * factorial(n-1)
}
}
Como se ve, es un lenguaje completo que le viene bien cualquier cosa, si queres podes programar imperativo, orientado a objeto, funcional, lo que pinte. Eso esta bueno pero es un problema si no entendes todos los conceptos. Un lenguaje de programación bien escrito te debería hacer fácil hacer las cosas bien y difícil hacer las cosas mal. Tanta libertad puede ser una desventaja. Esto ultimo es mi opinión.
Dejo link: https://concurnas.com/
En particular, hablaremos sobre cómo los actores pueden crear otros actores. Cómo cambian su comportamiento con el tiempo, cómo intercambian mensajes.
Antes de sumergirnos en lo que son los Actores, primero echemos un vistazo a por qué queremos investigarlos y de dónde provienen.
El formalismo Actor fue publicado por primera vez por Hewitt, Bishop y Steiger en 1973. Y lo que querían lograr es crear un modelo en el que puedan formular los programas para su investigación de inteligencia artificial.
Uno de los estudiantes de Hewitt publicó su tesis doctoral en 1986. Gul Agha formuló lenguajes de actores, cómo escribir programas de actores, cómo razonar sobre ellos. Describió los patrones de comunicación entre los Actores y cómo usarlos para resolver problemas reales.
En el mismo año, Ericsson comenzó a desarrollar un lenguaje de programación llamado Erlang. Este es un lenguaje de programación puramente funcional, cuyo modelo de concurrencia se basa en Actors. Y que luego se utilizó posteriormente en productos comerciales.
En 1995, Ericsson presentó su nueva plataforma de telecomunicaciones. El cual tuvo un gran éxito. Hubo alrededor de 30 milisegundos de tiempo de inactividad por año. Esta solidez y resiliencia fue posible gracias al modelo Actor.
Inspirado por el éxito de Erlang de Ericsson, Philipp Haller agregó Actors a la biblioteca estándar de Scala en 2006.
Luego, Jonas Bonér fue influenciado por Erlang, así como por Scala Actors para crear Akka en 2009. Akka es un framework de la JVM con APIs para Java y Scala que hace que el modelo Actor esté disponible para una amplia gama de desarrolladores.
Los actores son aplicables en una amplia gama de problemas. En el pasado, los programas se volvieron más rápidos al usar la próxima generación de CPU. Esto se debió a una frecuencia central cada vez mayor. Pero esto se detuvo alrededor del año 2005. Desde entonces, las CPU no son cada vez más rápidas, sino más anchas. Esto significa que, en lugar de hacer que un núcleo de ejecución sea más potente, se incorporan múltiples núcleos de este tipo dentro de un chip, accediendo a la memoria compartida.
Y en algunos de estos, los núcleos incluso están virtualizados, de modo que un núcleo de ejecución física puede albergar múltiples subprocesos de ejecución lógica. Hay diferentes formas de beneficiarse de estas CPU más amplias.
La primera es que puede ejecutar varios programas en paralelo en la misma computadora. Esto se llama multitarea y se ha hecho desde las primeras versiones de Unix. Pero si tiene un solo programa que necesita ejecutar más rápido y no tiene más remedio que ejecutar partes del mismo programa en paralelo. Y esto se llama subprocesos múltiples.
Para lograr subprocesos múltiples, su programa debe estar escrito de una manera diferente a la forma secuencial tradicional.
La diferencia entre ejecutar programas separados en paralelo y usar subprocesos del mismo programa en paralelo es que estos subprocesos colaboran en una tarea común.
Y si piensas en un grupo de personas haciendo algo juntas, necesitarán sincronizar sus acciones. De lo contrario, se pisan. Lo mismo puede suceder en un programa de subprocesos múltiples. Hay una nueva clase de errores y problemas que te esperan allí. Y esta es la razón por la cual los programas deben formularse de manera diferente para estar listos para subprocesos múltiples. Para entender lo que eso significa, echemos un vistazo a una cuenta bancaria:
class BankAccount {
private val balance = 0
def deposit(amount: Int): Unit = if (amount > 0) balance = balance + amount
def withdraw(amount: Int): Int =
if (amount > 0 && amount < balance) {
balance = balance - amount
balance
} else throw new Error("Fondo insuficiente")
}
Contiene un campo para el saldo y tiene dos métodos para depositar una cantidad y retirar una cantidad.
Ahora veamos el método de retiro en detalle. Entonces, echemos un vistazo a lo que sucede si dos subprocesos ejecutan este código al mismo tiempo.
Digamos que este es el subproceso 1 y este es el subproceso 2. Son ejecutados por diferentes CPU, por lo que pueden ejecutarse en paralelo.
Entran al método con una cantidad. Digamos que el hilo 1 quiere retirar 50 francos suizos, por ejemplo. Y el hilo 2 quiere retirar 40. Lo primero que harán ambos será leer el saldo actual. En ambos casos, digamos el saldo ahora mismo es 80, por lo que ambos verán 80. Luego, ambos ingresarán la declaración if, del chequeo. ¿La cantidad es positiva? Sí, lo es. ¿Y hay realmente suficientes fondos en la cuenta? Sí hay. Así ambos continuarán. Ellos calcularán el nuevo saldo.
El nuevo saldo en el primer subproceso será 30, y en el segundo será 40. Lo siguiente que harán los subprocesos será escribir el nuevo saldo en el campo de saldo de la cuenta bancaria, objeto.
El primer subproceso escribirá 30 y el segundo escribirá 40. Esto está claramente en conflicto, porque solo uno de los escritores puede ganar al final. El que viene en último lugar, sobrescribirá al que vino antes.
Este es el primer problema que vemos aquí. Es que en realidad se pierde una de las actualizaciones del saldo.
El otro problema es que se viola el invariante de la cuenta bancaria. En eso hemos retirado 50 y 40 francos suizos que son 90 en total, y el saldo era solo 80, lo que no debería haber sido posible. Uno de los hilos debería haber fallado. Y eso, eso no sucedió es el otro problema con este código.
Ahora, ¿qué podemos hacer para solucionar este problema? Necesitamos agregar sincronización. Cuando varios subprocesos trabajan con los mismos datos, necesitan sincronizar sus acciones.
Porque de lo contrario, serían propensos a pisarse. Lo que debemos hacer es asegurarnos de que cuando un subproceso esté trabajando con los datos, los demás se mantengan al margen. Como si pusieras un cartel de no molestar en la puerta de tu hotel.
Entonces, digamos que en este ejemplo estamos viendo el saldo, como los datos que se protegerán. Y lo que tenemos que hacer es poner una valla alrededor, de modo que cuando un subproceso esté trabajando con los datos, digamos el subproceso 1. Que este tenga acceso exclusivo a él. Lo que significa que la transacción 2, si intenta acceder a los datos, en realidad se le negará el acceso en este momento. Y tiene que esperar hasta que la transacción 1 termine con ella. De esta manera, el saldo estará protegido. Y todas las modificaciones realizadas en él se realizan de manera consistente, una tras otra. También decimos serializado.
Las herramientas principales para lograr este tipo de sincronización son lock o mutex. Que es básicamente el mismo concepto que se mostró anteriormente. O un semáforo donde la diferencia es que múltiples pero solo un número definido de subprocesos pueden ingresar a esta región.
En Scala, cada objeto tiene un candado asociado. Al que puede acceder llamando al método sincronizado en él. Y acepta un bloqueo de código que se ejecutará en esta región protegida.
¿Cómo aplicamos esto a la cuenta bancaria para sincronizarla?
def withdraw(amount: Int): Int = this.synchronized {
if (amount > 0 && amount < balance) {
balance = balance - amount
balance
} else throw new Error("Fondo insuficiente")
}
Bueno, aquí tenemos el método de retiro. Y si lo ponemos todo dentro de un bloque sincronizado, entonces leemos el balance aquí. Realizar el cheque y volver a escribirlo, todo se hará como una acción atómica. Que no puede ser perturbado por otro hilo que ejecuta retirar, al mismo tiempo. Ese otro hilo tendrá que esperar.
Pero, ¿debemos sincronizar también aquí en el método de depósito? El método de depósito también modifica el saldo. Y si no esta sincronizado, entonces podría modificarlo sin protección. Y una vez que el retiro devuelve el saldo aquí, anularía la anulación de la actualización realizada por depósito al mismo tiempo.
Esto es para ilustrar que todos los accesos al saldo deben estar sincronizados, y no solo el que hemos demostrado que es problemático.
Ahora, intentemos transferir algo de dinero de una cuenta bancaria a otra.
Lo que debemos hacer es sincronizar ambos objetos, de modo que estén en un estado consistente. Porque de lo contrario, alguien leyendo el saldo de las cuentas podría encontrar el dinero en fuga. Retiramos de una cuenta. Depositamos después en otro.
Durante este tiempo, el dinero básicamente no está en ninguna parte. Y si la invariante que debe cumplirse es que la suma de from y to debe ser la misma, esto se violará. Por lo tanto, necesitamos sincronizar.
Entonces, primero tomamos el bloqueo en la cuenta de origen. Luego tomamos el bloqueo en la cuenta. Ahora estamos seguros de que ningún otro hilo puede modificar estas dos cuentas, pero nosotros podemos. Una propiedad de los bloqueos en Scala es que son reentrantes, lo que significa que el mismo subproceso puede tomarlo dos veces o cualquier cantidad de veces.
Hay un problema con este código, ya que introduce la posibilidad de un interbloqueo.
Digamos que un hilo quiere transferir de la cuenta A a la cuenta B en un hilo.
Y otro hilo intenta transferir en la dirección opuesta. Si ambos comienzan al mismo tiempo, toman el primer candado, este en la cuenta A, este en la cuenta B.
Luego van a tomar la otra cerradura. Este no logrará tomar el bloqueo, porque ya fue tomado por el otro subproceso para la cuenta B. Lo mismo es cierto para la cuenta A en el otro subproceso.
Esto significa que ninguno de los subprocesos puede progresar, ambos se atascarán para siempre. Porque no hay posibilidad de que ninguno de los dos ceda el candado que ya tienen. Esto se llama interbloqueo o deadlock
Hay soluciones. Por ejemplo, llevar siempre las transacciones en el mismo orden. Debe definir un orden para las cuentas, etc. y entonces podrías potencialmente resolver esto.
Descubrirá que la mayoría de las veces hay soluciones de ese tipo, pero se agregarán y harán que su código sea mucho más complicado con el tiempo. Y en este caso es sencillo, porque ambas son cuentas bancarias. Pero, ¿qué sucede si desea que colaboren objetos que no provienen de la misma base de código, por ejemplo, en los que no puede modificar?
En ese sentido, sería mucho mejor que nuestros objetos no requirieran sincronización o bloqueo. Porque el bloqueo es lo que realmente hace que ocurra el interbloqueo.
El otro problema con el bloqueo es que es malo para la utilización de la CPU. Si hay otros subprocesos para ejecutar, el sistema operativo los ejecutará. Pero de lo contrario, la CPU estará inactiva. Y despertarlo o hacer que un subproceso vuelva a ejecutarse cuando otro subproceso lo ha interrumpido lleva mucho tiempo. Por lo tanto, su programa se ejecutará más lentamente si usa el bloqueo.
Otro problema con el bloqueo de objetos es que la comunicación sincrónica une al emisor y al receptor con bastante fuerza. Porque el remitente debe esperar hasta que el receptor esté listo. Entonces, si llamo a una cuenta bancaria que está sincronizada, esa cuenta bancaria me bloqueará hasta que esté lista. Los objetos que no bloquean son exactamente lo que son los Actores.
Los actores representan objetos y el modelo de actor describe cómo estos objetos interactúan, y todo esto está inspirado en cómo nos organizamos los humanos y respeta especialmente las leyes de la física. ¿Qué quiero decir con esto? Digamos que una persona sin reloj que quiere saber la hora actual y hay otra persona con un reloj. Ahora la primera persona se hará la pregunta, ¿qué hora es? Y la segunda persona podría responder, son las 12:43. Este es un intercambio simple entre dos humanos y todos hemos hecho algo así antes, así que espero que todos puedan identificarse con este ejemplo. Pero hay más de lo que es visible en la superficie. En primer lugar, tomamos nota de que transmitimos información hablando y escuchando las palabras. Eso significa que la primera persona envía un mensaje a la segunda persona. La segunda persona luego piensa un poco, mira el reloj y responde con otro mensaje, viajando hacia atrás, en ondas de sonido. Las dos cualidades fundamentales aquí son, en primer lugar, que se basa únicamente en mensajes y, en segundo lugar, los mensajes tardan en viajar de un objeto a otro. Es útil pensar en los actores no como estos objetos abstractos a los que llamas métodos. Pero en lugar de visualizarlos como personas que hablan entre sí. Una cosa también a tener en cuenta es que cuando los humanos hablamos, no nos metemos en el cerebro de los demás, por ejemplo, leyendo la mente. nos basamos únicamente en los mensajes, y eso es una parte muy fundamental de los actores. Más formalmente, un actor, tal como lo definen Hewitt, Bishop y Steiger, es un objeto con identidad. Y también tiene un comportamiento, que ya hemos encontrado, y solo interactúa mediante el paso de mensajes. Lo que sigue siendo compatible con la definición de objeto normal, lo especial de los actores es que su paso de mensajes siempre es asíncrono.
Tenemos un actor, llamémoslo Actor A. Y este quiere enviar un mensaje al actor B. El actor A enviará el mensaje y luego puede continuar haciendo lo que quiera después de eso, sin tener que esperar por el mensaje, este viajará a B y será procesado por B, especialmente el procesamiento ocurre en un contexto diferente, en un momento posterior diferente según lo determine el sistema en el que se ejecutan los actores.
Esta es la propiedad de las más importante de los actores. Para esto, usamos los trait de actor en Akka. El tipo de actor define un método abstracto que se llama recibir y este recibir devuelve algo del tipo Receive. Recibir es una función parcial de Any a Unit y describe la respuesta del actor a un mensaje.
Cualquier mensaje podría entrar, por lo tanto es Any, y el actor puede hacer muchas cosas, pero al final, no devuelve nada, debido al paso asincrónico del mensaje.
Por ahora vamos a ver solo estos conseptos, y luego los profundizaremos en post posteriores.
Dejo link : https://akka.io/
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value
is Sum -> eval(e.right) + eval(e.left)
else ->
throw IllegalArgumentException("Unknown expression")
}
Cuando evalúas una expresión usando la construcción when, el compilador de Kotlin te obliga a especificar todas las opciones, pero nosotros sabemos que no es posible que haya otra opción.
Tener que agregar siempre el else no es conveniente. Además, si agrega una nueva subclase, el compilador no detectará que algo ha cambiado y va a ir por el else.
Kotlin proporciona una solución a este problema: Sealed classes o clases selladas. Marcas una superclase con el modificador sellado, y eso restringe la posibilidad de crear subclases. Todas las subclases directas deben anidarse en la superclase:
sealed class Expr {
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr): Int =
when (e) {
is Expr.Num -> e.value
is Expr.Sum -> eval(e.right) + eval(e.left)
}
Y listo!
El modificador sealed implica que la clase es abierta por lo tanto, no necesitamos agregar modificador open de forma explícita.
Otra cosa importante es que no puede declarar una interfaz sellada. ¿Por qué? Si pudiera, el compilador de Kotlin no podría garantizar que alguien no pueda implementar esta interfaz en el código Java.
Scala School!: Un tutorial de Scala por Twitter
A Tour of Scala: Tutorial que presenta los conceptos principales de Scala
Scala Overview on StackOverflow: Una lista de preguntas útiles ordenadas por tema
Glossary of Scala and FP terms, para cualquier problema con la terminología..
Scala By Example, para ver más ejemplos que ilustran los conceptos tratados en las conferencias.
Scala Cheatsheet, para una referencia rápida que cubre la sintaxis de scala.
Structure and Interpretation of Computer Programs. Harold Abelson and Gerald J. Sussman. 2nd edition. MIT Press 1996. - [Full text available online].
Programming in Scala. Martin Odersky, Lex Spoon and Bill Venners. 3rd edition. Artima 2016. http://www.artima.com/shop/programming_in_scala_3ed
Programming in Scala. Martin Odersky, Lex Spoon and Bill Venners. 2nd edition. Artima 2010. - [Full text of 1st edition available online].Artima has graciously provided a 25% discount on the 2nd edition of Programming in Scala to all participants of this course. To receive the discount, simply visit http://www.artima.com/shop/programming_in_scala_2ed and during checkout, please use the coupon code: COURSERA-ODERSKY to have the discount applied.
Scala for the Impatient. Cay Horstmann. Addison-Wesley 2012. - [First part available for download.]
Scala in Depth. Joshua D. Suereth. Manning 2012. - [Available for purchase].
Programming Scala. Dean Wampler and Alex Payne. O’Reilly 2009. [Available for purchase].
Generamos nuestro proyecto en : https://start.spring.io/ o como quieran pero tienen que elegir spring web como dependencia.
Si ya funciona el entorno, vamos a la dependencia de spring web y excluimos tomcat y luego agregamos jetty :
implementation ('org.springframework.boot:spring-boot-starter-web') {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
}
implementation 'org.springframework.boot:spring-boot-starter-jetty'
Y listo, cuando inicie spring lo va hacer con un jetty embebido. Van a poder ver la siguiente linea en el log :
2022-09-20 08:06:28.788 INFO 1304813 --- [ main] o.s.b.web.embedded.jetty.JettyWebServer
Imagine que desea definir un elemento View, cuyo estado se puede serializar. Puede que no sea fácil serializar una vista, pero puede copiar todos los datos necesarios en otra clase auxiliar. Declaras la interfaz State que implementa Serializable. La interfaz View declara los métodos getCurrentState y restoreState que se pueden usar para guardar el estado de una vista.
interface State: Serializable
interface View {
fun getCurrentState(): State
fun restoreState(state: State) {}
}
Es útil definir una clase que guarde el estado de un botón en la clase Botón. Veamos cómo se puede hacer en Java (el código Kotlin similar se mostrará en un momento).
/* Java */
public class Button implements View {
@Override
public State getCurrentState() {
return new ButtonState();
}
@Override
public void restoreState(State state) { /*...*/ }
public class ButtonState implements State { /*...*/ }
}
La clase ButtonState que implementa la interfaz State y contiene información específica para Button. En el método getCurrentState, crea una nueva instancia de esta clase. En un caso real, inicializaría ButtonState con todos los datos necesarios.
¿Qué tiene de malo este código? ¿Por qué se obtiene un java.io.NotSerializableException? Eso puede parecer extraño al principio: ButtonState guarda una referencia a Button que no es Serializable.
Todo queda claro cuando recuerdas que en Java, cuando declaras una clase en otra clase, se convierte en una clase interna por defecto. La clase ButtonState del ejemplo almacena implícitamente una referencia a su clase Button externa. Eso explica por qué ButtonState no se puede serializar: Button no se puede serializar y la referencia a él interrumpe la serialización de ButtonState.
Para solucionar este problema, debe declarar la clase ButtonState como estática. Declarar una clase anidada como estática elimina la referencia implícita de esa clase a su clase adjunta. En Kotlin, el comportamiento predeterminado de las clases internas es lo opuesto a lo que acabamos de describir.
class Button : View {
override fun getCurrentState(): State = ButtonState()
override fun restoreState(state: State) { /*...*/ }
class ButtonState : State { /*...*/ }
}
Una clase anidada en Kotlin sin modificadores explícitos es lo mismo que una clase anidada estática en Java. Para convertirlo en una clase interna para que contenga una referencia a una clase externa se puede usar el modificador inner.
La sintaxis para hacer referencia a una instancia de una clase externa en Kotlin también difiere de Java. Se escribe this@Outer para acceder a la clase Outer desde la clase Inner:
class Outer {
inner class Inner {
fun getOuterReference(): Outer = this@Outer
}
}