martes, 17 de septiembre de 2024

Calculadora de notación polaca inversa en Erlang


La mayoría de las personas han aprendido a escribir expresiones aritméticas con los operadores entre los números ((2 + 2) / 5). Así es como la mayoría de las calculadoras te permiten insertar expresiones matemáticas y probablemente la notación con la que te enseñaron a contar en la escuela. Esta notación tiene la desventaja de que necesitas saber sobre la precedencia de los operadores: la multiplicación y la división son más importantes (tienen una precedencia más alta) que la suma y la resta.

Existe otra notación, llamada notación de prefijo o notación polaca, donde el operador va antes de los operandos. Bajo esta notación, (2 + 2) / 5 se convertiría en (/ (+ 2 2) 5). Si decidimos decir que + y / siempre toman dos argumentos, entonces (/ (+ 2 2) 5) puede escribirse simplemente como / + 2 2 5.

Sin embargo, nos centraremos en la notación polaca inversa (o simplemente RPN), que es lo opuesto a la notación de prefijo: el operador sigue a los operandos. El mismo ejemplo que el anterior en RPN se escribiría 2 2 + 5 /. Otras expresiones de ejemplo podrían ser 9 * 5 + 7 o 10 * 2 * (3 + 4) / 2 que se traducen a 9 5 * 7 + y 10 2 * 3 4 + * 2 /, respectivamente. Esta notación se utilizó mucho en los primeros modelos de calculadoras, ya que ocupaba poca memoria para su uso. 

En primer lugar, puede ser bueno entender cómo leer expresiones RPN. Una forma de hacerlo es encontrar los operadores uno por uno y luego reagruparlos con sus operandos por aridad:

10 4 3 + 2 * -

10 (4 3 +) 2 * -

10 ((4 3 +) 2 *) -

(10 ((4 3 +) 2 *) -)

(10 (7 2 *) -)

(10 14 -)

-4

Sin embargo, en el contexto de una computadora o una calculadora, una forma más sencilla de hacerlo es hacer una pila de todos los operandos tal como los vemos. Tomando la expresión matemática 10 4 3 + 2 * -, el primer operando que vemos es 10. Lo agregamos a la pila. Luego está el 4, así que también lo colocamos en la parte superior de la pila. En tercer lugar, tenemos el 3; coloquemos también ese en la pila. Nuestra pila ahora debería verse así:

Una pila que muestra los valores [3 4 10]

El siguiente carácter a analizar es un +. Esa es una función de aridad 2. Para poder usarla, necesitaremos alimentarla con dos operandos, que se tomarán de la pila.

Entonces tomamos 3 y 4 de la pila, utilizados en la expresión de sufijo '3 4 +' y que devuelve 7 y ponemos este valor en la parte superior de la pila

La pila ahora es [7,10] y lo que queda de la expresión es 2 * -. Podemos tomar el 2 y colocarlo en la parte superior de la pila. Luego vemos *, que necesita dos operandos para funcionar. Nuevamente, los tomamos de la pila. Los operandos 2 y 7 tomados de la pila, utilizados en '7 2 *', que devuelve 14. Y colocamos 14 de nuevo en la parte superior de nuestra pila. Todo lo que queda es -, que también necesita dos operandos. 

Dibuje los operandos 14 y 10 tomados de la pila en la operación '10 14 -' para el resultado '-4'

Y así tenemos nuestro resultado. Este enfoque basado en la pila es relativamente infalible y la poca cantidad de análisis que se necesita hacer antes de comenzar a calcular los resultados explica por qué era una buena idea que las calculadoras antiguas lo usaran.

Escribir esta solución en Erlang no es demasiado difícil una vez que hemos hecho las cosas complejas. Resulta que la parte difícil es averiguar qué pasos se deben realizar para obtener nuestro resultado final y eso es lo que acabamos de hacer. Creemos un archivo llamado calc.erl.

La primera parte de la que preocuparse es cómo vamos a representar una expresión matemática. Para simplificar las cosas, probablemente los ingresaremos como una cadena: "10 4 3 + 2 * -". Esta cadena tiene espacios en blanco, lo cual no es parte de nuestro proceso de resolución de problemas, pero es necesario para usar un tokenizador simple. Lo que sería utilizable entonces es una lista de términos de la forma ["10", "4", "3", "+", "2", "*", "-"] después de pasar por el tokenizador. Resulta que la función string:tokens/2 hace exactamente eso:

> string:tokens("10 4 3 + 2 * -", " ").

["10","4","3","+","2","*","-"]

Esa será una buena representación para nuestra expresión. La siguiente parte a definir es la pila. ¿Cómo vamos a hacer eso? Es posible que hayas notado que las listas de Erlang actúan de manera muy similar a una pila. El uso del operador cons (|) en [Head|Tail] se comporta de manera efectiva de la misma manera que colocar Head en la parte superior de una pila (Tail, en este caso). Usar una lista para una pila será suficiente.

Para leer la expresión, solo tenemos que hacer lo mismo que hicimos cuando resolvimos el problema a mano. Leer cada valor de la expresión, si es un número, colocarlo en la pila. Si es una función, extraer todos los valores que necesita de la pila y luego volver a colocar el resultado. Para generalizar, todo lo que necesitamos hacer es recorrer toda la expresión como un bucle solo una vez y acumular los resultados. ¡Suena como el trabajo perfecto para un fold!

Lo que necesitamos planificar es la función que lists:foldl/3 aplicará en cada operador y operando de la expresión. Esta función, como se ejecutará en un pliegue, necesitará tomar dos argumentos: el primero será el elemento de la expresión con el que se trabajará y el segundo será la pila.

Podemos comenzar a escribir nuestro código en el archivo calc.erl. Escribiremos la función responsable de todos los bucles y también de la eliminación de espacios en la expresión:


-module(calc).

-export([rpn/1]).

 

rpn(L) when is_list(L) -> 

    [Res] = lists:foldl(fun rpn/2, [], string:tokens(L, " ")), 

    Res.


Implementaremos rpn/2 teniendo en cuenta que, dado que cada operador y operando de la expresión termina colocándose en la parte superior de la pila, el resultado de la expresión resuelta estará en esa pila. Necesitamos sacar ese último valor de allí antes de devolvérselo al usuario. Es por eso que hacemos una coincidencia de patrones sobre [Res] y solo devolvemos Res.

Bien, ahora vamos a la parte más difícil. Nuestra función rpn/2 deberá manejar la pila para todos los valores que se le pasen. El encabezado de la función probablemente se verá como rpn(Op,Stack) y su valor de retorno como [NewVal|Stack]. Cuando obtenemos números regulares, la operación será:


rpn(X, Stack) -> [read(X)|Stack].


Aquí, read/1 es una función que convierte una cadena en un valor entero o de punto flotante. Lamentablemente, no hay una función incorporada para hacer esto en Erlang (solo una o la otra). La agregaremos nosotros mismos:


read(N) ->

case string:to_float(N) of

{error,no_float} -> list_to_integer(N);

{F,_} -> F

end.


Donde string:to_float/1 realiza la conversión de una cadena como "13.37" a su equivalente numérico. Sin embargo, si no hay forma de leer un valor de punto flotante, devuelve {error,no_float}. Cuando eso sucede, necesitamos llamar a list_to_integer/1 en su lugar.

Ahora volvamos a rpn/2. Todos los números que encontramos se agregan a la pila. Sin embargo, debido a que nuestro patrón coincide con cualquier cosa (consulte Coincidencia de patrones), los operadores también se colocarán en la pila. Para evitar esto, los colocaremos todos en cláusulas anteriores. La primera con la que intentaremos esto es la suma:


rpn("+", [N1,N2|S]) -> [N2+N1|S];

rpn(X, Stack) -> [read(X)|Stack].


Podemos ver que siempre que encontramos la cadena "+", tomamos dos números de la parte superior de la pila (N1,N2) y los sumamos antes de volver a colocar el resultado en esa pila. Esta es exactamente la misma lógica que aplicamos al resolver el problema a mano. Al probar el programa, podemos ver que funciona:


1> c(calc).

{ok,calc}

2> calc:rpn("3 5 +").

8

3> calc:rpn("7 3 + 5 +").

15

El resto es trivial, ya que solo hay que sumar todos los demás operadores:

rpn("+", [N1,N2|S]) -> [N2+N1|S];
rpn("-", [N1,N2|S]) -> [N2-N1|S];
rpn("*", [N1,N2|S]) -> [N2*N1|S];
rpn("/", [N1,N2|S]) -> [N2/N1|S];
rpn("^", [N1,N2|S]) -> [math:pow(N2,N1)|S];
rpn("ln", [N|S]) -> [math:log(N)|S];
rpn("log10", [N|S]) -> [math:log10(N)|S];
rpn(X, Stack) -> [read(X)|Stack].

Para asegurarnos de que todo esto funcione bien, escribiremos pruebas unitarias muy simples. El operador = de Erlang puede actuar como una función de aserción. Las aserciones deberían fallar siempre que encuentren valores inesperados, que es exactamente lo que necesitamos. Por supuesto, existen marcos de prueba más avanzados para Erlang, incluidos Common Test y EUnit. Los revisaremos más adelante, pero por ahora el = básico hará el trabajo:

rpn_test() ->
    5 = rpn("2 3 +"),
    87 = rpn("90 3 -"),
    -4 = rpn("10 4 3 + 2 * -"),
    -2.0 = rpn("10 4 3 + 2 * - 2 /"),
    ok = try
        rpn("90 34 12 33 55 66 + * - +")
    catch
        error:{badmatch,[_|_]} -> ok
    end,
    4037 = rpn("90 34 12 33 55 66 + * - + -"),
    8.0 =  rpn("2 3 ^"),
    true = math:sqrt(2) == rpn("2 0.5 ^"),
    true = math:log(2.7) == rpn("2.7 ln"),
    true = math:log10(2.7) == rpn("2.7 log10"),
    50 = rpn("10 10 10 20 sum"),
    10.0 = rpn("10 10 10 20 sum 5 /"),
    1000.0 = rpn("10 10 20 0.5 prod"),
    ok.

La función de prueba prueba todas las operaciones; si no se genera ninguna excepción, las pruebas se consideran exitosas. Las primeras cuatro pruebas verifican que las funciones aritméticas básicas funcionen correctamente. La quinta prueba especifica un comportamiento que aún no he explicado. La función try ... catch espera que se genere un error de coincidencia incorrecta porque la expresión no puede funcionar:


90 34 12 33 55 66 + * - +

90 (34 (12 (33 (55 66 +) *) -) +)


Al final de rpn/1, los valores -3947 y 90 se dejan en la pila porque no hay ningún operador que trabaje en el 90 que se queda colgado allí. Hay dos formas posibles de manejar este problema: ignorarlo y solo tomar el valor en la parte superior de la pila (que sería el último resultado calculado) o bloquearse porque la aritmética es incorrecta. Dado que la política de Erlang es dejar que se bloquee, es lo que se eligió aquí. La parte que realmente falla es la [Res] en rpn/1. Esta se asegura de que solo quede un elemento, el resultado, en la pila.

Las pocas pruebas que tienen la forma true = FunctionCall1 == FunctionCall2 están ahí porque no se puede tener una llamada de función en el lado izquierdo de =. Sigue funcionando como una aserción porque comparamos el resultado de la comparación con true.

También he añadido los casos de prueba para los operadores sum y prod para que puedan practicar su implementación. Si todas las pruebas son exitosas, deberían ver lo siguiente:

1> c(calc).
{ok,calc}
2> calc:rpn_test().
ok
3> calc:rpn("1 2 ^ 2 2 ^ 3 2 ^ 4 2 ^ sum 2 -").
28.0

Donde 28 es, de hecho, igual a suma(1² + 2² + 3² + 4²) - 2. Pruebe tantas como desee.

Una cosa que se podría hacer para mejorar nuestra calculadora sería asegurarse de que genere errores de badarith cuando se bloquea debido a operadores desconocidos o valores que quedan en la pila, en lugar de nuestro error de coincidencia incorrecta actual. Sin duda, facilitaría la depuración para el usuario del módulo calc.

domingo, 15 de septiembre de 2024

Use de Gleam


import gleam/io

import gleam/result


pub fn main() {

  let _ = io.debug(without_use())

  let _ = io.debug(with_use())

}


pub fn without_use() {

  result.try(get_username(), fn(username) {

    result.try(get_password(), fn(password) {

      result.map(log_in(username, password), fn(greeting) {

        greeting <> ", " <> username

      })

    })

  })

}


pub fn with_use() {

  use username <- result.try(get_username())

  use password <- result.try(get_password())

  use greeting <- result.map(log_in(username, password))

  greeting <> ", " <> username

}


// Here are some pretend functions for this example:


fn get_username() {

  Ok("alice")

}


fn get_password() {

  Ok("hunter2")

}


fn log_in(_username: String, _password: String) {

  Ok("Welcome")

}


El resultado es : 

Ok("Welcome, alice")
Ok("Welcome, alice")

Gleam carece de excepciones, macros, clases de tipos, retornos anticipados y una variedad de otras características, en lugar de eso, se centra en funciones de primera clase y coincidencia de patrones. Esto hace que el código de Gleam sea más fácil de entender, pero a veces puede resultar en una sangría excesiva.

La expresión use de Gleam ayuda en este caso al permitirnos escribir código que utiliza devoluciones de llamadas en un estilo sin sangría, como se muestra en el código anterior. 

La función de orden superior que se llama va del lado derecho del operador <-. Debe tomar una función de devolución de llamada como argumento final.

Los nombres de los argumentos para la función de devolución de llamada van del lado izquierdo del operador <-. La función puede tomar cualquier cantidad de argumentos, incluido cero.

Todo el código restante en el bloque {} que lo encierra se convierte en el cuerpo de la función de devolución de llamada.

Esta es una característica muy útil y capaz, pero la aplicación excesiva de use puede resultar en un código poco claro, especialmente para principiantes. ¡Por lo general, la sintaxis de llamada de función regular da como resultado un código más accesible!

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.