Translate

miércoles, 29 de enero de 2025

Expresiones Switch en Java


Con Java 12, Oracle introdujo una nueva funcionalidad en el lenguaje: las expresiones switch, como parte de una característica en fase de vista previa. Esta adición busca mejorar la legibilidad y reducir la verbosidad del código al trabajar con estructuras switch.

En versiones anteriores de Java, la estructura switch era exclusivamente una sentencia, lo que significa que no devolvía un valor. Con la introducción de las expresiones switch, ahora puedes usar switch como una expresión que devuelve un valor, simplificando significativamente códigos comunes y eliminando la necesidad de manejar variables auxiliares.


Vea,mos un ejemplo básico de expresión switch


En versiones anteriores de Java:


int day = 3;

String dayName;

switch (day) {

    case 1:

        dayName = "Lunes";

        break;

    case 2:

        dayName = "Martes";

        break;

    case 3:

        dayName = "Miércoles";

        break;

    default:

        dayName = "Día inválido";

        break;

}

System.out.println(dayName);


Con expresiones switch en Java 12:


int day = 3;

String dayName = switch (day) {

    case 1 -> "Lunes";

    case 2 -> "Martes";

    case 3 -> "Miércoles";

    default -> "Día inválido";

};

System.out.println(dayName);


La nueva sintaxis también admite bloques de código más complejos, usando llaves, veamos un ejemplo:


int number = 5;

String parity = switch (number % 2) {

    case 0 -> {

        System.out.println("Es un número par.");

        yield "Par";

    }

    case 1 -> {

        System.out.println("Es un número impar.");

        yield "Impar";

    }

    default -> throw new IllegalStateException("Valor inesperado: " + number % 2);

};

System.out.println("El número es " + parity);


Las expresiones switch introducidas en Java 12 y estabilizada en Java 14, representan un paso importante hacia la modernización del lenguaje, ofreciendo una sintaxis más concisa y fácil de usar. Si bien inicialmente estaban en fase de vista previa, su adopción completa en versiones posteriores las convierte en una herramienta esencial para los desarrolladores Java modernos.


Concurrencia en Erlang parte 9


Un enlace es un tipo específico de relación que se puede crear entre dos procesos. Cuando se establece esa relación y uno de los procesos muere debido a un error o salida inesperados, el otro proceso vinculado también muere.

Este es un concepto útil desde la perspectiva de no detener los errores lo antes posible: si el proceso que tiene un error se bloquea pero los que dependen de él no, entonces todos estos procesos dependientes tienen que lidiar con la desaparición de una dependencia. Dejar que mueran y luego reiniciar todo el grupo suele ser una alternativa aceptable. Los enlaces nos permiten hacer exactamente esto.

Para establecer un enlace entre dos procesos, Erlang tiene la función primitiva link/1, que toma un Pid como argumento. Cuando se llama, la función creará un enlace entre el proceso actual y el identificado por Pid. Para deshacerse de un enlace, usamos unlink/1. Cuando uno de los procesos vinculados se bloquea, se envía un tipo especial de mensaje, con información relativa a lo que sucedió. No se envía dicho mensaje si el proceso muere por causas naturales (léase: termina de ejecutar sus funciones). Primero veamos esta nueva función como parte de linkmon.erl:


myproc() ->

    timer:sleep(5000),

    exit(reason).


Si ejecutamos las siguiente función (y espera 5 segundos entre cada comando de generación), debería ver que el shell se bloquea por "razón" solo cuando se haya establecido un vínculo entre los dos procesos.


1> c(linkmon).

{ok,linkmon}

2> spawn(fun linkmon:myproc/0).

<0.52.0>

3> link(spawn(fun linkmon:myproc/0)).

true

** exception error: reason


No se puede capturar con un try... catch el mensaje de error como de costumbre. Se deben utilizar otros mecanismos para hacer esto. 

Es importante señalar que los enlaces se utilizan para establecer grupos más grandes de procesos que deberían morir todos juntos:


chain(0) ->

    receive

        _ -> ok

    after 2000 ->

        exit("chain dies here")

    end;

chain(N) ->

    Pid = spawn(fun() -> chain(N-1) end),

    link(Pid),

    receive

        _ -> ok

    end.


Esta función tomará un entero N, iniciará N procesos vinculados entre sí. Para poder pasar el argumento N-1 al siguiente proceso de "cadena" (que llama a spawn/1), envuelvo la llamada dentro de una función anónima para que ya no necesite argumentos. Llamar a spawn(?MODULE, chain, [N-1]) habría hecho un trabajo similar.

Aquí, tendremos muchos procesos vinculados entre sí, que morirán cuando cada uno de sus sucesores salga:


4> c(linkmon).               

{ok,linkmon}

5> link(spawn(linkmon, chain, [3])).

true

** exception error: "chain dies here"


Y como puedes ver, el shell recibe la señal de muerte de algún otro proceso. Aquí hay una representación dibujada de los procesos generados y los enlaces que se caen:


[shell] == [3] == [2] == [1] == [0]

[shell] == [3] == [2] == [1] == *dead*

[shell] == [3] == [2] == *dead*

[shell] == [3] == *dead*

[shell] == *dead*

*dead, error message shown*

[shell] <-- restarted


Después de que el proceso que ejecuta linkmon:chain(0) muere, el error se propaga a lo largo de la cadena de enlaces hasta que el proceso de shell muere por ello. El fallo podría haber ocurrido en cualquiera de los procesos enlazados; como los enlaces son bidireccionales, solo es necesario que uno de ellos muera para que los demás sigan su ejemplo.

Si deseamos matar otro proceso desde el shell, podemos utilizar la función exit/2, que se llama de esta manera: exit(Pid, Reason). 

Los enlaces no se pueden apilar. Si llama a link/1 15 veces para los mismos dos procesos, solo seguirá existiendo un enlace entre ellos y una sola llamada a unlink/1 será suficiente para borrarlo.

Es importante tener en cuenta que link(spawn(Function)) o link(spawn(M,F,A)) ocurren en más de un paso. En algunos casos, es posible que un proceso muera antes de que se haya establecido el enlace y luego provoque un comportamiento inesperado. Por este motivo, se ha añadido al lenguaje la función spawn_link/1-3. Esta función toma los mismos argumentos que spawn/1-3, crea un proceso y lo vincula como si link/1 hubiera estado allí, excepto que todo se realiza como una operación atómica (las operaciones se combinan como una sola, que puede fallar o tener éxito, pero nada más). Esto generalmente se considera más seguro y también ahorra un conjunto de paréntesis.

lunes, 27 de enero de 2025

Las características introducidas en las versiones de Java desde la 12 hasta la más reciente


Hace mucho que no pispeo las nuevas características de java, desde la 11 más o menos. Por lo tanto he resuelto hacer un post por cada nueva característica.

Java 12 (marzo 2019):

  •  Expresiones `switch` (vista previa): Permiten utilizar `switch` como una expresión, simplificando la sintaxis y reduciendo errores.
  •  API de Compilación de Resúmenes de Archivos: Facilita la generación de resúmenes hash para archivos y directorios.
  •  Colectores de Teeing: Introducción de un nuevo colector en la API de Streams que permite combinar dos colecciones en una.


Java 13 (septiembre 2019):

  •  Bloques de texto (vista previa): Permiten manejar cadenas de texto multilínea de manera más sencilla y legible.
  •  Mejoras en ZGC: El Garbage Collector Z se ha mejorado para devolver memoria al sistema operativo más eficientemente.


Java 14 (marzo 2020):

  •  Clases de registros (vista previa): Introducción de `record` para simplificar la creación de clases que son principalmente contenedores de datos.
  •  Coincidencia de patrones para `instanceof` (vista previa): Simplifica el uso de `instanceof` al permitir la asignación directa de la variable si la comprobación es exitosa.


Java 15 (septiembre 2020):

  •  Clases selladas (vista previa): Permiten restringir qué clases pueden heredar de una clase o implementar una interfaz, mejorando el control sobre la jerarquía de clases.
  •  Eliminación de Nashorn: El motor JavaScript Nashorn fue eliminado del JDK.


Java 16 (marzo 2021):

  •  Clases de registros: La funcionalidad de `record` se estabilizó, facilitando la creación de clases inmutables.
  •  API de Acceso a Memoria Externa (incubadora): Proporciona una forma segura y eficiente de acceder a memoria fuera del montón de Java.


Java 17 (septiembre 2021):

  •  Coincidencia de patrones para `switch` (vista previa): Extiende la coincidencia de patrones al `switch`, permitiendo casos basados en el tipo del argumento.
  •  Funciones de clase sellada: Las clases selladas se estabilizaron, ofreciendo un control más preciso sobre la herencia.


Java 18 (marzo 2022):

  •  API de Servidor Web Simple: Introduce una API para crear servidores web mínimos, útiles para pruebas y propósitos educativos.
  •  Mejoras en la API de Caracteres Unicode: Actualizaciones para soportar las últimas versiones del estándar Unicode.


Java 19 (septiembre 2022):

  •  API de Vectores (incubadora): Proporciona una API para operaciones vectoriales que pueden ser optimizadas en hardware compatible.
  •  Patrones de registros (vista previa): Extiende la coincidencia de patrones para trabajar con componentes de registros.


Java 20 (marzo 2023):

  •  Extensiones de la API de Memoria y Función Externa (vista previa): Mejoras en la API para interactuar con memoria y funciones externas de manera más segura y eficiente.
  •  Mejoras en la API de Vectores: Continúan las mejoras en la API de Vectores para un mejor rendimiento y soporte de hardware.


Java 21 (septiembre 2023):

  •  Clases sin nombre y métodos principales sin nombre (vista previa): Simplifica la creación de programas pequeños al eliminar la necesidad de clases y métodos principales explícitos.
  •  Mejoras en la coincidencia de patrones: Ampliaciones adicionales para la coincidencia de patrones en diversas estructuras del lenguaje.


Este es el resumen. Vamos a ver como me va. Si quieren que haga un post para otra característica, escriban en los comentarios. 



domingo, 19 de enero de 2025

Diferencias entre Hilos y Procesos en Elixir


Elixir, gracias a la BEAM VM, utiliza procesos livianos que se diferencian fundamentalmente de los hilos tradicionales:  

1. Aislamiento Completo  

  • Los procesos en Elixir no comparten memoria. Esto evita condiciones de carrera, simplificando el manejo de la concurrencia.  
  • En contraste, los hilos suelen compartir memoria, requiriendo mecanismos como locks y semáforos para sincronización.  


2. Ligereza y Escalabilidad  

  • Cada proceso en Elixir consume muy pocos recursos, permitiendo que millones coexistan.  
  • Los hilos son más pesados y su número está limitado por el sistema operativo.  


3. Comunicación por Mensajes  

  • Los procesos en Elixir se comunican mediante mensajes asíncronos, usando send y receive.  
  • Los hilos comparten datos directamente, lo que puede complicar la concurrencia.  

4. Tolerancia a Fallos  

  • Elixir sigue la filosofía "Let it crash", donde los supervisores reinician procesos fallidos.  
  • Los hilos carecen de un sistema equivalente nativo de supervisión.  


Veamos un ejemplo rápido: 


spawn(fn -> receive do

  msg -> IO.puts("Mensaje recibido: #{msg}")

end end)

|> send("¡Hola, proceso!")


Elixir muestra cómo un enfoque basado en procesos simplifica y fortalece la concurrencia, ideal para sistemas distribuidos y resilientes.  


sábado, 18 de enero de 2025

El Operador |> de Elixir y sus equivalentes en otros lenguajes


En Elixir, el operador |> pasa el resultado de una expresión como el primer argumento de la siguiente función. Ya lo explicamos en el post anterior. 


" hello "

|> String.trim()

|> String.upcase()

Resultado: "HELLO"


Este diseño promueve una lectura fluida del código, eliminando la necesidad de paréntesis anidados.


F#, un lenguaje funcional inspirado en ML, también tiene un operador pipe |> con un propósito similar al de Elixir.


" hello "

|> String.trim

|> String.uppercase


El operador en F# permite que el flujo de datos sea explícito, facilitando la composición de funciones.


Python no tiene un operador pipe nativo, pero existen bibliotecas que lo emulan, como `pipe` o `toolz`. Sin embargo, sin bibliotecas adicionales, puedes lograr algo similar con reduce:


from functools import reduce


data = " hello "

result = reduce(lambda acc, fn: fn(acc), [str.strip, str.upper], data)

print(result)  # HELLO


Con una biblioteca como pipe:


from pipe import Pipe


result = " hello " | Pipe(str.strip) | Pipe(str.upper)

print(result)  # HELLO


JavaScript aún no tiene un operador pipe oficial, pero hay una propuesta en desarrollo en el comité TC39 (etapa 2 al momento de escribir). Con esta propuesta, el pipe se usa de la siguiente manera:


" hello "

  |> (x => x.trim())

  |> (x => x.toUpperCase());


Por ahora, puedes emularlo con funciones:


const pipeline = (...fns) => x => fns.reduce((v, f) => f(v), x);


const result = pipeline(

  x => x.trim(),

  x => x.toUpperCase()

)(" hello ");

console.log(result); // HELLO


Scala no tiene un operador pipe nativo, pero es posible definir uno:


implicit class PipeOps[T](val value: T) extends AnyVal {

  def |>[R](f: T => R): R = f(value)

}


val result = " hello "

  |> (_.trim)

  |> (_.toUpperCase)

println(result) // HELLO


En C#, aunque no existe un operador pipe, los métodos de extensión de LINQ se comportan de manera similar:


string result = " hello "

    .Trim()

    .ToUpper();

Console.WriteLine(result); // HELLO


El concepto detrás del operador pipe (`|>`) es universal: facilita la composición de funciones y mejora la legibilidad. Aunque su implementación varía entre lenguajes, su propósito sigue siendo el mismo: transformar datos paso a paso de manera clara y concisa.


viernes, 17 de enero de 2025

Concurrencia en Erlang parte 8


El concepto de 'flushing' permite implementar una recepción selectiva que puede dar prioridad a los mensajes que recibe mediante la anidación de llamadas:


important() ->

    receive

        {Priority, Message} when Priority > 10 ->

            [Message | important()]

    after 0 ->

        normal()

    end.


normal() ->

    receive

        {_, Message} ->

            [Message | normal()]

    after 0 ->

        []

    end.


Esta función creará una lista de todos los mensajes, comenzando primero con aquellos con una prioridad superior a 10:


1> c(multiproc).

{ok,multiproc}

2> self() ! {15, high}, self() ! {7, low}, self() ! {1, low}, self() ! {17, high}.       

{17,high}

3> multiproc:important().

[high,high,low,low]


Como utilicé el bit posterior a 0, se obtendrán todos los mensajes hasta que no quede ninguno, pero el proceso intentará capturar todos aquellos con una prioridad superior a 10 antes incluso de considerar los demás mensajes, que se acumulan en la llamada normal/0.

Si esta práctica le parece interesante, tenga en cuenta que a veces no es segura debido a la forma en que funcionan las recepciones selectivas en Erlang.

Cuando se envían mensajes a un proceso, se almacenan en el buzón hasta que el proceso los lee y coinciden con un patrón allí. Los mensajes se almacenan en el orden en que se recibieron. Esto significa que cada vez que coincide con un mensaje, comienza por el más antiguo.

Luego, ese mensaje más antiguo se prueba con cada patrón de la recepción hasta que uno de ellos coincide. Cuando lo hace, el mensaje se elimina del buzón y el código del proceso se ejecuta normalmente hasta la próxima recepción. Cuando se evalúa esta próxima recepción, la máquina virtual buscará el mensaje más antiguo que se encuentre actualmente en el buzón (el siguiente al que eliminamos), y así sucesivamente.

Cuando no hay forma de encontrar una coincidencia con un mensaje determinado, se lo coloca en una cola de guardado y se intenta con el siguiente mensaje. Si el segundo mensaje coincide, el primero se vuelve a colocar en la parte superior del buzón para volver a intentarlo más tarde.

Esto le permite preocuparse únicamente por los mensajes que son útiles. Ignorar algunos mensajes para manejarlos más tarde de la manera descrita anteriormente es la esencia de las recepciones selectivas. Si bien son útiles, el problema con ellas es que si su proceso tiene muchos mensajes que nunca le interesan, leer los mensajes útiles en realidad llevará cada vez más tiempo (y los procesos también crecerán en tamaño).

Imagine que queremos el mensaje n.° 367, pero los primeros 366 son basura ignorada por nuestro código. Para obtener el mensaje n.° 367, el proceso debe intentar hacer coincidir los 366 primeros. Una vez que haya terminado y todos se hayan colocado en la cola, se saca el mensaje n.° 367 y los primeros 366 se vuelven a colocar en la parte superior del buzón. El siguiente mensaje útil podría estar mucho más escondido y tardar aún más en encontrarlo.

Este tipo de recepción es una causa frecuente de problemas de rendimiento en Erlang. Si su aplicación se ejecuta con lentitud y sabe que hay muchos mensajes circulando, esta podría ser la causa.

Si estas recepciones selectivas están causando una ralentización masiva de su código, lo primero que debe hacer es preguntarse por qué recibe mensajes que no desea. ¿Se envían los mensajes a los procesos correctos? ¿Son correctos los patrones? ¿Los mensajes tienen un formato incorrecto? ¿Está utilizando un proceso cuando debería haber muchos? Responder a una o varias de estas preguntas podría resolver su problema.

Debido a los riesgos de que los mensajes inútiles contaminen el buzón de un proceso, los programadores de Erlang a veces toman una medida defensiva contra tales eventos. Una forma estándar de hacerlo podría ser la siguiente:


receive

    Pattern1 -> Expression1;

    Pattern2 -> Expression2;

    Pattern3 -> Expression3;

    ...

    PatternN -> ExpressionN;

    Unexpected ->

        io:format("unexpected message ~p~n", [Unexpected])

end.


Lo que esto hace es asegurarse de que cualquier mensaje coincida con al menos una cláusula. La variable Unexpected coincidirá con cualquier cosa, sacará el mensaje inesperado del buzón y mostrará una advertencia. Dependiendo de su aplicación, es posible que desee almacenar el mensaje en algún tipo de servicio de registro donde podrá encontrar información sobre él más adelante: si los mensajes van al proceso equivocado, sería una pena perderlos para siempre y tener dificultades para encontrar por qué ese otro proceso no recibe lo que debería.

En el caso de que necesite trabajar con una prioridad en sus mensajes y no pueda usar una cláusula de captura general, una forma más inteligente de hacerlo sería implementar un min-heap o usar el módulo gb_trees y volcar todos los mensajes recibidos en él (asegúrese de poner el número de prioridad primero en la clave para que se use para ordenar los mensajes). Luego, puede simplemente buscar el elemento más pequeño o más grande en la estructura de datos según sus necesidades.

En la mayoría de los casos, esta técnica debería permitirle recibir mensajes con una prioridad de manera más eficiente que las recepciones selectivas. Sin embargo, podría ralentizarlo si la mayoría de los mensajes que recibe tienen la máxima prioridad posible. Como siempre, el truco es perfilar y medir antes de optimizar.

Desde R14A, se agregó una nueva optimización al compilador de Erlang. Simplifica las recepciones selectivas en casos muy específicos de comunicaciones de ida y vuelta entre procesos. Un ejemplo de una función de este tipo es optimized/1 en multiproc.erl.

-module(multiproc).

-compile([export_all]).


sleep(T) ->

    receive

    after T -> ok

    end.


flush() ->

    receive

        _ -> flush()

    after 0 ->

        ok

    end.


important() ->

    receive

        {Priority, Message} when Priority > 10 ->

            [Message | important()]

    after 0 ->

        normal()

    end.


normal() ->

    receive

        {_, Message} ->

            [Message | normal()]

    after 0 ->

        []

    end.


%% optimized in R14A

optimized(Pid) ->

    Ref = make_ref(),

    Pid ! {self(), Ref, hello},

    receive

        {Pid, Ref, Msg} ->

            io:format("~p~n", [Msg])

    end.

Para que funcione, se debe crear una referencia (make_ref()) en una función y luego enviarla en un mensaje. En la misma función, se realiza una recepción selectiva. Si ningún mensaje puede coincidir a menos que contenga la misma referencia, el compilador se asegura automáticamente de que la máquina virtual omitirá los mensajes recibidos antes de la creación de esa referencia.

Tenga en cuenta que no debe intentar forzar su código para que se ajuste a dichas optimizaciones. Los desarrolladores de Erlang solo buscan patrones que se usan con frecuencia y luego los hacen más rápidos. Si escribe código idiomático, las optimizaciones deberían venir a usted. No al revés.

Con estos conceptos entendidos, el siguiente paso será realizar el manejo de errores con múltiples procesos.


martes, 14 de enero de 2025

El Poder del Operador |> en Elixir: Elegancia y Legibilidad


La programación funcional se centra en la composición de funciones para resolver problemas de manera clara y concisa. Uno de los operadores más representativos de este paradigma es el operador pipe |>, que permite encadenar llamadas a funciones de forma fluida y natural.

El operador |> (pipe) se utiliza para pasar el resultado de una expresión como el primer argumento de la siguiente función en la cadena.


value |> function1() |> function2()


Esto es equivalente a:


function2(function1(value))


Como ventajas podemos nombrar: 

  1. Legibilidad Mejorada: El flujo de datos se representa de forma secuencial, como si leyeras un proceso paso a paso.
  2. Eliminación de Paréntesis Anidados: Reduce la complejidad visual de funciones anidadas.
  3. Facilita el Refactoring: Reordenar o agregar pasos en el flujo es más sencillo.


Esto :

String.upcase(String.trim(" hola "))


Se puede escribir así:

" hola "

|> String.trim()

|> String.upcase()


Ambos códigos producen el mismo resultado: `"HOLA"`, pero la versión con |> es más legible.

El operador |> no se limita a funciones de la librería estándar; también puedes usarlo con tus propias funciones.


defmodule Math do

  def square(x), do: x * x

  def double(x), do: x * 2

end


5

|> Math.square()

|> Math.double()

Resultado: 50


Veamos ejemplos de uso : 


[1, 2, 3, 4]

|> Enum.map(&(&1 * 2))

|> Enum.filter(&(&1 > 4))

Resultado: [6, 8]


Otro ejemplo con estructuras más complejas: 


%{name: "John", age: 30}

|> Map.put(:country, "USA")

|> Map.update!(:age, &(&1 + 1))

Resultado: %{name: "John", age: 31, country: "USA"}


El operador `|>` es una herramienta fundamental en Elixir que no solo mejora la legibilidad del código, sino que también alienta un diseño funcional y modular. Al adoptarlo, puedes construir pipelines claros y efectivos que hagan que tu código sea más expresivo y fácil de mantener.


El Operador ?? y ??= en C#


En C#, el manejo de valores nulos es crucial para escribir código robusto y limpio. Dos herramientas poderosas que el lenguaje nos ofrece son los operadores ?? y ??=. Estos operadores no solo mejoran la legibilidad, sino que también reducen el código repetitivo.

El operador de coalescencia nula ?? se utiliza para proporcionar un valor alternativo en caso de que una expresión sea null.


var result = value ?? defaultValue;


  • Si value no es null, result será igual a value.
  • Si value es null, result tomará el valor de defaultValue.


Veamos un ejemplo: 


string? name = null;

string displayName = name ?? "Usuario por defecto";

Console.WriteLine(displayName); // Salida: Usuario por defecto


El operador ??= fue introducido en C# 8.0, el operador de asignación de coalescencia nula simplifica el proceso de asignar un valor a una variable solo si esta es null.


variable ??= value;

Seria como : 


variable = (variable == null) ? value : variable


  • Si variable es null, se le asignará value.
  • Si variable ya tiene un valor, no se realiza ninguna acción.


Veamos un ejemplo: 


List<int>? numbers = null;

numbers ??= new List<int>();

numbers.Add(42);


Console.WriteLine(string.Join(", ", numbers)); // Salida: 42


Los operadores `??` y `??=` son herramientas esenciales para trabajar con valores nulos de manera elegante y eficiente en C#. Su uso adecuado no solo mejora la legibilidad del código, sino que también ayuda a prevenir errores comunes relacionados con referencias nulas.


domingo, 12 de enero de 2025

Concurrencia en Erlang parte 7


Probemos algo con la ayuda del comando pid(A,B,C), que nos permite convertir los 3 números enteros A, B y C en un pid. Aquí le daremos deliberadamente a kitchen:take/2 uno falso:


20> kitchen:take(pid(0,250,0), dog).


Ups. El shell está congelado. Esto sucedió debido a la forma en que se implementó take/2. Para entender lo que sucede, primero revisemos lo que sucede en el caso normal:


  1. Un mensaje para tomar comida se envía desde el shell al proceso del refrigerador;
  2. Su proceso cambia al modo de recepción y espera un nuevo mensaje;
  3. El refrigerador retira el artículo y lo envía a su proceso;
  4. Su proceso lo recibe y continúa con su vida.


Y esto es lo que sucede cuando el shell se congela:


  1. Un mensaje para tomar comida se envía desde el shell a un proceso desconocido;
  2. Su proceso cambia al modo de recepción y espera un nuevo mensaje;
  3. El proceso desconocido no existe o no espera tal mensaje y no hace nada con él;
  4. Su proceso de shell está bloqueado en modo de recepción.

Eso es molesto, especialmente porque no hay manejo de errores posible aquí. No sucedió nada ilegal, el programa solo está esperando. En general, cualquier cosa que tenga que ver con operaciones asincrónicas (que es como se hace el paso de mensajes en Erlang) necesita una forma de abandonar el proceso después de un cierto período de tiempo si no obtiene señales de recibir datos. Un navegador web lo hace cuando una página o imagen tarda demasiado en cargarse, usted lo hace cuando alguien tarda demasiado en responder el teléfono o llega tarde a una reunión. Erlang ciertamente tiene un mecanismo apropiado para eso, y es parte de la construcción de recepción:


receive

    Match -> Expression1

after Delay ->

    Expression2

end.


La parte entre recibir y después es exactamente la misma que ya conocemos. La parte después se activará si se ha pasado tanto tiempo como Delay (un entero que representa milisegundos) sin recibir un mensaje que coincida con el patrón Match. Cuando esto sucede, se ejecuta Expression2.

Escribiremos dos nuevas funciones de interfaz, store2/2 y take2/2, que actuarán exactamente como store/2 y take/2 con la excepción de que dejarán de esperar después de 3 segundos:


store2(Pid, Food) ->

    Pid ! {self(), {store, Food}},

    receive

        {Pid, Msg} -> Msg

    after 3000 ->

        timeout

    end.


take2(Pid, Food) ->

    Pid ! {self(), {take, Food}},

    receive

        {Pid, Msg} -> Msg

    after 3000 ->

        timeout

    end.


Ahora puedes descongelar el shell con ^G y probar las nuevas funciones de la interfaz:


User switch command

 --> k 

 --> s

 --> c

Eshell V5.7.5  (abort with ^G)

1> c(kitchen).

{ok,kitchen}

2> kitchen:take2(pid(0,250,0), dog).

timeout


Y ahora funciona.

After no solo toma milisegundos como valor, en realidad es posible usar el átomo infinito. Si bien esto no es útil en muchos casos (podría simplemente eliminar la cláusula after por completo), a veces se usa cuando el programador puede enviar el tiempo de espera a una función donde se espera recibir un resultado. De esa manera, si el programador realmente quiere esperar eternamente, puede hacerlo.

Existen usos para estos temporizadores además de darse por vencidos después de demasiado tiempo. Un ejemplo muy simple es cómo funciona la función timer:sleep/1 que hemos usado antes. Aquí se muestra cómo se implementa (pongámosla en un nuevo módulo multiproc.erl):


sleep(T) ->

    receive

    after T -> ok

    end.


En este caso específico, nunca se encontrará ningún mensaje en la parte de recepción de la construcción porque no hay ningún patrón. En cambio, se llamará a la parte posterior de la construcción una vez que haya transcurrido el retraso T.

Otro caso especial es cuando el tiempo de espera es 0:


flush() ->

    receive

        _ -> flush()

    after 0 ->

        ok

    end.


Cuando esto sucede, la máquina virtual Erlang intentará encontrar un mensaje que se ajuste a uno de los patrones disponibles. En el caso anterior, todo coincide. Mientras haya mensajes, la función flush/0 se llamará a sí misma de forma recursiva hasta que el buzón esté vacío. Una vez hecho esto, se ejecuta la parte after 0 -> ok del código y la función retorna.

sábado, 11 de enero de 2025

Quicksort en prolog


Un Algoritmo que me gusta mucho es el quicksort, porque es un algoritmo por demás claro. Ya he escrito lo fácil que es implementarlo en scalaerlangrusthaskell , F# y lisp.

Ahora le toca a prolog. Básicamente el algoritmo toma un pivot y agrupa los menores del pivot al principio y los mayores al final y aplica quicksort a estos 2 grupos. Y si la lista es vacía o tiene un elemento, ya esta ordenada. 

Vamos al código:  


quicksort([], []).

quicksort([X], [X]).

quicksort([Pivot|Rest], Sorted) :-

    partition(Rest, Pivot, Smaller, Greater),

    quicksort(Smaller, SortedSmaller),

    quicksort(Greater, SortedGreater),

    append(SortedSmaller, [Pivot|SortedGreater], Sorted).


partition([], _, [], []).

partition([X|Rest], Pivot, [X|Smaller], Greater) :-

    X =< Pivot,

    partition(Rest, Pivot, Smaller, Greater).

partition([X|Rest], Pivot, Smaller, [X|Greater]) :-

    X > Pivot,

    partition(Rest, Pivot, Smaller, Greater).


Vamos a probarlo: 


?- quicksort([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5], Sorted).

Sorted = [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9].



viernes, 10 de enero de 2025

Programación Reactiva con Spring WebFlux y Cassandra


Vamos a crear un proyecto webflux utilizando Cassndra. 

Primero creamos un proyecto Spring Boot y agregamos las dependencias necesarias:

  • spring-boot-starter-webflux
  • spring-boot-starter-data-cassandra-reactive


Si utilizamos maven el archivo pom.xml tendria estas dependencias :


<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-webflux</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-data-cassandra-reactive</artifactId>

    </dependency>

    <dependency>

        <groupId>com.datastax.oss</groupId>

        <artifactId>java-driver-core</artifactId>

    </dependency>

</dependencies>

Si usamos gradle seria algo así : 

        implementation("org.springframework.boot:spring-boot-starter-data-cassandra-reactive")

implementation("org.springframework.boot:spring-boot-starter-webflux")


Agregamos la configuración de Cassandra en application.yml:


spring:

  data:

    cassandra:

      contact-points: [localhost]

      port: 9042

      keyspace-name: demo_keyspace

      schema-action: create-if-not-exists

o en el properties: 

spring.cassandra.contact-points=127.0.0.1

spring.cassandra.port=9042

spring.cassandra.keyspace-name=demo_keyspace

spring.cassandra.schema-action= create-if-not-exists

spring.cassandra.local-datacenter=datacenter1


Ahora vamos a definir una entidad para guardar y recuperar:


import org.springframework.data.annotation.Id;

import org.springframework.data.cassandra.core.mapping.PrimaryKey;

import org.springframework.data.cassandra.core.mapping.Table;


@Table("products")

public class Product {

    @Id

    @PrimaryKey

    private String id;

    private String name;

    private double price;


    // Getters y setters

}


Crea un repositorio usando ReactiveCassandraRepository:


import org.springframework.data.cassandra.repository.ReactiveCassandraRepository;

import org.springframework.stereotype.Repository;


@Repository

public interface ProductRepository extends ReactiveCassandraRepository<Product, String> {

}


Ahora hacemos el servicio: 


import org.springframework.stereotype.Service;

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;


@Service

public class ProductService {


    private final ProductRepository productRepository;


    public ProductService(ProductRepository productRepository) {

        this.productRepository = productRepository;

    }


    public Flux<Product> getAllProducts() {

        return productRepository.findAll();

    }


    public Mono<Product> getProductById(String id) {

        return productRepository.findById(id);

    }


    public Mono<Product> createProduct(Product product) {

        return productRepository.save(product);

    }


    public Mono<Void> deleteProduct(String id) {

        return productRepository.deleteById(id);

    }

}


Ahora creamos un controlador REST con WebFlux:


import org.springframework.web.bind.annotation.*;

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;


@RestController

@RequestMapping("/products")

public class ProductController {


    private final ProductService productService;


    public ProductController(ProductService productService) {

        this.productService = productService;

    }


    @GetMapping

    public Flux<Product> getAllProducts() {

        return productService.getAllProducts();

    }


    @GetMapping("/{id}")

    public Mono<Product> getProductById(@PathVariable String id) {

        return productService.getProductById(id);

    }


    @PostMapping

    public Mono<Product> createProduct(@RequestBody Product product) {

        return productService.createProduct(product);

    }


    @DeleteMapping("/{id}")

    public Mono<Void> deleteProduct(@PathVariable String id) {

        return productService.deleteProduct(id);

    }

}


Por ultimo tenemos que agregar las anotaciones @EnableReactiveCassandraRepositories y @Push a nuestro application : 


@SpringBootApplication

@EnableReactiveCassandraRepositories

@Push

class ApplicationDemo : AppShellConfigurator


fun main(args: Array<String>) {

runApplication<ApplicationDemo>(*args)

}


Y ahora podemos probar nuestros servicios. 

miércoles, 8 de enero de 2025

Concurrencia en Erlang parte 6


Algo molesto del ejemplo del post anterior es que el programador que va a utilizar el frigorífico tiene que conocer el protocolo que se ha inventado para ese proceso. Es una carga inútil. Una buena forma de solucionarlo es abstraer los mensajes con la ayuda de funciones que se ocupen de recibirlos y enviarlos:


store(Pid, Food) ->

    Pid ! {self(), {store, Food}},

    receive

        {Pid, Msg} -> Msg

    end.


take(Pid, Food) ->

    Pid ! {self(), {take, Food}},

    receive

        {Pid, Msg} -> Msg

    end.


Ahora la interacción con el proceso es mucho más limpia:


9> c(kitchen).

{ok,kitchen}

10> f().

ok

11> Pid = spawn(kitchen, fridge2, [[baking_soda]]).

<0.73.0>

12> kitchen:store(Pid, water).

ok

13> kitchen:take(Pid, water).

{ok,water}

14> kitchen:take(Pid, juice).

not_found


Ya no tenemos que preocuparnos por cómo funcionan los mensajes si se necesita enviar self() o un átomo preciso como take o store: todo lo que se necesita es un pid y saber qué funciones llamar. Esto oculta todo el trabajo sucio y facilita la creación del proceso de la nevera.

Una cosa que queda por hacer sería ocultar toda esa parte sobre la necesidad de generar un proceso. Nos ocupamos de ocultar los mensajes, pero aún esperamos que el usuario se encargue de la creación del proceso. Veamos la siguiente función start/1:


start(FoodList) ->

    spawn(?MODULE, fridge2, [FoodList]).


Aquí, ?MODULE es una macro que devuelve el nombre del módulo actual. No parece que haya ventajas en escribir una función de este tipo, pero realmente las hay. La parte esencial sería la coherencia con las llamadas a take/2 y store/2: todo lo relacionado con el proceso de refrigerador ahora lo maneja el módulo de cocina. Si tuviera que agregar un registro cuando se inicia el proceso de refrigerador o iniciar un segundo proceso (por ejemplo, un congelador), sería muy fácil hacerlo dentro de nuestra función start/1. Sin embargo, si se deja que el usuario realice la generación a través de spawn/3, entonces cada lugar que inicie un refrigerador ahora debe agregar las nuevas llamadas. Eso es propenso a errores y los errores son una mierda.

Veamos cómo se usa esta función:

15> f().

ok

16> c(kitchen).

{ok,kitchen}

17> Pid = kitchen:start([rhubarb, dog, hotdog]).

<0.84.0>

18> kitchen:take(Pid, dog).

{ok,dog}

19> kitchen:take(Pid, dog).

not_found


¡Hurra! ¡El perro ha salido del frigorífico y nuestra abstracción es total!


martes, 7 de enero de 2025

Implementación Explícita de Interfaces en C#


La implementación explícita de interfaces en C# es una característica poderosa que permite definir miembros de una interfaz de manera que solo sean accesibles a través de la referencia de la interfaz, no directamente desde una instancia de la clase que la implementa. Esto resulta útil para resolver conflictos de nombres y mejorar la encapsulación. 

Cuando una clase implementa una interfaz, normalmente los miembros de esta interfaz se definen como públicos en la clase. Sin embargo, en algunos casos puede ser deseable que los miembros de la interfaz solo estén disponibles cuando se utiliza una referencia a la interfaz, y no desde la clase directamente. Esto es lo que permite la implementación explícita.

La sintaxis consiste en especificar el nombre de la interfaz antes del nombre del método o propiedad que se está implementando:


void NombreInterfaz.Metodo()


Veamos un ejemplo simple:


interface IA

{

    void B();

}


class MiClase : IA

{

    // Implementación explícita

    void IA.B()

    {

        Console.WriteLine("Método IA.B implementado explícitamente.");

    }

}


class Program

{

    static void Main()

    {

        MiClase obj = new MiClase();


        // No puedes llamar directamente a obj.B()

        // obj.B(); // Esto generaría un error de compilación


        // Debes convertir a la interfaz

        IA interfaz = obj;

        interfaz.B(); // Ahora funciona

    }

}


Un uso común de la implementación explícita es cuando una clase implementa varias interfaces que contienen miembros con el mismo nombre.


interface IA { void B(); }

interface IB { void B(); }


class MiClase : IA, IB

{

    void IA.B()

    {

        Console.WriteLine("IA.B llamado");

    }


    void IB.B()

    {

        Console.WriteLine("IB.B llamado");

    }

}


class Program

{

    static void Main()

    {

        MiClase obj = new MiClase();


        // Acceso explícito a través de cada interfaz

        ((IA)obj).B(); // IA.B llamado

        ((IB)obj).B(); // IB.B llamado

    }

}


Las ventajas de usar esto tenemos:

  1. Evitar conflictos de nombres: Permite implementar métodos con el mismo nombre en diferentes interfaces sin ambigüedades.
  2. Encapsulación: Los miembros de la interfaz solo son accesibles cuando la instancia de la clase se trata como una referencia a la interfaz.
  3. Separación de responsabilidades: Permite mantener lógicas separadas y claras cuando se implementan varias interfaces.

La implementación explícita de interfaces es una herramienta valiosa en C#, especialmente en escenarios donde se requiere mayor encapsulación o resolución de conflictos de nombres. Si bien puede parecer menos intuitiva al principio, ofrece flexibilidad y control sobre cómo se exponen los miembros de una interfaz.

domingo, 5 de enero de 2025

Concurrencia en Erlang parte 5


No hay una gran ventaja para los procesos y actores si son solo funciones con mensajes. Para solucionar esto, tenemos que poder mantener el estado en un proceso.

Primero, creemos una función en un nuevo módulo kitchen.erl que permitirá que un proceso actúe como un refrigerador. El proceso permitirá dos operaciones: almacenar alimentos en el refrigerador y sacar alimentos del refrigerador. Solo debería ser posible sacar alimentos que se hayan almacenado de antemano. La siguiente función puede actuar como base para nuestro proceso:


-module(kitchen).

-compile(export_all).


fridge1() ->

    receive

        {From, {store, _Food}} ->

            From ! {self(), ok},

            fridge1();

        {From, {take, _Food}} ->

            %% uh....

            From ! {self(), not_found},

            fridge1();

        terminate ->

            ok

    end.


Algo anda mal. Cuando pedimos almacenar la comida, el proceso debería responder ok, pero no hay nada que almacene la comida; se llama a fridge1() y luego la función comienza desde cero, sin estado. También puedes ver que cuando llamamos al proceso para sacar comida del refrigerador, no hay estado del cual sacarla y, por lo tanto, lo único que se debe responder es not_found. Para almacenar y sacar alimentos, necesitaremos agregar estado a la función.

Con la ayuda de la recursión, el estado de un proceso puede entonces mantenerse completamente en los parámetros de la función. En el caso de nuestro proceso de refrigerador, una posibilidad sería almacenar toda la comida como una lista y luego buscar en esa lista cuando alguien necesite comer algo:


fridge2(FoodList) ->

    receive

        {From, {store, Food}} ->

            From ! {self(), ok},

            fridge2([Food|FoodList]);

        {From, {take, Food}} ->

            case lists:member(Food, FoodList) of

                true ->

                    From ! {self(), {ok, Food}},

                    fridge2(lists:delete(Food, FoodList));

                false ->

                    From ! {self(), not_found},

                    fridge2(FoodList)

            end;

        terminate ->

            ok

    end.


Lo primero que hay que notar es que fridge2/1 toma un argumento, FoodList. Puedes ver que cuando enviamos un mensaje que coincide con {From, {store, Food}}, la función agregará Food a FoodList antes de continuar. Una vez que se realiza esa llamada recursiva, será posible recuperar el mismo elemento. De hecho, lo implementé allí. La función usa lists:member/2 para verificar si Food es parte de FoodList o no. Dependiendo del resultado, el elemento se envía de vuelta al proceso de llamada (y se elimina de FoodList) o se envía not_found de vuelta en caso contrario:


1> c(kitchen).

{ok,kitchen}

2> Pid = spawn(kitchen, fridge2, [[baking_soda]]).

<0.51.0>

3> Pid ! {self(), {store, milk}}.

{<0.33.0>,{store,milk}}

4> flush().

Shell got {<0.51.0>,ok}

ok

Parece que almacenar los alimentos en el frigorífico funciona. Probaremos con más cosas y luego intentaremos sacarlas del frigorífico.


5> Pid ! {self(), {store, bacon}}.

{<0.33.0>,{store,bacon}}

6> Pid ! {self(), {take, bacon}}.

{<0.33.0>,{take,bacon}}

7> Pid ! {self(), {take, turkey}}.

{<0.33.0>,{take,turkey}}

8> flush().

Shell got {<0.51.0>,ok}

Shell got {<0.51.0>,{ok,bacon}}

Shell got {<0.51.0>,not_found}

ok


Como era de esperar, podemos sacar el tocino del frigorífico porque lo hemos puesto allí primero (junto con la leche y el bicarbonato de sodio), pero el proceso del frigorífico no encuentra ningún pavo cuando lo solicitamos. Por eso recibimos el último mensaje {<0.51.0>,not_found}.


sábado, 4 de enero de 2025

Actores en Elixir


Elixir, construido sobre la Máquina Virtual de Erlang (BEAM), es conocido por su capacidad para manejar concurrencia y tolerancia a fallos de manera elegante. Una de las piezas clave detrás de esta potencia es el modelo de actores.

El modelo de actores es un paradigma de concurrencia donde las entidades llamadas actores:

  • Reciben mensajes.
  • Procesan esos mensajes.
  • Pueden responder, enviar mensajes a otros actores o crear nuevos actores.

En Elixir, los actores se implementan como procesos ligeros gestionados por la BEAM, lo que permite manejar miles o incluso millones de ellos simultáneamente. Las caracteristicas más importantes son: 

  1. Aislamiento completo: Cada actor tiene su propio estado y no comparte memoria con otros actores.
  2. Comunicación mediante mensajes: Los mensajes entre a ctores son asíncronos y pasan a través de colas de mensajes.
  3. Tolerancia a fallos: Si un actor falla, su supervisor puede reiniciarlo, manteniendo la estabilidad del sistema.

En Elixir, los procesos se crean con spawn, y se comunican usando send para enviar mensajes y receive para manejarlos. Veamos un ejemplo: 


defmodule ActorExample do

  def start do

    spawn(fn -> listen() end)

  end


  defp listen do

    receive do

      {:greet, name} ->

        IO.puts("¡Hola, #{name}!")

        listen()

      :stop ->

        IO.puts("Proceso detenido.")

      _ ->

        IO.puts("Mensaje no reconocido.")

        listen()

    end

  end

end


# Crear el actor

pid = ActorExample.start()


# Enviar mensajes al actor

send(pid, {:greet, "Mundo"})

send(pid, :stop)


Elixir hereda de Erlang una rica tradición de más de tres décadas en sistemas concurrentes y distribuidos. Esto lo convierte en una elección ideal para aplicaciones modernas como:

  • Sistemas distribuidos.
  • Aplicaciones web con alta concurrencia.
  • Sistemas en tiempo real.