Translate

sábado, 14 de septiembre de 2024

Programemos una función que nos indique si una palabra es palíndromo en Erlang


Con el titulo explique todo. Entonces programemos: 

 

-module(palindrome).

-export([is_palindrome/1]).


is_palindrome(Word) ->

    NormalizedWord = string:to_lower(Word), % Convertir a minúsculas para evitar errores por mayúsculas

    NormalizedWord == lists:reverse(NormalizedWord).


Y si lo probamos: 

1> c(palindrome).
{ok,palindrome}
2> palindrome:is_palindrome("radar").
true
3> palindrome:is_palindrome("hello").
false


Como vemos anda muy bien, el tema es que tenemos que dar vuelta la palabra para comparar, lo podemos hacer un poquito más eficiente. Podriamos comparar el primer caracter con el ultimo, el segundo con el anteultimo y así ...

-module(palindrome).
-export([is_palindrome/1]).

is_palindrome(Word) ->
    NormalizedWord = string:to_lower(Word), % Convertir a minúsculas
    check_palindrome(NormalizedWord).

check_palindrome([]) -> true;  % Caso base: una palabra vacía es palíndroma
check_palindrome([_]) -> true; % Caso base: una palabra de un solo carácter es palíndroma
check_palindrome(Word) ->
    case lists:nth(1, Word) == lists:nth(length(Word), Word) of
        true -> check_palindrome(lists:sublist(Word, 2, length(Word)-2));
        false -> false
    end.

Y si lo probamos: 

1> c(palindrome).
{ok,palindrome}
2> palindrome:is_palindrome("radar").
true
3> palindrome:is_palindrome("hello").
false
4> palindrome:is_palindrome("Aibohphobia").
true

Este enfoque es más eficiente en términos de memoria porque no genera una nueva cadena invertida, sino que trabaja directamente comparando los extremos y reduciendo la longitud de la palabra.


¿Qué es ANTLR?



ANTLR es un generador de analizadores. Un analizador toma un fragmento de texto y lo transforma en una estructura organizada, un árbol de análisis, también conocido como árbol de sintaxis abstracta (AST). Puedes pensar en el AST como una historia que describe el contenido del código, o también como su representación lógica, creada al juntar las distintas piezas. 

Qué debes hacer para obtener un AST:

  1. definir una gramática lexer y analizador
  2. invocar ANTLR: generará un lexer y un analizador en su lenguaje de destino (por ejemplo, Java, Python, C#, JavaScript)
  3. use el lexer y el analizador generados: los invoca pasando el código para reconocer y le devuelven un árbol de análisis

Por lo tanto, debe comenzar definiendo una gramática lexer y analizador para lo que está analizando. Normalmente la “cosa” es un lenguaje, pero también podría ser un formato de datos, un diagrama o cualquier tipo de estructura que se represente con texto.

Tenga en cuenta que técnicamente lo que obtiene de ANTLR es un árbol de análisis en lugar de un AST. La diferencia es que un árbol de análisis es exactamente lo que sale del analizador, mientras que AST es una versión más refinada del árbol de análisis. El AST se crea manipulando el árbol de análisis para obtener algo que sea más fácil de usar en las partes posteriores de su programa. Estos cambios a veces son necesarios porque un árbol de análisis puede estar organizado de manera que el análisis sea más fácil o tenga un mejor rendimiento. Sin embargo, es posible que prefieras algo más fácil de usar en el resto del programa.

La distinción es discutible en los ejemplos que se muestran aquí, dado que son bastante simples, por lo que aquí usamos los términos indistintamente. Sin embargo, es algo a tener en cuenta al leer otros documentos.

Dejo link: https://www.antlr.org/

viernes, 13 de septiembre de 2024

Tipos opacos de Gleam


import gleam/io


pub fn main() {

  let positive = new(1)

  let zero = new(0)

  let negative = new(-1)


  io.debug(to_int(positive))

  io.debug(to_int(zero))

  io.debug(to_int(negative))

}


pub opaque type PositiveInt {

  PositiveInt(inner: Int)

}


pub fn new(i: Int) -> PositiveInt {

  case i >= 0 {

    True -> PositiveInt(i)

    False -> PositiveInt(0)

  }

}


pub fn to_int(i: PositiveInt) -> Int {

  i.inner

}


El Resultado : 

1
0
0

Los tipos opacos son tipos en los que el tipo personalizado es público y puede ser utilizado por otros módulos, pero los constructores del tipo son privados y solo pueden ser utilizados por el módulo que define el tipo. Esto evita que otros módulos construyan o realicen una coincidencia de patrones con el tipo.

Esto es útil para crear tipos con constructores inteligentes. Un constructor inteligente es una función que construye un valor de un tipo, pero es más restrictivo que si el programador utilizara uno de los constructores del tipo directamente. Esto puede ser útil para garantizar que el tipo se utilice correctamente.

Por ejemplo, este tipo personalizado PositiveInt es opaco. Si otros módulos quieren construir uno, deben utilizar la nueva función, que garantiza que el entero sea positivo.

martes, 10 de septiembre de 2024

Descubre y Mejora tus Habilidades de Programación con Exercism.org


Si estás buscando una forma práctica y desafiante de mejorar tus habilidades de programación, Exercism.org es una plataforma que no querrás pasar por alto. Diseñada tanto para principiantes como para desarrolladores experimentados, Exercism ofrece ejercicios de codificación en más de 50 lenguajes de programación.

Cada ejercicio te permite resolver problemas reales y recibir feedback de mentores experimentados. Lo mejor de todo es que puedes aprender a tu propio ritmo, sin plazos ni restricciones.

Ya sea que estés aprendiendo un nuevo lenguaje o profundizando en tus conocimientos actuales, Exercism.org es un recurso invaluable para cualquier programador.

Dejo link:  https://exercism.org/

lunes, 9 de septiembre de 2024

Comprendiendo `ref`, `out` e `in` en C#


ref, out, e in permiten controlar cómo se pasan los parámetros a los métodos, pero no es fácil recordar las diferencias de cada uno. Bueno, vamos a ver si este post puede dar luz a este asunto. 

Cuando llamamos a un método en C#, los parámetros se pueden pasar de diferentes maneras: por valor o por referencia. Entender la diferencia es crucial para controlar cómo los métodos interactúan con las variables que reciben.

  • Por Valor: El método recibe una copia del valor, por lo que los cambios dentro del método no afectan la variable original.
  • Por Referencia: El método recibe una referencia a la variable original, permitiendo modificar su valor.


ref: Pasar Parámetros por Referencia para Modificar


La palabra clave `ref` se utiliza cuando se quiere que un método tenga la capacidad de modificar la variable que se le pasa como parámetro. El parámetro debe estar inicializado antes de pasar al método.


void Incrementar(ref int numero) {

    numero++;

}


int valor = 5;

Incrementar(ref valor);

// valor ahora es 6


`ref` en C# es similar a pasar un parámetro por referencia utilizando `&` en C++, lo que permite la modificación del valor original dentro del método.

`out` se usa cuando un método necesita devolver un valor a través de un parámetro. A diferencia de `ref`, el parámetro no necesita estar inicializado antes de pasarse al método.

Por ejemplo: 

void AsignarValor(out int resultado) {

    resultado = 10;

}


int valor;

AsignarValor(out valor);

// valor ahora es 10


in: Pasar Parámetros por Referencia para Solo Lectura

La palabra clave `in` se usa para pasar un parámetro por referencia de manera que el método pueda leer el valor, pero no modificarlo. Esto es útil para evitar copias innecesarias de grandes estructuras de datos, mientras se asegura que no se alterarán.


void MostrarValor(in int valor) {

    Console.WriteLine(valor);

}


int numero = 5;

MostrarValor(in numero);

// Imprime 5


`in` se puede comparar con el uso de referencias constantes en C++ (`const int&`), donde la función puede leer el valor, pero no modificarlo.


ref` y out permiten que un método modifique el valor de un parámetro, pero `ref` requiere que la variable esté inicializada antes de ser pasada, mientras que `out` no.

in permite la pasada por referencia para mejorar la eficiencia (especialmente con grandes estructuras), pero no permite la modificación del valor, mientras que `ref` sí.

Debemos usar ref` cuando necesites que el método pueda leer y modificar el valor original.  Y out cuando quieras devolver un valor a través de un parámetro que no necesita estar inicializado.

Y  in, cuando se quiere pasar grandes estructuras de datos por referencia para evitar copias, pero asegurándote de que el método no pueda modificar el valor.

Este conocimiento permite a los desarrolladores de C# escribir métodos más flexibles y eficientes, optimizando el rendimiento y controlando cómo se manipulan los datos.

viernes, 6 de septiembre de 2024

Módulo de opciones



import gleam/io

import gleam/option.{type Option, None, Some}


pub type Person {

  Person(name: String, pet: Option(String))

}


pub fn main() {

  let person_with_pet = Person("Al", Some("Nubi"))

  let person_without_pet = Person("Maria", None)


  io.debug(person_with_pet)

  io.debug(person_without_pet)

}


Person(name: "Al", pet: Some("Nubi"))
Person(name: "Maria", pet: None)


Los valores en Gleam no son nulos, por lo que el módulo de la biblioteca estándar gleam/option define el tipo Option de Gleam, que se puede utilizar para representar un valor que está presente o ausente.

El tipo option es muy similar al tipo result, pero no tiene un valor de error. Algunos lenguajes tienen funciones que devuelven una opción cuando no hay detalles de error adicionales que proporcionar, pero Gleam siempre utiliza result. Esto hace que todas las funciones falibles sean consistentes y elimina cualquier código repetitivo que se requeriría al mezclar funciones que utilizan cada tipo.

seekg, seekp, tellg y tellp de C++




No soy mucho de utilizar archivos pero cuando tengo que hacer algo tengo que investigar las funciones seek* y tell*. Este post tiene como objetivo que me aprenda esto, por favooor!!


seekg` y `tellg:  Estas funciones se utilizan para manejar la posición del cursor de lectura en un flujo de entrada (`istream`).


seekg (seek get):

Cambia la posición del cursor de lectura en un flujo de entrada.

Su sintaxis es :  stream.seekg(position); o stream.seekg(offset, direction);

Y los Parámetros:

  1.     position: Especifica la nueva posición absoluta.
  2.     offset: Desplazamiento en bytes desde el punto indicado por `direction`.
  3.     direction: Puede ser `std::ios::beg` (inicio del archivo), `std::ios::cur` (posición actual), o `std::ios::end` (final del archivo).

Veamos un ejemplo: 

std::ifstream file("example.txt");

file.seekg(10, std::ios::beg); // Mueve el cursor de lectura al décimo byte desde el inicio del archivo


tellg (tell get):

Retorna la posición actual del cursor de lectura en un flujo de entrada.

Su sintaxis es :stream.tellg();

Devuelve la posición actual como un valor de tipo std::streampos.

Veamos un ejemplo: 

std::ifstream file("example.txt");

std::streampos pos = file.tellg(); // Obtiene la posición actual del cursor de lectura


seekp y tellp: Estas funciones se utilizan para manejar la posición del cursor de escritura en un flujo de salida (`ostream`).


seekp (seek put): Cambia la posición del cursor de escritura en un flujo de salida.

Su sintaxis es stream.seekp(position); o stream.seekp(offset, direction);

Los parámetros son:

  1.    position: Especifica la nueva posición absoluta.
  2.    offset: Desplazamiento en bytes desde el punto indicado por `direction`.
  3.    direction: Puede ser `std::ios::beg` (inicio del archivo), `std::ios::cur` (posición actual), o `std::ios::end` (final del archivo).

Veamos un ejemplo: 


std::ofstream file("example.txt");

file.seekp(5, std::ios::beg); // Mueve el cursor de escritura al quinto byte desde el inicio del archivo


tellp (tell put): Retorna la posición actual del cursor de escritura en un flujo de salida.

Su sintaxis: stream.tellp();

Devuelve la posición actual como un valor de tipo `std::streampos`.


Un ejemplo: 

std::ofstream file("example.txt");

std::streampos pos = file.tellp(); // Obtiene la posición actual del cursor de escritura


Ambos pares de funciones permiten manipular y consultar la posición de los cursores dentro de los archivos o flujos, pero seekg/tellg están destinados a la lectura, mientras que seekp/tellp están destinados a la escritura. Vamos a ver si me lo aprendo. 

Módulo Dict de Gleam

 


import gleam/dict

import gleam/io


pub fn main() {

  let scores = dict.from_list([#("Lucy", 13), #("Drew", 15)])

  io.debug(scores)


  let scores =

    scores

    |> dict.insert("Bushra", 16)

    |> dict.insert("Darius", 14)

    |> dict.delete("Drew")

  io.debug(scores)

}

El resultado : 

dict.from_list([#("Drew", 15), #("Lucy", 13)])
dict.from_list([#("Darius", 14), #("Bushra", 16), #("Lucy", 13)])


El módulo estándar gleam/dict define el tipo Dict de Gleam y las funciones para trabajar con él. Un dict es una colección de claves y valores que otros lenguajes pueden llamar un mapa hash o una tabla.

new y from_list se pueden utilizar para crear nuevos dicts.

insert y delete se utilizan para agregar y eliminar elementos de un dict.

Al igual que las listas, los dicts son inmutables. Insertar o eliminar un elemento de un dict devolverá un nuevo dict con el elemento agregado o eliminado.

Los dicts no están ordenados. Si parece que los elementos de un dict están en un orden determinado, es incidental y no se debe confiar en ello. Cualquier orden puede cambiar sin previo aviso en futuras versiones o en diferentes entornos de ejecución.

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