Translate

martes, 3 de septiembre de 2024

Punteros inteligentes en C++


En C++, el manejo de memoria es crucial para el rendimiento y la estabilidad de las aplicaciones. Los punteros tradicionales (`raw pointers`) requieren que los desarrolladores gestionen manualmente la asignación y liberación de memoria, lo que puede llevar a errores como fugas de memoria, doble liberación, y accesos a memoria inválida.

Para facilitar esta gestión y reducir errores, C++ introduce los punteros inteligentes o smart pointers, que son clases que actúan como punteros, pero con capacidades adicionales para manejar automáticamente la memoria. Desde C++11, la Biblioteca Estándar de C++ incluye varias implementaciones de punteros inteligentes, que simplifican la vida del desarrollador.

Los punteros inteligentes son objetos que encapsulan un puntero nativo (`raw pointer`) y aseguran que la memoria se libere adecuadamente cuando ya no se necesita. Se encargan automáticamente de la destrucción del objeto apuntado, previniendo fugas de memoria.

En C++ estándar, existen tres tipos principales de punteros inteligentes:

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

std::unique_ptr: Representa la propiedad exclusiva de un objeto. Solo un std::unique_ptr puede apuntar a un objeto en un momento dado.

Cuando el `std::unique_ptr` se destruye (por ejemplo, cuando sale del ámbito), la memoria del objeto que apunta se libera automáticamente.

Es ideal para recursos que no deben ser compartidos entre diferentes partes del programa.


#include <iostream>

#include <memory>


void uniquePtrExample() {

    std::unique_ptr<int> ptr = std::make_unique<int>(42);

    std::cout << "Valor: " << *ptr << std::endl;

}


En este ejemplo, `ptr` es un puntero exclusivo a un entero que contiene el valor 42. No se puede copiar o compartir, solo se puede transferir mediante `std::move`.


std::shared_ptr permite que varios punteros compartan la propiedad de un objeto. La memoria se libera cuando el último `std::shared_ptr` que apunta al objeto se destruye.

Utiliza un contador de referencias para rastrear cuántos `std::shared_ptr` apuntan al mismo objeto. Cuando el contador llega a cero, se libera la memoria.

Es  útil cuando múltiples partes del código necesitan acceso compartido a un recurso.


#include <iostream>

#include <memory>


void sharedPtrExample() {

    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);

    std::shared_ptr<int> ptr2 = ptr1; // Compartir propiedad


    std::cout << "Valor: " << *ptr1 << std::endl;

    std::cout << "Referencia count: " << ptr1.use_count() << std::endl;

}


En este ejemplo, `ptr1` y `ptr2` comparten la propiedad del mismo entero. La memoria se liberará solo cuando ambos punteros se destruyan.


Y std::weak_ptr es un puntero no propietario que se utiliza junto con `std::shared_ptr` para evitar ciclos de referencia que podrían impedir la liberación de memoria.

No incrementa el contador de referencias del objeto. Se utiliza principalmente para obtener acceso temporal al objeto sin prolongar su vida.

Ideal para resolver problemas de ciclos de referencias que pueden ocurrir con `std::shared_ptr`.


#include <iostream>

#include <memory>


void weakPtrExample() {

    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);

    std::weak_ptr<int> weakPtr = ptr1;


    if (auto sharedPtr = weakPtr.lock()) {

        std::cout << "Valor: " << *sharedPtr << std::endl;

    } else {

        std::cout << "El objeto ya no existe." << std::endl;

    }

}


En este ejemplo, `weakPtr` no impide que el `shared_ptr` destruya el objeto cuando sale del ámbito, pero permite acceder temporalmente al objeto si aún existe.

Los punteros inteligentes son una característica poderosa de C++ que facilita la gestión de memoria y ayuda a prevenir errores comunes asociados con los punteros tradicionales. `std::unique_ptr`, `std::shared_ptr` y `std::weak_ptr` cubren una variedad de casos de uso, desde la propiedad exclusiva hasta la compartida y el acceso temporal, ofreciendo soluciones robustas para la administración de recursos en C++.

domingo, 1 de septiembre de 2024

El comando dotnet parte 2


Antes de empezar lee la parte 1. No te adelantes. 

dotnet new: Crear nuevos proyectos a partir de plantillas

El comando `dotnet new` se utiliza para crear un nuevo proyecto o solución desde una plantilla predeterminada. .NET ofrece una variedad de plantillas que cubren diferentes tipos de aplicaciones, como aplicaciones de consola, aplicaciones web, bibliotecas de clases, y más.

Veamos unos ejemplos: 

dotnet new console -n MyConsoleApp

Este comando crea un nuevo proyecto de consola en C# con el nombre `MyConsoleApp`.

  -n o --name: Especifica el nombre del proyecto o solución.
  -o o --output: Define el directorio donde se creará el proyecto.
  --list: Muestra todas las plantillas disponibles.

Plantillas comunes:
  • console: Aplicación de consola.
  • classlib: Biblioteca de clases.
  • web: Aplicación web ASP.NET Core.
  • mvc: Aplicación ASP.NET Core MVC.
  • blazorserver: Aplicación Blazor Server.

El comando `dotnet build` compila el proyecto y todas sus dependencias, generando los binarios necesarios para la ejecución. Es útil para verificar que el código se puede compilar correctamente y que no hay errores de compilación.

dotnet build

Este comando compila el proyecto en el directorio actual.

-c o --configuration: Especifica la configuración de compilación (por ejemplo, `Debug` o `Release`). El valor predeterminado es `Debug`.
-o o --output: Especifica el directorio de salida para los archivos compilados.

Veamos un ejemplo: 

dotnet build -c Release

dotnet run: Ejecutar un proyecto

El comando `dotnet run` permite compilar y ejecutar una aplicación directamente desde la línea de comandos. Es particularmente útil durante el desarrollo, ya que simplifica el ciclo de construir y ejecutar.
Se escribe: 

dotnet run

Este comando compila y ejecuta el proyecto en el directorio actual.
Podemos agregar: 
--project: Permite especificar un proyecto o solución para ejecutar si estás en un directorio con múltiples proyectos.
 -c o --configuration: Ejecuta el proyecto en la configuración especificada (`Debug`, `Release`, etc.).

Por ejemplo: 

dotnet run --project MyConsoleApp/MyConsoleApp.csproj

dotnet test: Ejecutar pruebas unitarias

El comando `dotnet test` se utiliza para ejecutar pruebas unitarias en un proyecto de pruebas. Este comando ejecuta todas las pruebas definidas en el proyecto y proporciona un informe con los resultados.

Un ejemplo de uso : 

dotnet test

Ejecuta todas las pruebas en el proyecto o solución del directorio actual.
Podemos agregar:
--filter: Filtra las pruebas que se ejecutarán basado en criterios específicos (por ejemplo, nombre del test, categoría).
-l o --logger: Especifica un logger para formatear los resultados de las pruebas.
 --no-build: Evita la compilación del proyecto antes de ejecutar las pruebas, útil si el proyecto ya está compilado.


dotnet test --filter "FullyQualifiedName~MyNamespace.MyTestClass"

dotnet publish: Publicar un proyecto

El comando `dotnet publish` compila el proyecto y empaqueta los archivos necesarios para su despliegue en un entorno específico. Es el paso final antes de implementar la aplicación en producción.

Por ejemplo:

dotnet publish -c Release -o ./publish


Compila el proyecto en modo `Release` y publica los archivos en el directorio `./publish`.

Podemos agregar:
-r o --runtime: Especifica el runtime de destino para la publicación (por ejemplo, `win-x64`, `linux-x64`).
 --self-contained: Genera una publicación que incluye el runtime .NET, lo que permite ejecutar la aplicación en una máquina sin .NET instalado.
-p o --property: Define propiedades adicionales de MSBuild durante la publicación.

Por ejemplo:

dotnet publish -c Release -r win-x64 --self-contained

Estos comandos cubren las operaciones básicas más comunes que los desarrolladores necesitan realizar durante el desarrollo de aplicaciones .NET. Con esta base, podremos gestionar fácilmente proyectos, compilarlos, ejecutarlos, probarlos y publicarlos.

El Módulo Result de Gleam



import gleam/int

import gleam/io

import gleam/result


pub fn main() {

  io.println("=== map ===")

  let _ = io.debug(result.map(Ok(1), fn(x) { x * 2 }))

  let _ = io.debug(result.map(Error(1), fn(x) { x * 2 }))


  io.println("=== try ===")

  let _ = io.debug(result.try(Ok("1"), int.parse))

  let _ = io.debug(result.try(Ok("no"), int.parse))

  let _ = io.debug(result.try(Error(Nil), int.parse))


  io.println("=== unwrap ===")

  io.debug(result.unwrap(Ok("1234"), "default"))

  io.debug(result.unwrap(Error(Nil), "default"))


  io.println("=== pipeline ===")

  int.parse("-1234")

  |> result.map(int.absolute_value)

  |> result.try(int.remainder(_, 42))

  |> io.debug

}

=== map ===
Ok(2)
Error(1)
=== try ===
Ok(1)
Error(Nil)
Error(Nil)
=== unwrap ===
"1234"
"default"
=== pipeline ===
Ok(16)

El módulo de la biblioteca estándar gleam/result contiene funciones para trabajar con resultados. Los programas Gleam harán un uso intensivo de este módulo para evitar expresiones de caso anidadas excesivas al llamar a múltiples funciones que pueden fallar.

  • map actualiza un valor contenido dentro de Ok de un resultado llamando a una función dada sobre él. Si el resultado es un error, no se llama a la función.
  • try ejecuta una función que devuelve un resultado sobre el valor contenido dentro de Ok de un resultado. Si el resultado es un error, no se llama a la función. Esto es útil para encadenar varias llamadas de función que pueden fallar, una tras otra, deteniéndose en el primer error.
  • unwrap extrae el valor de éxito de un resultado o devuelve un valor predeterminado si el resultado es un error.

Las funciones de resultado se utilizan a menudo con canalizaciones para encadenar varias llamadas a funciones que devuelven resultados.

viernes, 30 de agosto de 2024

Primary constructors in C# 12 very similar to Scala constructors


With the arrival of C# 12, the language has introduced several new features that make programming more concise and expressive. Among them, Primary Constructors stand out for simplifying the way classes initialize their members. This feature is similar to what Scala has long offered with its primary constructors.

Primary Constructors allow a constructor to be defined directly in the class declaration, which reduces the need for repetitive code and simplifies the definition of immutable classes.


public class Person(string name, int age)

{

    public string Name { get; } = name;

    public int Age { get; } = age;

}


In this example, the `Person` class has a primary constructor that takes two parameters: name and age. And the Name and Age properties are initialized directly from the constructor parameters, making it cleaner and more concise.

Scala has offered a similar concept since its earliest versions. In Scala, the main constructor parameters are defined along with the class and can be used to initialize the class members directly.


class Person(val name: String, val age: Int)


Both C# 12 and Scala eliminate the need to define a separate constructor and assign the parameters to the class properties manually.

But in C#, properties are assigned inside the class body using an explicit assignment (= name;), while in Scala, this assignment is implicit. And in Scala, you can control the visibility of constructor parameters (val or var) more directly. In C#, the default pattern is to create immutable properties with get; only.

Scala, being a more functional programming oriented language, offers features such as eliminating the need for {} braces for simple class bodies, while C# remains more verbose in its syntax.

In conclusion, the addition of Primary Constructors in C# 12 is a step in the right direction, making the language more expressive and less verbose, approaching the simplicity that Scala has offered for years. This parallel not only demonstrates the influence of functional languages on more traditional languages like C#, but also highlights the trend toward more concise, declarative programming.

And with each passing day I see C# becoming more like Scala ...

martes, 27 de agosto de 2024

Lambdas en C++


El lenguaje C++ es conocido por su potencia y flexibilidad, y una de las características que refuerza esta reputación es la introducción de las expresiones lambda. Estas funciones anónimas y ligeras permiten escribir código más limpio y conciso, especialmente en escenarios donde se necesitan funciones pequeñas y de un solo uso. 

Una lambda es una función anónima que se puede definir en línea en el lugar donde se utiliza. Las lambdas permiten crear pequeñas funciones sin necesidad de nombrarlas o declararlas previamente. Fueron introducidas en C++11 y han sido una herramienta clave para el desarrollo moderno en C++.

La sintaxis básica de una lambda en C++ es :


[captura](parametros) -> tipo_retorno {

    // cuerpo de la lambda

};



  • [captura]: Define cómo la lambda captura las variables del entorno en el que se declara.
  • (parametros): Lista de parámetros que la lambda acepta.
  • -> tipo_retorno: Especifica el tipo de retorno de la lambda (puede omitirse si el compilador puede inferirlo).
  • { cuerpo }: El código que define la función de la lambda.


Veamos un ejemplo simple de una lambda que suma dos números:


#include <iostream>


int main() {

    auto suma = [](int a, int b) -> int {

        return a + b;

    };


    std::cout << "La suma de 3 y 4 es: " << suma(3, 4) << std::endl;

    return 0;

}


En este ejemplo, la lambda captura dos parámetros `a` y `b`, y retorna su suma.

Una de las características más poderosas de las lambdas es su capacidad para capturar variables del entorno donde son definidas. Hay varias formas de hacerlo:

  • Captura por valor: `[x]` captura `x` por valor.
  • Captura por referencia: `[&x]` captura `x` por referencia.
  • Captura todo por valor: `[=]` captura todas las variables que se usan en la lambda por valor.
  • Captura todo por referencia: `[&]` captura todas las variables que se usan en la lambda por referencia.


#include <iostream>


int main() {

    int x = 10;

    int y = 20;


    auto suma = [x, &y]() {

        y = x + y;

    };


    suma();

    std::cout << "El nuevo valor de y es: " << y << std::endl;  // Imprime 30

    return 0;

}


Aquí, `x` se captura por valor, y `y` se captura por referencia, lo que significa que cualquier modificación de `y` dentro de la lambda afecta a `y` fuera de la lambda.

Las lambdas son particularmente útiles cuando se combinan con las funciones de la STL (Standard Template Library) como `std::sort`, `std::for_each`, etc.

Por ejemplo, ordenar un vector de enteros en orden descendente usando `std::sort` y una lambda:


#include <iostream>

#include <vector>

#include <algorithm>


int main() {

    std::vector<int> vec = {3, 1, 4, 1, 5, 9};


    std::sort(vec.begin(), vec.end(), [](int a, int b) {

        return a > b;  // Orden descendente

    });


    for (int n : vec) {

        std::cout << n << " ";  // Imprime: 9 5 4 3 1 1

    }

    return 0;

}


Por defecto, las variables capturadas por valor dentro de una lambda no pueden ser modificadas. Sin embargo, si necesitas modificar las variables capturadas por valor, puedes declarar la lambda como `mutable`:


#include <iostream>


int main() {

    int x = 10;


    auto incrementa = [x]() mutable {

        x++;

        std::cout << "Valor dentro de la lambda: " << x << std::endl;

    };


    incrementa();  // Imprime 11

    std::cout << "Valor fuera de la lambda: " << x << std::endl;  // Imprime 10


    return 0;

}


En este caso, `x` se incrementa dentro de la lambda, pero fuera de ella permanece inalterado.

Las lambdas en C++ son una herramienta poderosa para escribir código más claro y conciso. Facilitan la escritura de funciones pequeñas y de un solo uso y son especialmente útiles cuando se trabaja con funciones de la STL y otras APIs que aceptan funciones como parámetros.

domingo, 25 de agosto de 2024

Módulo List de Gleam


import gleam/io

import gleam/list


pub fn main() {

  let ints = [0, 1, 2, 3, 4, 5]


  io.println("=== map ===")

  io.debug(list.map(ints, fn(x) { x * 2 }))


  io.println("=== filter ===")

  io.debug(list.filter(ints, fn(x) { x % 2 == 0 }))


  io.println("=== fold ===")

  io.debug(list.fold(ints, 0, fn(count, e) { count + e }))


  io.println("=== find ===")

  let _ = io.debug(list.find(ints, fn(x) { x > 3 }))

  io.debug(list.find(ints, fn(x) { x > 13 }))

}

=== map ===
[0, 2, 4, 6, 8, 10]
=== filter ===
[0, 2, 4]
=== fold ===
15
=== find ===
Ok(4)
Error(Nil)

El módulo de la biblioteca estándar gleam/list contiene funciones para trabajar con listas. Es probable que un programa Gleam haga un uso intensivo de este módulo, ya que las distintas funciones sirven como diferentes tipos de bucles sobre listas.

  • map crea una nueva lista ejecutando una función en cada elemento de la lista.
  • filter crea una nueva lista que contiene solo los elementos para los que una función devuelve verdadero.
  • fold combina todos los elementos de una lista en un único valor ejecutando una función de izquierda a derecha en cada elemento, pasando el resultado de la llamada anterior a la siguiente llamada.
  • find devuelve el primer elemento de una lista para el que una función devuelve verdadero.

Vale la pena familiarizarse con todas las funciones de este módulo al escribir código Gleam, ¡las usará mucho!

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: 

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!