Translate

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: 

https://ellibrodepython.com/

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}


El siguiente ejemplo del menú muestra cómo combinar todos los tipos de excepciones en un único try... catch. Primero declararemos una función para generar todas las excepciones que necesitamos:


sword(1) -> throw(slice);
sword(2) -> erlang:error(cut_arm);
sword(3) -> exit(cut_leg);
sword(4) -> throw(punch);
sword(5) -> exit(cross_bridge).

black_knight(Attack) when is_function(Attack, 0) ->
    try Attack() of
        _ -> "None shall pass."
    catch
        throw:slice -> "It is but a scratch.";
        error:cut_arm -> "I've had worse.";
        exit:cut_leg -> "Come on you pansy!";
        _:_ -> "Just a flesh wound."
    end.


Aquí is_function/2 es un BIF que garantiza que la variable Attack sea una función de aridad 0. Luego agregamos esto por si acaso:

talk() -> "blah blah".

Y ahora algo completamente diferente:


7> c(exceptions).
{ok,exceptions}
8> exceptions:talk().
"blah blah"
9> exceptions:black_knight(fun exceptions:talk/0).
"None shall pass."
10> exceptions:black_knight(fun() -> exceptions:sword(1) end).
"It is but a scratch."
11> exceptions:black_knight(fun() -> exceptions:sword(2) end).
"I've had worse."
12> exceptions:black_knight(fun() -> exceptions:sword(3) end).
"Come on you pansy!"
13> exceptions:black_knight(fun() -> exceptions:sword(4) end).
"Just a flesh wound."
14> exceptions:black_knight(fun() -> exceptions:sword(5) end).
"Just a flesh wound."


La expresión de la línea 9 demuestra el comportamiento normal, cuando la ejecución de la función se produce normalmente. Cada línea que sigue a esa demuestra la coincidencia de patrones en las excepciones según su clase (throw, error, exit) y la razón asociada a ellas (slice, cut_arm, cut_leg).

Una cosa que se muestra aquí en las expresiones 13 y 14 es una cláusula general para excepciones. El patrón _:_ es lo que necesita utilizar para asegurarse de capturar cualquier excepción de cualquier tipo. En la práctica, debe tener cuidado al utilizar los patrones generales: intente proteger su código de lo que puede controlar, pero no más que eso. Erlang tiene otras funciones para encargarse del resto.

También hay una cláusula adicional que se puede agregar después de un try ... catch que siempre se ejecutará. Esto es equivalente al bloque 'finally' en muchos otros lenguajes:


try Expr of
    Pattern -> Expr1
catch
    Type:Exception -> Expr2
after % this always gets executed
    Expr3
end


No importa si hay errores o no, se garantiza que las expresiones dentro de la parte after se ejecutarán. Sin embargo, no puede obtener ningún valor de retorno de la construcción after. Por lo tanto, after se usa principalmente para ejecutar código con efectos secundarios. El uso canónico de esto es cuando desea asegurarse de que un archivo que estaba leyendo se cierre independientemente de que se generen excepciones o no.

Ahora sabemos cómo manejar las 3 clases de excepciones en Erlang con bloques catch. Sin embargo, le he ocultado información: en realidad, es posible tener más de una expresión entre try y of!


whoa() ->
    try
        talk(),
        _Knight = "None shall Pass!",
        _Doubles = [N*2 || N <- lists:seq(1,100)],
        throw(up),
        _WillReturnThis = tequila
    of
        tequila -> "hey this worked!"
    catch
        Exception:Reason -> {caught, Exception, Reason}
    end.


Al llamar a exceptions:whoa(), obtendremos lo obvio {caught, throw, up}, debido a throw(up). Entonces, sí, es posible tener más de una expresión entre try y of...

Lo que acabo de resaltar en exceptions:whoa/0 y que quizás no hayas notado es que cuando usamos muchas expresiones de esa manera, es posible que no siempre nos importe cuál es el valor de retorno. La parte of se vuelve un poco inútil. Bueno, buenas noticias, puedes dejarla:

im_impressed() ->
    try
        talk(),
        _Knight = "None shall Pass!",
        _Doubles = [N*2 || N <- lists:seq(1,100)],
        throw(up),
        _WillReturnThis = tequila
    catch
        Exception:Reason -> {caught, Exception, Reason}
    end.

Es importante saber que la parte protegida de una excepción no puede ser recursiva de cola. La máquina virtual siempre debe mantener una referencia allí en caso de que aparezca una excepción.

Debido a que la construcción try ... catch sin la parte of no tiene nada más que una parte protegida, llamar a una función recursiva desde allí puede ser peligroso para programas que se supone que se ejecutarán durante mucho tiempo (que es el nicho de Erlang). Después de suficientes iteraciones, se quedará sin memoria o su programa se volverá más lento sin saber realmente por qué. Al colocar sus llamadas recursivas entre of y catch, no está en una parte protegida y se beneficiará de la optimización de última llamada.

Algunas personas usan try ... of ... catch en lugar de try ... catch de forma predeterminada para evitar errores inesperados de ese tipo, excepto para el código obviamente no recursivo con resultados que no serán utilizados por nada. ¡Lo más probable es que pueda tomar su propia decisión sobre qué hacer!

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.

jueves, 1 de agosto de 2024

¿Por qué una clase abstracta en Python tiene que heredar de abc.ABC?


En Python, una clase abstracta no necesariamente tiene que heredar de `abc.ABC`, pero es una práctica común y recomendable. La razón principal para heredar de `abc.ABC` es que proporciona una forma estructurada y explícita de definir clases abstractas y métodos abstractos, asegurando que las subclases implementen esos métodos.

Heredar de `abc.ABC` permite definir métodos abstractos utilizando el decorador `@abstractmethod`. Esto asegura que cualquier subclase debe implementar esos métodos, de lo contrario, se levantará una excepción `TypeError`.

Las clases que heredan de `abc.ABC` no pueden ser instanciadas directamente. Esto ayuda a evitar errores al intentar crear instancias de una clase que debería ser abstracta.

Utilizar `abc.ABC` fomenta un enfoque más formal y orientado a objetos en el diseño de software, haciendo que el código sea más mantenible y entendible.

Veamos un ejemplo:


from abc import ABC, abstractmethod


class Animal(ABC):

    

    @abstractmethod

    def hacer_sonido(self):

        pass


# Intentar instanciar la clase abstracta resulta en un error

# animal = Animal()  # Esto lanzará TypeError


# Definir una subclase concreta que implementa el método abstracto

class Perro(Animal):

    

    def hacer_sonido(self):

        return "Guau!"


class Gato(Animal):

    

    def hacer_sonido(self):

        return "Miau!"


# Instanciar las subclases concretas

perro = Perro()

gato = Gato()


print(perro.hacer_sonido())  # Salida: Guau!

print(gato.hacer_sonido())   # Salida: Miau!



Si decidimos no heredar de `abc.ABC`, puedes definir métodos que esperas que las subclases implementen, pero no tendrás las garantías estructurales que ofrece `abc.ABC`. Por ejemplo:


class Animal:

    

    def hacer_sonido(self):

        raise NotImplementedError("Este método debe ser implementado por subclases")


class Perro(Animal):

    

    def hacer_sonido(self):

        return "Guau!"


# Esto funcionará, pero no es tan estructurado como usar abc.ABC

perro = Perro()

print(perro.hacer_sonido())  # Salida: Guau!


Usar `abc.ABC` para definir clases abstractas en Python es una práctica recomendada porque proporciona una forma clara y estructurada de garantizar que las subclases implementen métodos abstractos y ayuda a prevenir errores al intentar instanciar clases abstractas directamente.

lunes, 29 de julio de 2024

Nil de Gleam


import gleam/io


pub fn main() {

  let x = Nil

  io.debug(x)

  let result = io.println("Hello!")

  io.debug(result == Nil)

}


Nil es el tipo de unidad de Gleam. Es un valor que devuelven las funciones que no tienen nada más que devolver, ya que todas las funciones deben devolver algo.

Nil no es un valor válido de ningún otro tipo. Por lo tanto, los valores en Gleam no son nulos. Si el tipo de un valor es Nil, entonces es el valor Nil. Si es otro tipo, entonces el valor no es Nil.