Translate
viernes, 23 de agosto de 2024
lunes, 19 de agosto de 2024
Se encuentran abiertas las inscripciones para los cursos Gugler!!!
¡Tengo grandes noticias! Estoy emocionado de anunciar que ya están abiertas las inscripciones para los tan esperados cursos Gugler. Si estás buscando avanzar en tu carrera, aprender nuevas habilidades, o simplemente profundizar tus conocimientos en áreas tecnológicas, ¡estos cursos son para ti!
Inscripciones abiertas del segundo cuatrimestre 2024. Inscripciones.gugler.com.ar
domingo, 18 de agosto de 2024
El libro de Python
"Te damos la bienvenida a El Libro De Python, un espacio en el que podrás aprender y consultar dudas acerca del lenguaje de programación Python. Nuestro libro es totalmente gratis y abierto, por lo que te invitamos a colaborar con nosotros a través de GitHub."
De esta forma se presenta la pagina "El libro de python", un libro o web (como quieras llamarlo) super recomendado para aprender python.
Dejo link:
viernes, 16 de agosto de 2024
Scripts de Lua en Redis
Redis, conocido por ser un sistema de almacenamiento de datos en memoria altamente rápido, no tiene soporte directo para procedimientos almacenados o funciones como los sistemas de bases de datos relacionales tradicionales. Sin embargo, Redis ofrece características que permiten realizar operaciones complejas y reutilizables de manera similar a los procedimientos almacenados y funciones, principalmente a través de scripts en Lua.
Lua es un lenguaje de scripting ligero y potente, y Redis permite la ejecución de scripts Lua en su entorno. Esto brinda la posibilidad de realizar operaciones más complejas que las que se pueden lograr con los comandos básicos de Redis.
Por qué usar Lua en Redis:
- Atomicidad: Los scripts Lua se ejecutan de manera atómica en Redis, lo que significa que ninguna otra operación puede interferir con la ejecución del script.
- Reutilización: Puedes almacenar y reutilizar scripts Lua para realizar operaciones complejas, lo que es análogo a los procedimientos almacenados.
- Flexibilidad: Lua te permite hacer uso de la lógica de programación, como condicionales y bucles, directamente dentro de Redis.
Supongamos que queremos implementar un procedimiento que incremente el valor de una clave solo si la clave existe y su valor es mayor que un umbral dado. Este es un típico ejemplo donde un procedimiento almacenado sería útil en un sistema de bases de datos relacional.
-- Script Lua para incrementar un valor si es mayor que un umbral
local current = redis.call('GET', KEYS[1])
if current and tonumber(current) > tonumber(ARGV[1]) then
return redis.call('INCRBY', KEYS[1], ARGV[2])
else
return nil
end
Para ejecutar este script en Redis, puedes usar el comando `EVAL`:
EVAL "local current = redis.call('GET', KEYS[1])
if current and tonumber(current) > tonumber(ARGV[1]) then
return redis.call('INCRBY', KEYS[1], ARGV[2])
else
return nil
end" 1 mykey 10 5
Este comando recibe los siguientes parametros :
- 1 indica el número de claves (`mykey`) que el script recibirá.
- mykey es la clave en Redis que el script verificará y posiblemente incrementará.
- 10 es el umbral; si el valor actual de `mykey` es mayor que este valor, se incrementará.
- 5 es la cantidad por la cual se incrementará el valor de `mykey` si la condición se cumple.
Aunque Redis no tiene una noción de funciones al estilo SQL, puedes pensar en los scripts Lua como funciones reutilizables. Si bien Redis no permite definir funciones Lua en el mismo sentido que los procedimientos almacenados en SQL, puedes almacenar el script en Redis y llamarlo repetidamente.
Para almacenar un script:
SCRIPT LOAD "local current = redis.call('GET', KEYS[1])
if current and tonumber(current) > tonumber(ARGV[1]) then
return redis.call('INCRBY', KEYS[1], ARGV[2])
else
return nil
end
Esto te devolverá un `sha1` hash del script, que puedes usar para invocarlo nuevamente:
EVALSHA <sha1> 1 mykey 10 5
Mientras que Redis no soporta procedimientos almacenados y funciones en el sentido tradicional de bases de datos relacionales, su capacidad para ejecutar scripts Lua te permite realizar operaciones avanzadas y reutilizables de manera similar. Esta funcionalidad es extremadamente útil cuando necesitas lógica compleja o atomicidad en tus operaciones con Redis.
dotnet el comando con que .net soluciona todos nuestros problemas.
Me he dado cuenta que no conozco en profundidad le comando dotnet, lo uso para correr mis test, para hacer un proyecto de ejemplo pero listo... Pero muchas veces necesitamos abrir esta caja de herramientas y usar todo lo que trae. Por eso me voy a poner a estudiar..
Empecemos por el principio. El comando `dotnet` es la herramienta de línea de comandos que viene con el SDK de .NET y que permite a los desarrolladores realizar una amplia gama de tareas relacionadas con la creación, compilación, depuración y despliegue de aplicaciones .NET. Es la interfaz principal para interactuar con el runtime y las bibliotecas de .NET, así como para gestionar paquetes NuGet, herramientas y otros componentes.
dotnet --version
Este comando muestra la versión del SDK de .NET instalado en tu máquina, lo que es útil para verificar rápidamente qué versión estás utilizando.
El comando `dotnet` se utiliza para una variedad de tareas esenciales en el desarrollo de aplicaciones .NET:
- Crear nuevos proyectos: A través de plantillas, puedes inicializar rápidamente aplicaciones de consola, aplicaciones web, bibliotecas, y más.
- Compilar código: Facilita la compilación de proyectos .NET en múltiples plataformas.
- Ejecutar aplicaciones: Puedes ejecutar aplicaciones de consola o servidores web directamente desde la línea de comandos.
- Gestionar paquetes: Incluye comandos para agregar, actualizar y listar paquetes NuGet en tu proyecto.
- Probar código: Ejecuta pruebas unitarias para verificar la funcionalidad de tu código.
- Publicar aplicaciones: Empaqueta y prepara aplicaciones para despliegue en diferentes entornos.
Para crear y ejecutar una simple aplicación de consola, usarías:
dotnet new console -n MyApp
cd MyApp
dotnet run
Este conjunto de comandos crea una nueva aplicación de consola, navega al directorio del proyecto, y ejecuta la aplicación.
Como es de esperar, para usar el comando `dotnet`, necesitas tener instalado el SDK de .NET en tu máquina. El SDK incluye todo lo necesario para desarrollar aplicaciones con .NET, incluyendo el runtime y la herramienta `dotnet`.
Los pasos para instalar el sdk son sencillos:
Bajar el instalador de https://dotnet.microsoft.com/download. Tenes que elegir la plataforma que usas (Windows, macOS, Linux).
Una vez que tengas el instalador doble click y le das next todas las veces que necesite (sin miedo al exito)
Luego abris una terminal o línea de comandos y ejecuta:
dotnet --version
Si el comando devuelve un número de versión, la instalación fue exitosa.
Este SDK es necesario no solo para compilar y ejecutar aplicaciones, sino también para utilizar todas las funcionalidades avanzadas que el comando `dotnet` ofrece.
Constructores primarios en C# 12 muy parecidos a los constructores de Scala
Con la llegada de C# 12, el lenguaje ha introducido varias características nuevas que hacen que la programación sea más concisa y expresiva. Entre ellas, los Primary Constructors destacan por simplificar la forma en que las clases inicializan sus miembros. Esta característica es similar a lo que Scala ha ofrecido desde hace tiempo con sus constructores primarios.
Los constructores primarios permiten definir un constructor directamente en la declaración de la clase, lo que reduce la necesidad de código repetitivo y simplifica la definición de clases inmutables.
public class Person(string name, int age)
{
public string Name { get; } = name;
public int Age { get; } = age;
}
En este ejemplo, la clase `Person` tiene un constructor primario que toma dos parámetros: `name` y `age`. Y las propiedades `Name` y `Age` se inicializan directamente desde los parámetros del constructor, haciéndolo más limpio y conciso.
Scala ha ofrecido un concepto similar desde sus primeras versiones. En Scala, los parámetros del constructor principal se definen junto con la clase y se pueden utilizar para inicializar los miembros de la clase de forma directa.
class Person(val name: String, val age: Int)
Tanto C# 12 como Scala eliminan la necesidad de definir un constructor separado y asignar los parámetros a las propiedades de la clase manualmente.
Pero en C#, las propiedades se asignan dentro del cuerpo de la clase usando una asignación explícita (`= name;`), mientras que en Scala, esta asignación es implícita. Y en Scala, se puede controlar la visibilidad de los parámetros del constructor (`val` o `var`) más directamente. En C#, el patrón predeterminado es crear propiedades inmutables con `get;` solamente.
Scala, siendo un lenguaje más orientado a la programación funcional, ofrece características como la eliminación de la necesidad de llaves `{}` para cuerpos de clase simples, mientras que C# sigue siendo más detallado en su sintaxis.
En conclusión, la incorporación de Primary Constructors en C# 12 es un paso en la dirección correcta, haciendo que el lenguaje sea más expresivo y menos verboso, acercándose a la simplicidad que Scala ha ofrecido durante años. Este paralelismo no solo demuestra la influencia de los lenguajes funcionales en lenguajes más tradicionales como C#, sino que también resalta la tendencia hacia una programación más concisa y declarativa.
Y cada día que pasa veo a C# más parecido a Scala ...
miércoles, 14 de agosto de 2024
Try ... catch en Erlang parte 2
Erlang tiene otra estructura de manejo de errores. Esa estructura se define como la palabra clave catch y básicamente captura todos los tipos de excepciones además de los buenos resultados. Es un poco extraña porque muestra una representación diferente de las excepciones:
1> catch throw(whoa).
whoa
2> catch exit(die).
{'EXIT',die}
3> catch 1/0.
{'EXIT',{badarith,[{erlang,'/',[1,0]},
{erl_eval,do_apply,5},
{erl_eval,expr,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}}
4> catch 2+2.
4
Lo que podemos ver de esto es que los lanzamientos siguen siendo los mismos, pero que las salidas y los errores se representan como {'EXIT', Reason}. Esto se debe a que los errores se incorporan al lenguaje después de las salidas (mantuvieron una representación similar para compatibilidad con versiones anteriores).
La forma de leer este seguimiento de pila es la siguiente:
5> catch doesnt:exist(a,4).
{'EXIT',{undef,[{doesnt,exist,[a,4]},
{erl_eval,do_apply,5},
{erl_eval,expr,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}}
El tipo de error es indefinido, lo que significa que la función que llamaste no está definida.
La lista que aparece justo después del tipo de error es un seguimiento de la pila
La tupla que está en la parte superior del seguimiento de la pila representa la última función que se llamó ({Módulo, Función, Argumentos}). Esa es tu función indefinida.
Las tuplas que siguen son las funciones llamadas antes del error. Esta vez tienen la forma {Módulo, Función, Aridad}.
Eso es todo lo que hay que hacer, en realidad.
También se puede obtener un seguimiento de la pila manualmente llamando a erlang:get_stacktrace/0 en el proceso que falló.
A menudo verás que catch está escrito de la siguiente manera:
catcher(X,Y) ->
case catch X/Y of
{'EXIT', {badarith,_}} -> "uh oh";
N -> N
end.
Y como era de esperar:
6> c(exceptions).
{ok,exceptions}
7> exceptions:catcher(3,3).
1.0
8> exceptions:catcher(6,3).
2.0
9> exceptions:catcher(6,0).
"uh oh"
Suena compacto y fácil de capturar excepciones, pero hay algunos problemas con catch. El primero de ellos es la precedencia de operadores:
10> X = catch 4+2.
* 1: syntax error before: 'catch'
10> X = (catch 4+2).
6
Esto no es exactamente intuitivo, dado que la mayoría de las expresiones no necesitan estar entre paréntesis de esta manera. Otro problema con catch es que no se puede ver la diferencia entre lo que parece ser la representación subyacente de una excepción y una excepción real:
11> catch erlang:boat().
{'EXIT',{undef,[{erlang,boat,[]},
{erl_eval,do_apply,5},
{erl_eval,expr,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}}
12> catch exit({undef, [{erlang,boat,[]}, {erl_eval,do_apply,5}, {erl_eval,expr,5}, {shell,exprs,6}, {shell,eval_exprs,6}, {shell,eval_loop,3}]}).
{'EXIT',{undef,[{erlang,boat,[]},
{erl_eval,do_apply,5},
{erl_eval,expr,5},
{shell,exprs,6},
{shell,eval_exprs,6},
{shell,eval_loop,3}]}}
Y no puedes saber la diferencia entre un error y una salida real. También podrías haber usado throw/1 para generar la excepción anterior. De hecho, un throw/1 en un catch también podría ser problemático en otro escenario:
one_or_two(1) -> return;
one_or_two(2) -> throw(return).
Y ahora el problema mortal:
13> c(exceptions).
{ok,exceptions}
14> catch exceptions:one_or_two(1).
return
15> catch exceptions:one_or_two(2).
return
Como estamos detrás de un catch, nunca podemos saber si la función generó una excepción o si devolvió un valor real. Es posible que esto no suceda con mucha frecuencia en la práctica, pero sigue siendo un problema lo suficientemente grave como para justificar la incorporación de la construcción try...catch.
lunes, 12 de agosto de 2024
Procedimientos Almacenados y Funciones en H2
H2 es una base de datos relacional escrita en Java que es especialmente popular por su ligereza y facilidad de uso. Aunque es muy común utilizar H2 como una base de datos en memoria para pruebas, también es lo suficientemente potente para manejar operaciones más avanzadas, como procedimientos almacenados y funciones.
Un procedimiento almacenado es un conjunto de instrucciones SQL que pueden ser almacenadas en la base de datos y ejecutadas cuando sea necesario. Los procedimientos almacenados son útiles porque permiten encapsular lógica compleja en un solo lugar, lo que facilita su mantenimiento y reutilización.
Las funciones son similares a los procedimientos almacenados, pero con algunas diferencias clave. Mientras que un procedimiento almacenado puede ejecutar una serie de comandos SQL y no necesariamente retornar un valor, una función siempre retorna un valor y está diseñada para ser utilizada en expresiones SQL.
En H2, los procedimientos almacenados se crean utilizando la palabra clave `CREATE ALIAS`. Veamos un ejemplo sencillo de cómo crear un procedimiento almacenado que inserta un nuevo registro en una tabla:
CREATE ALIAS insert_user AS $$
void insert_user(Connection conn, String username, String email) throws SQLException {
PreparedStatement prep = conn.prepareStatement("INSERT INTO users(username, email) VALUES(?, ?)");
prep.setString(1, username);
prep.setString(2, email);
prep.execute();
prep.close();
}
$$;
Una vez que el procedimiento almacenado ha sido creado, puedes llamarlo usando `CALL`:
CALL insert_user('JohnDoe', 'john.doe@example.com');
Si necesitas que tu procedimiento devuelva resultados, puedes modificarlo ligeramente:
CREATE ALIAS get_user_by_id AS $$
ResultSet get_user_by_id(Connection conn, int id) throws SQLException {
PreparedStatement prep = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
prep.setInt(1, id);
return prep.executeQuery();
}
$$;
Luego puedes utilizar el procedimiento en una consulta:
CALL get_user_by_id(1);
Al igual que los procedimientos, las funciones en H2 se crean utilizando `CREATE ALIAS`, pero la diferencia es que las funciones siempre retornan un valor. Aquí tienes un ejemplo de una función que calcula el IVA de un precio dado:
CREATE ALIAS calculate_vat AS $$
double calculate_vat(double price) {
return price * 0.21;
}
$$;
Una vez creada, puedes utilizar la función en tus consultas SQL:
SELECT calculate_vat(100) AS vat_amount;
Esto te devolverá el valor con el IVA aplicado.
H2 ofrece un soporte robusto para procedimientos almacenados y funciones, lo que lo hace muy útil incluso en aplicaciones más allá del simple almacenamiento de datos en memoria. Con la capacidad de encapsular lógica SQL compleja en procedimientos y funciones, puedes mantener tu código más limpio y organizado.
domingo, 11 de agosto de 2024
Try... catch en Erlang
Una expresión try... catch es una forma de evaluar una expresión y al mismo tiempo permitirle manejar tanto el caso exitoso como los errores encontrados. La sintaxis general para una expresión de este tipo es:
try Expression of
SuccessfulPattern1 [Guards] ->
Expression1;
SuccessfulPattern2 [Guards] ->
Expression2
catch
TypeOfError:ExceptionPattern1 ->
Expression3;
TypeOfError:ExceptionPattern2 ->
Expression4
end.
Se dice que la expresión entre try y of está protegida. Esto significa que cualquier tipo de excepción que ocurra dentro de esa llamada será capturada. Los patrones y expresiones entre try ... of y catch se comportan exactamente de la misma manera que un case ... of. Finalmente, la parte catch: aquí, puedes reemplazar TypeOfError por error, throw o exit. Si no se proporciona ningún tipo, se asume un throw.
En primer lugar, comencemos un módulo llamado excepciones. Vamos a optar por algo simple:
-module(exceptions).
-compile(export_all).
throws(F) ->
try F() of
_ -> ok
catch
Throw -> {throw, caught, Throw}
end.
Podemos compilarlo y probarlo con diferentes tipos de excepciones:
1> c(exceptions).
{ok,exceptions}
2> exceptions:throws(fun() -> throw(thrown) end).
{throw,caught,thrown}
3> exceptions:throws(fun() -> erlang:error(pang) end).
** exception error: pang
Este try... catch solo recibe Throw. Como se dijo anteriormente, esto se debe a que cuando no se menciona ningún tipo, se asume un Throw. Entonces tenemos funciones con cláusulas catch de cada tipo:
errors(F) ->
try F() of
_ -> ok
catch
error:Error -> {error, caught, Error}
end.
exits(F) ->
try F() of
_ -> ok
catch
exit:Exit -> {exit, caught, Exit}
end.
Y para probarlos:
4> c(exceptions).
{ok,exceptions}
5> exceptions:errors(fun() -> erlang:error("Die!") end).
{error,caught,"Die!"}
6> exceptions:exits(fun() -> exit(goodbye) end).
{exit,caught,goodbye}
viernes, 9 de agosto de 2024
Generar excepciones en Erlang parte 3
Antes de seguir, por favor lee la parte 2. Y recorda que "Hay tres tipos de excepciones en Erlang: errors, throws y exits. Todas tienen diferentes usos :"
Y ahora vamos con throws. Un throws es una clase de excepción que se utiliza para los casos que se espera que el programador maneje. En comparación con exits y errors, en realidad no tienen ninguna intención de "bloquear ese proceso", sino que controlan el flujo. Como se utilizan excepciones mientras se espera que el programador las maneje, suele ser una buena idea documentar su uso dentro de un módulo que las utilice.
La sintaxis para generar una excepción es:
1> throw(permission_denied).
** exception throw: permission_denied
Donde puedes reemplazar permission_denied por cualquier cosa que quieras (incluso "todo está bien", pero eso no es útil y perderás amigos).
Los throws también se pueden usar para retornos no locales cuando se está en una recursión profunda. Un ejemplo de eso es el módulo ssl que usa throw/1 como una forma de enviar tuplas {error, Reason} de regreso a una función de nivel superior. Esta función simplemente devuelve esa tupla al usuario. Esto permite que el implementador solo escriba para los casos exitosos y tenga una función que se ocupe de las excepciones además de todo.
Otro ejemplo podría ser el módulo de matriz, donde hay una función de búsqueda que puede devolver un valor predeterminado proporcionado por el usuario si no puede encontrar el elemento necesario. Cuando no se puede encontrar el elemento, el valor predeterminado se lanza como una excepción y la función de nivel superior lo maneja y lo sustituye con el valor predeterminado proporcionado por el usuario. Esto evita que el programador del módulo tenga que pasar el valor predeterminado como parámetro de cada función del algoritmo de búsqueda, centrándose nuevamente solo en los casos exitosos.
Como regla general, intente limitar el uso de sus lanzamientos para retornos no locales a un solo módulo para facilitar la depuración de su código. También le permitirá cambiar las partes internas de su módulo sin requerir cambios en su interfaz.
Paquete de la biblioteca estándar de Gleam
import gleam/io
pub fn main() {
io.println("Hello, Joe!")
io.println("Hello, Mike!")
}
La biblioteca estándar de Gleam es un paquete de Gleam normal que se ha publicado en el repositorio de paquetes Hex. Puede optar por no utilizarla si lo desea, aunque casi todos los proyectos de Gleam dependen de ella.
Todos los módulos importados que usamos, como gleam/io , son de la biblioteca estándar.
Toda la documentación de la biblioteca estándar está disponible en HexDocs.
martes, 6 de agosto de 2024
Matrices de bits en Gleam
import gleam/io
pub fn main() {
// 8 bit int. In binary: 00000011
io.debug(<<3>>)
io.debug(<<3>> == <<3:size(8)>>)
// 16 bit int. In binary: 0001100000000011
io.debug(<<6147:size(16)>>)
// A bit array of UTF8 data
io.debug(<<"Hello, Joe!":utf8>>)
// Concatenation
let first = <<4>>
let second = <<2>>
io.debug(<<first:bits, second:bits>>)
}
Las matrices de bits representan una secuencia de 1 y 0, y son una sintaxis conveniente para construir y manipular datos binarios.
A cada segmento de una matriz de bits se le pueden dar opciones para especificar la representación utilizada para ese segmento.
- size: el tamaño del segmento en bits.
- unit: la cantidad de bits de los cuales el valor de tamaño es un múltiplo.
- bits: una matriz de bits anidada de cualquier tamaño.
- bytes: una matriz de bits anidada alineada con bytes.
- float: un número de punto flotante de 64 bits.
- int: un int con un tamaño predeterminado de 8 bits.
- big: big endian.
- little: little endian.
- native: el orden de bits del procesador.
- utf8: texto codificado en utf8.
- utf16: texto codificado en utf16.
- utf32: texto codificado en utf32.
- utf8_codepoint: un punto de código utf8.
- utf16_codepoint: un punto de código utf16.
- utf32_codepoint: un punto de código utf32.
- signed: un número con signo.
- unsigned: un número sin signo.
Se pueden dar múltiples opciones a un segmento separándolas con un guion: x:unsigned-little-size(2).
Las matrices de bits tienen un soporte limitado al compilar en JavaScript, no se pueden usar todas las opciones. En el futuro se implementará un soporte completo para matrices de bits.
lunes, 5 de agosto de 2024
Results de Gleam
import gleam/int
import gleam/io
pub fn main() {
let _ = io.debug(buy_pastry(10))
let _ = io.debug(buy_pastry(8))
let _ = io.debug(buy_pastry(5))
let _ = io.debug(buy_pastry(3))
}
pub type PurchaseError {
NotEnoughMoney(required: Int)
NotLuckyEnough
}
fn buy_pastry(money: Int) -> Result(Int, PurchaseError) {
case money >= 5 {
True ->
case int.random(4) == 0 {
True -> Error(NotLuckyEnough)
False -> Ok(money - 5)
}
False -> Error(NotEnoughMoney(required: 5))
}
}
Gleam no utiliza excepciones, sino que los cálculos que pueden tener éxito o fallar devuelven un valor del tipo Result(value, error). Tiene dos variantes:
- Ok, que contiene el valor de retorno de un cálculo exitoso.
- Error, que contiene el motivo de un cálculo fallido.
El tipo es genérico con dos parámetros de tipo, uno para el valor de éxito y otro para el error. Con estos, el resultado puede contener cualquier tipo de éxito o fracaso.
Comúnmente, un programa o biblioteca de Gleam definirá un tipo personalizado con una variante para cada posible problema que pueda surgir, junto con cualquier información de error que pueda ser útil para el programador.
Esto es ventajoso sobre las excepciones, ya que puede ver inmediatamente qué errores puede devolver una función, si los hay, y el compilador se asegurará de que se gestionen. ¡No hay sorpresas desagradables con excepciones inesperadas!
Un valor de resultado se puede gestionar mediante la coincidencia de patrones con una expresión de case, pero dada la frecuencia con la que se devuelven los resultados, esto puede volverse difícil de manejar. El código Gleam comúnmente utiliza el módulo de biblioteca estándar gleam/result y utiliza expresiones cuando trabaja con resultados.
Generar excepciones en Erlang parte 2
Antes de seguir, por favor lee la parte 1. Y recorda que "Hay tres tipos de excepciones en Erlang: errors, throws y exits. Todas tienen diferentes usos :"
Hay dos tipos de excepciones exits: salidas "internas" y salidas "externas". Las salidas internas se activan llamando a la función exit/1 y hacen que el proceso actual detenga su ejecución. Las salidas externas se llaman con exit/2 y tienen que ver con múltiples procesos en el aspecto concurrente de Erlang; como tal, nos centraremos principalmente en las salidas internas y visitaremos el tipo externo más adelante.
Las salidas internas son bastante similares a los errores. De hecho, históricamente hablando, eran lo mismo y solo existía exit/1. Tienen aproximadamente los mismos casos de uso. Entonces, ¿cómo elegir uno? Bueno, la elección no es obvia. Para entender cuándo usar uno u otro, no hay más remedio que comenzar a mirar los conceptos de actores y procesos desde lejos.
En la introducción, comparé los procesos con personas que se comunican por correo. Por ejemplo, un proceso 'A' envia un mensaje a un proceso 'B'
Aquí los procesos pueden enviarse mensajes entre sí. Un proceso también puede escuchar mensajes, esperarlos. También puede elegir qué mensajes escuchar, descartar algunos, ignorar otros, dejar de escuchar después de un tiempo determinado, etc.
Un proceso 'A' enviando 'hola' a un proceso 'B', que a su vez envía un mensaje a C con 'A dice hola!'
Estos conceptos básicos permiten a los implementadores de Erlang utilizar un tipo especial de mensaje para comunicar excepciones entre procesos. Actúan un poco como el último aliento de un proceso; se envían justo antes de que un proceso muera y el código que contiene deje de ejecutarse. Otros procesos que estaban escuchando ese tipo específico de mensaje pueden entonces saber acerca del evento y hacer lo que quieran con él. Esto incluye el registro, el reinicio del proceso que murió, etc.
Un proceso muerto que envía "Estoy muerto" a un proceso "B"
Una vez explicado este concepto, la diferencia entre usar erlang:error/1 y exit/1 es más fácil de entender. Si bien ambos se pueden usar de una manera extremadamente similar, la verdadera diferencia está en la intención. Luego, puede decidir si lo que tiene es "simplemente" un error o una condición que amerita matar el proceso actual. Este punto se fortalece por el hecho de que erlang:error/1 devuelve un seguimiento de la pila y exit/1 no. Si tuviera un seguimiento de la pila bastante grande o muchos argumentos para la función actual, copiar el mensaje de salida a cada proceso que escucha significaría copiar los datos. En algunos casos, esto podría volverse poco práctico.
viernes, 2 de agosto de 2024
Generar excepciones en Erlang
Al intentar supervisar la ejecución del código y protegerse contra errores lógicos, suele ser una buena idea provocar fallos en tiempo de ejecución para que los problemas se detecten de forma temprana.
Hay tres tipos de excepciones en Erlang: errors, throws y exits. Todas tienen diferentes usos :
Errores: Llamar a erlang:error(Reason) finalizará la ejecución en el proceso actual e incluirá un seguimiento de la pila de las últimas funciones llamadas con sus argumentos cuando lo detecte. Estos son los tipos de excepciones que provocan los errores en tiempo de ejecución.
Los errores son los medios que utiliza una función para detener su ejecución cuando no puede esperar que el código que la llama maneje lo que acaba de suceder. Si recibe un error if_clause, ¿qué puede hacer? Cambiar el código y volver a compilar, eso es lo que puede hacer (además de mostrar un bonito mensaje de error).
También puedes definir tu propio tipo de errores:
1> erlang:error(badarith).
** exception error: bad argument in an arithmetic expression
2> erlang:error(custom_error).
** exception error: custom_error
Aquí, el shell Erlang no reconoce custom_error y no tiene una traducción personalizada como "argumento incorrecto en ...", pero se puede usar de la misma manera y el programador puede manejarlo de manera idéntica.
En post posteriores seguiremos con throws y exits.