Translate

martes, 17 de junio de 2025

Otra vez, la Arquitectura Elm

 Se acuerdan de este post, puede que te resulte más fácil ver cómo encajan en el diagrama que vimos en otro post anterior:


Elm comienza mostrando el valor inicial en pantalla. A partir de ahí, entras en este bucle:

  1. Esperar la entrada del usuario.
  2. Enviar un mensaje para actualizar.
  3. Producir un nuevo modelo.
  4. Llamar a la vista para obtener el nuevo HTML.
  5. Mostrar el nuevo HTML en pantalla.
  6. ¡Repetir!

Esta es la esencia de la arquitectura Elm. Cada ejemplo que veamos a partir de ahora será una ligera variación de este patrón básico.


lunes, 16 de junio de 2025

¿Qué es fixed en C#?


La palabra clave fixed en C# se usa en dos contextos, ambos relacionados con el trabajo de bajo nivel y el manejo de memoria no administrada (unsafe code).


Para obtener un puntero fijo a una variable administrada

En C#, los objetos administrados pueden moverse en memoria (por el Garbage Collector). Si queremos trabajar con punteros (como en C o C++), necesitamos asegurarnos de que el objeto no se mueva durante el uso del puntero.

Ahí entra fixed, le indica al runtime que fije el objeto en memoria.


unsafe 

{

    int[] numeros = { 1, 2, 3, 4 };


    fixed (int* p = numeros)

    {

        for (int i = 0; i < 4; i++)

        {

            Console.WriteLine(*(p + i));

        }

    }

}


  • fixed bloquea el arreglo numeros en memoria.
  • int* p es un puntero al primer elemento.
  • Mientras esté dentro del bloque fixed, numeros no se moverá.


Para fijar buffers dentro de structs fixed size buffer

C# permite declarar buffers de tamaño fijo dentro de structs cuando estás trabajando en código unsafe.


unsafe struct MiBuffer

{

    public fixed byte datos[10];

}


  • Aquí datos es un array de 10 bytes directamente dentro del struct.
  • Es muy usado cuando interoperás con código nativo o estructuras de bajo nivel.


Cosas importantes:

  • Solo se puede usar fixed dentro de código marcado como unsafe.
  • Necesitás habilitar la opción /unsafe en el compilador o el flag en el proyecto.
  • Ayuda a interoperar con APIs nativas, como Win32, o trabajar con bloques de memoria directamente.
  • No es algo común en código de negocio habitual en C#, pero sí muy usado en interop, performance crítica o trabajo con hardware.



Arrow Function vs Function en JavaScript


En JavaScript tenemos dos formas principales de declarar funciones:


// Function clásica

function sumar(a, b) {

  return a + b;

}


// Arrow function

const sumar = (a, b) => a + b;


Aunque pueden parecer equivalentes, tienen diferencias importantes:

this léxico:

  • Function clásica: el this depende de cómo se invoque la función.
  • Arrow function: el this se captura del contexto donde fue definida.


const obj = {

  numero: 42,

  funcionClasica: function() {

    console.log(this.numero);  // 42

  },

  arrowFunction: () => {

    console.log(this.numero);  // undefined (o distinto según el contexto externo)

  }

}


Las arrow functions no tienen su propio this.

Las arrow functions no tienen arguments:


function funcionClasica() {

  console.log(arguments);

}


const arrowFunction = () => {

  console.log(arguments); // ReferenceError

}


Si necesitas los argumentos en arrow function, podés usar parámetros rest (...args).


Las arrow functions no pueden ser constructores:


No podés hacer new sobre una arrow function.


function Persona() {}

const PersonaArrow = () => {};


new Persona(); // OK

new PersonaArrow(); // TypeError


Las arrow functions no tienen prototype:


console.log(Persona.prototype); // {}

console.log(PersonaArrow.prototype); // undefined


Las arrow functions tienen una sintaxis más compacta. Ideal para callbacks, funciones de orden superior o programación funcional:


const numeros = [1, 2, 3];

const dobles = numeros.map(n => n * 2);


Hoisting:

  • Las function declarations se elevan (hoisting).
  • Las arrow functions (al ser expresiones) no:


sumar(2,3); // OK

function sumar(a,b){ return a + b; }


sumarArrow(2,3); // TypeError: sumarArrow is not a function

const sumarArrow = (a,b) => a + b;


Mejor en callbacks, no siempre en métodos:


Por su manejo de this, las arrow functions suelen ser preferidas en callbacks, pero no tanto para métodos de objetos o clases.


Regla rápida: usa arrow functions cuando quieras mantener el this del contexto externo (sobre todo en callbacks). Usa functions clásicas cuando necesites un constructor, arguments, o una función completamente autónoma.


domingo, 15 de junio de 2025

¿Por qué C++ necesita punteros para implementar polimorfismo?


En muchos lenguajes de programación orientados a objetos, como Java, C#, Python o Ruby, el polimorfismo funciona de forma "natural" al manejar los objetos por referencia. Sin embargo, cuando trabajamos con C++, rápidamente notamos un detalle que desconcierta a muchos: para obtener polimorfismo dinámico necesitamos punteros (o referencias). ¿Por qué ocurre esto? Vamos a desglosarlo.

El polimorfismo permite invocar métodos sobre objetos de clases derivadas a través de una referencia o puntero a la clase base. El método invocado es el de la clase real del objeto, no necesariamente el de la clase base.

En C++, esto se logra declarando métodos virtual en la clase base:


class Animal {

public:

    virtual void hablar() {

        std::cout << "Soy un animal" << std::endl;

    }

};


class Perro : public Animal {

public:

    void hablar() override {

        std::cout << "Guau" << std::endl;

    }

};


Ahora viene la parte clave.


Animal a = Perro();

a.hablar();  // Imprime: "Soy un animal"


A pesar de haber creado un Perro, el método hablar de Animal es el que se llama. ¿Por qué?

Porque en esta asignación:


Animal a = Perro();


se produce slicing ("corte de objeto"). Solo la parte Animal del Perro es copiada en el objeto a. El comportamiento dinámico queda perdido.


Perro p;

Animal* a = &p;

a->hablar();  // Imprime: "Guau"


Ahora sí, el método correcto se invoca. ¿Qué cambió? Simple: no hay slicing. Estamos trabajando con la dirección de memoria del objeto original, donde toda la jerarquía de clases está intacta.

La razón está en el modelo de objetos de C++:

  • C++ permite que los objetos vivan por valor. Si asignamos Animal a = Perro(), se copia sólo la parte visible desde Animal. Las partes de Perro no existen en esa copia.
  • Los lenguajes como Java o C# manejan los objetos siempre por referencia; nunca se copia el objeto en una asignación como `Animal a = new Perro()`. Por eso ahí el slicing no ocurre.


Cuando usamos punteros o referencias en C++:

  • No se copia el objeto.
  • Se mantiene la dirección al objeto original.
  • El compilador puede usar el vtable (tabla virtual) para despachar la llamada al método correcto.

Una de las ventajas de C++ es que te permite elegir:

  • Si quieres performance máxima (sin polimorfismo), puedes trabajar por valor.
  • Si quieres polimorfismo dinámico, debes trabajar por puntero o referencia.


Este control es una de las razones por las cuales C++ sigue siendo muy usado en sistemas embebidos, motores de juego, drivers, etc.

C++ también permite otras formas de polimorfismo (estático), como el basado en plantillas (CRTP, SFINAE, concepts en C++20), donde no necesitamos punteros porque el binding ocurre en tiempo de compilación.


miércoles, 11 de junio de 2025

Ejemplo de una Aplicación en Elm


Nuestro primer ejemplo es un contador que se puede incrementar o decrementar:


-- Press buttons to increment and decrement a counter.

--

-- Read how it works:

--   https://guide.elm-lang.org/architecture/buttons.html

--



import Browser

import Html exposing (Html, button, div, text)

import Html.Events exposing (onClick)




-- MAIN

main =

  Browser.sandbox { init = init, update = update, view = view }




-- MODEL

type alias Model = Int

init : Model

init =  0




-- UPDATE

type Msg  = Increment  | Decrement


update : Msg -> Model -> Model

update msg model =

  case msg of

    Increment ->  model + 1

    Decrement ->  model - 1


-- VIEW

view : Model -> Html Msg

view model =  div []

    [ button [ onClick Decrement ] [ text "-" ]

    , div [] [ text (String.fromInt model) ]

    , button [ onClick Increment ] [ text "+" ]

    ]


Ahora que has explorado un poco el código, puede que tengas algunas preguntas. ¿Qué hace el valor principal? ¿Cómo se integran las diferentes partes? Analicemos el código y hablemos de ello.

El código utiliza anotaciones de tipo, alias de tipo y tipos personalizados. El objetivo de esta sección es familiarizarse con la arquitectura de Elm, así que no los abordaremos hasta más adelante. 

El main es especial en Elm. Describe lo que se muestra en pantalla. En este caso, inicializaremos nuestra aplicación con el valor init, la función de vista mostrará todo en pantalla y la entrada del usuario se introducirá en la función de actualización. Considérelo la descripción general de nuestro programa.

El modelado de datos es fundamental en Elm. El objetivo del modelo es capturar todos los detalles de la aplicación como datos.

Para crear un contador, necesitamos registrar un número que sube y baja. Esto significa que nuestro modelo es muy pequeño esta vez:


type alias Model = Int


Solo necesitamos un valor entero para registrar el conteo actual. Podemos verlo en nuestro valor inicial:


init : Model

init =  0


El valor inicial es cero y aumentará y disminuirá a medida que las personas presionen diferentes botones.

Tenemos un modelo, pero ¿cómo lo mostramos en pantalla? Esa es la función de vista:


view : Model -> Html Msg

view model =

  div []

    [ button [ onClick Decrement ] [ text "-" ]

    , div [] [ text (String.fromInt model) ]

    , button [ onClick Increment ] [ text "+" ]

    ]


Esta función toma el Modelo como argumento. Genera HTML. Por lo tanto, indicamos que queremos mostrar un botón de decremento, el conteo actual y un botón de incremento.


Observa que tenemos un controlador "onClick" para cada botón. Este indica que, al hacer clic, se genera un mensaje. El botón "+" genera un mensaje de incremento. ¿Qué es y adónde va? ¡A la función de actualización!

La función de actualización describe cómo cambiará nuestro modelo con el tiempo.


Definimos dos mensajes que podría recibir:


type Msg = Increment | Decrement


A partir de ahí, la función de actualización simplemente describe qué hacer cuando recibes uno de estos mensajes.


update : Msg -> Model -> Model

update msg model =

  case msg of

    Increment ->

      model + 1

    Decrement ->

      model - 1


Si recibe un mensaje de Incremento, incrementa el modelo. Si recibe un mensaje de Decremento, lo decrementa.


Así que, cada vez que recibimos un mensaje, lo ejecutamos mediante la función de actualización para obtener un nuevo modelo. Luego, llamamos a la vista para determinar cómo mostrar el nuevo modelo en pantalla. ¡Y luego repetimos! La entrada del usuario genera un mensaje, actualiza el modelo, lo visualiza en pantalla, etc.





martes, 10 de junio de 2025

new vs override en C#: ¿cuál es la diferencia y cuándo usar cada uno?



Cuando trabajamos con herencia en C#, dos palabras clave aparecen para definir comportamiento en clases derivadas: new y override. A primera vista pueden parecer similares, pero representan intenciones diferentes. Entenderlas bien puede evitarte muchos errores.

En C# una clase puede heredar miembros (métodos, propiedades, etc.) de otra. ¿Pero qué pasa si queremos cambiar el comportamiento de un método heredado?

Ahí entran en juego override y new.

Cuando queremos modificar el comportamiento de un método heredado, usámos override.


class Animal

{

    public virtual void Hablar()

    {

        Console.WriteLine("Hace un sonido");

    }

}


class Perro : Animal

{

    public override void Hablar()

    {

        Console.WriteLine("Guau");

    }

}


Y ahora mirá esto:


Animal a = new Perro();

a.Hablar(); // Imprime "Guau"


Con override, el comportamiento depende del tipo real del objeto, no del tipo de la variable. Esto se llama polimorfismo.


En cambio, new oculta el método de la clase base. No lo reemplaza a nivel del sistema de tipos.


class Animal

{

    public void Hablar()

    {

        Console.WriteLine("Hace un sonido");

    }

}


class Gato : Animal

{

    public new void Hablar()

    {

        Console.WriteLine("Miau");

    }

}


Probá esto:

Animal a = new Gato();

a.Hablar(); // Imprime "Hace un sonido"


Porque a es del tipo Animal, y el método Hablar() de Gato no lo reemplazó: solo lo ocultó. Pero:


Gato g = new Gato();

g.Hablar(); // Imprime "Miau"


¿cuándo uso new?

  • Cuando querés ocultar intencionalmente un método de la clase base, pero sin reemplazarlo para el sistema de tipos.
  • Generalmente se evita, porque puede causar confusión. Se usa cuando no podés cambiar la clase base (por ejemplo, una librería externa) pero necesitás un método con el mismo nombre y diferente comportamiento.


¿Qué pasa si no usás ni new ni override? El compilador mostrará una advertencia si detecta que estás ocultando un miembro sin decirlo explícitamente.


// Esto compila, pero da warning:

public void Hablar() { ... } // Oculta un método de la base


Siempre que ocultes un miembro heredado, C# quiere que lo declares con new o override, para que tu intención sea clara.

domingo, 8 de junio de 2025

La Arquitectura Elm

La Arquitectura Elm es un patrón para diseñar programas interactivos, como aplicaciones web y juegos.

Esta arquitectura parece surgir de forma natural en Elm. En lugar de que alguien la inventara, los primeros programadores de Elm seguían descubriendo los mismos patrones básicos en su código. ¡Era un poco inquietante ver a gente terminar con código bien diseñado sin planificación previa!

Así que la Arquitectura Elm es sencilla en Elm, pero útil en cualquier proyecto front-end. De hecho, proyectos como Redux se han inspirado en la Arquitectura Elm, así que quizás ya hayas visto derivados de este patrón. La cuestión es que, incluso si aún no puedes usar Elm en el trabajo, obtendrás mucho de su uso e internalización.

Los programas Elm siempre se ven así:


El programa Elm produce HTML para mostrar en pantalla y luego el ordenador envía mensajes de respuesta sobre lo que está sucediendo. "¡Han pulsado un botón!"

Pero ¿qué sucede dentro del programa Elm? Siempre se divide en tres partes:

  • Modelo: el estado de la aplicación
  • Vista: una forma de convertir el estado a HTML
  • Actualización: una forma de actualizar el estado según los mensajes

Estos tres conceptos son la base de la arquitectura Elm.


jueves, 5 de junio de 2025

Proxy en javascript


En JavaScript moderno hay varias formas de observar cambios en objetos, pero la más directa y nativa es usando Proxy, que te permite interceptar y reaccionar a operaciones sobre un objeto, como modificar una propiedad.


Veamos un ejemplo:


function observeObject(obj, callback) {

  return new Proxy(obj, {

    set(target, prop, value) {

      const oldValue = target[prop];

      target[prop] = value;


      // Ejecutamos el callback cuando se modifica algo

      callback(prop, value, oldValue);


      return true;

    }

  });

}


// Ejemplo de uso

const original = { nombre: 'Juan', edad: 30 };


const observado = observeObject(original, (prop, newVal, oldVal) => {

  console.log(`Propiedad '${prop}' cambió de ${oldVal} a ${newVal}`);

});


observado.nombre = 'Ana';   // Se dispara el callback

observado.edad = 31;        // También se dispara


Me gusto mucho, es simple y puede venir al pelo para ciertas ocaciones, pero debemos de tener en cuenta que : 

  • Solo observa propiedades directas, no anidadas. Si querés observar objetos dentro del objeto, hay que aplicar recursivamente el Proxy.
  • No detecta borrados (como delete obj.prop) a menos que también implementes el deleteProperty trap.
  • No funciona con arrays de forma intuitiva si hacés push, pop, etc., salvo que interceptes esos métodos.


martes, 3 de junio de 2025

Tuplas en Elm


Las tuplas son otra estructura de datos útil. Una tupla puede contener dos o tres valores, y cada valor puede ser de cualquier tipo. Un uso común es cuando se necesita devolver más de un valor de una función. La siguiente función recibe un nombre y envía un mensaje al usuario:

> isGoodName name =

|   if String.length name <= 20 then

|     (True, "name accepted!")

|   else

|     (False, "name was too long; please limit it to 20 characters")

<function>


> isGoodName "Tom"

(True, "name accepted!")



>  

domingo, 1 de junio de 2025

Listas en Elm


Las listas son una de las estructuras de datos más comunes en Elm. Contienen una secuencia de elementos relacionados, de forma similar a los arrays en JavaScript.

Las listas pueden contener muchos valores. Todos estos valores deben ser del mismo tipo. A continuación, se muestran algunos ejemplos que utilizan funciones del módulo Lista:


> names =

|   [ "Alice", "Bob", "Chuck" ]

["Alice","Bob","Chuck"]


> List.isEmpty names

False

> List.length names

3

> List.reverse names

["Chuck","Bob","Alice"]

> numbers =

|   [4,3,2,1]

[4,3,2,1]

> List.sort numbers

[1,2,3,4]

> increment n =

|   n + 1

<function>

> List.map increment numbers

[5,4,3,2]


¡todos los elementos de la lista deben tener el mismo tipo!

  

If en ELM


Para lograr un comportamiento condicional en Elm, se usa una expresión if.

Creemos una nueva función de saludo que sea apropiadamente respetuosa con el presidente Abraham Lincoln:


> greet name =

|   if name == "Abraham Lincoln" then

|     "Greetings Mr. President!"

|   else

|     "Hey!"

<function>

> greet "Tom"

"Hey!"

> greet "Abraham Lincoln"

"Greetings Mr. President!"

  

jueves, 29 de mayo de 2025

Funciones en Elm


Una función transforma valores. Toma un valor y genera otro.

Por ejemplo, aquí hay una función de saludo que toma un nombre y dice hola:


> greet name =

|   "Hello " ++ name ++ "!"

<function>

> greet "Alice"

"Hello Alice!"

> greet "Bob"

"Hello Bob!"


Los valores pasados ​​a la función se llaman comúnmente argumentos, así que podrías decir "saludar es una función que toma un argumento".

Bien, ahora que ya hablamos de saludos, ¿qué tal una función de madlib que toma dos argumentos?


> madlib animal adjective =

|   "The ostentatious " ++ animal ++ " wears " ++ adjective ++ " shorts."

<function>


> madlib "cat" "ergonomic"

"The ostentatious cat wears ergonomic shorts."

> madlib ("butter" ++ "fly") "metallic"

"The ostentatious butterfly wears metallic shorts."


Se debe usar paréntesis para agrupar "butter" ++ "fly". Cada argumento debe ser un valor primitivo como "cat" o debe estar entre paréntesis.

Quienes usan lenguajes como JavaScript podrían sorprenderse de que las funciones se vean diferentes aquí:


madlib "cat" "ergonomic"                  -- Elm

madlib("cat", "ergonomic")                // JavaScript


madlib ("butter" ++ "fly") "metallic"      -- Elm

madlib("butter" + "fly", "metallic")       // JavaScript


Esto puede resultar sorprendente al principio, pero este estilo termina usando menos paréntesis y comas. ¡Hace que el lenguaje se sienta realmente limpio y minimalista una vez que te acostumbras!

lunes, 26 de mayo de 2025

¡Comencemos por familiarizarnos con el código Elm!


El componente más pequeño de Elm se llama valor. Esto incluye valores como 42, Verdadero y "¡Hola!".

Comencemos analizando los números:


> 1 + 1

2

Hacer cálculos matemáticos está bien, pero es sorprendentemente poco común en la mayoría de los programas. Es mucho más común trabajar con cadenas como esta:


> "hola"

"hola"


> "butter" ++ "fly"

"butterfly"

>


¡Estos valores primitivos se vuelven más interesantes cuando empezamos a escribir funciones para transformarlos! Por lo tanto en proximos post veremos funciones. 


domingo, 25 de mayo de 2025

Cómo instalar Elm en Ubuntu


Si te interesa la programación funcional y te atrae la idea de crear interfaces web robustas y sin errores, Elm puede ser una excelente elección. 

Elm es un lenguaje de programación funcional y fuertemente tipado que compila a JavaScript. Se usa principalmente para construir interfaces web confiables, sin errores en tiempo de ejecución.

Vamos a instalarlo!! 

Antes de instalar Elm, asegurate de tener instalado:


Node.js y npm (el gestor de paquetes de Node)


Podés instalarlos con:


sudo apt update

sudo apt install nodejs npm


Verificá la instalación:

node -v

npm -v


Podés instalar Elm de varias formas, pero la más sencilla y recomendada para Ubuntu es a través de npm:


sudo npm install -g elm


Esto instalará los siguientes comandos:

  • elm (CLI principal)
  • elm-repl (intérprete interactivo)
  • elm-reactor (servidor de desarrollo)
  • elm-make (compilador, alias de elm desde Elm 0.19)


Verificá que esté instalado correctamente:


elm --version

Deberías ver algo como: 0.19.1


Podés crear un proyecto de prueba:


mkdir hola-elm

cd hola-elm

elm init


Esto generará un archivo elm.json. Luego, podés crear un archivo src/Main.elm y escribir tu primer módulo Elm. Por ejemplo : 


module Main exposing (main)


import Browser

import Html exposing (text)


main =

    Browser.sandbox

        { init = ()

        , update = \_ model -> model

        , view = \_ -> text "¡Hola Mundo desde Elm!"

        }


Para ver tus archivos Elm directamente en el navegador, ejecutá:


elm reactor


Y abrí http://localhost:8000 para explorar tu proyecto visualmente.


Elm es un lenguaje potente con un ecosistema pequeño pero muy enfocado. Si venís de JavaScript o TypeScript, o simplemente te interesa la programación funcional, Elm te va a sorprender.


jueves, 22 de mayo de 2025

1 Billon de iteraciones de bucles anidados

Me encanto esta comparativa por lo que quería compartirla, básicamente es un benchmark en diferentes lenguajes :


Dejo link

https://benjdd.com/languages/

martes, 20 de mayo de 2025

Conversiones implicit y explicit en C#


En C#, los operadores implicit y explicit nos permiten definir conversiones personalizadas entre tipos. Esto es útil cuando trabajamos con estructuras o clases que representan conceptos que pueden transformarse naturalmente en otros valores, como metros a kilómetros, grados a radianes, o monedas.

Cuando diseñamos tipos personalizados, muchas veces necesitamos que se comporten como si fueran valores comunes. Por ejemplo, si tenemos un struct Metros, queremos poder escribir:


Metros distancia = 100.0;


En lugar de tener que usar un constructor o un método auxiliar. O, si ya tenemos un objeto de tipo Metros, queremos poder convertirlo a double sin esfuerzo.

Sin conversiones definidas, tendríamos que hacer algo como:


var distancia = new Metros(100.0);

double d = distancia.Valor;


Definiendo operadores de conversión, podemos hacer que esta interacción sea más fluida.


El modificador implicit permite definir una conversión automática cuando no hay pérdida de información. El compilador permitirá esta conversión sin necesidad de cast.

Por ejemplo:


public struct Metros

{

    public double Valor { get; }


    public Metros(double valor) => Valor = valor;


    public static implicit operator Metros(double valor) => new Metros(valor);

}


Uso:

Metros distancia = 100.0; // conversión implícita desde double


Cuando una conversión puede perder información, o puede fallar, se recomienda marcarla como explicit. Esto obliga al programador a escribir el cast, dejando claro que está realizando una operación que podría tener consecuencias.

Por ejemplo:


public static explicit operator double(Metros metros) => metros.Valor;


Uso:


double d = (double)distancia; // cast explícito requerido


Las conversiones implicit y explicit en C# permiten diseñar APIs más amigables, legibles y seguras. Usá implicit cuando estés seguro de que la conversión no va a fallar ni perder datos, y explicit cuando quieras que el programador sea consciente del riesgo de la conversión.


domingo, 18 de mayo de 2025

Tipos Abstractos y Polimorfismo en Programación Funcional


Cuando pensamos en abstracción y polimorfismo, solemos imaginar clases, interfaces y herencia. Pero ¿sabías que la programación funcional también tiene sus propios superpoderes para modelar el comportamiento genérico y abstraer detalles? 

En POO, usamos clases abstractas o interfaces para definir estructuras que deben ser implementadas. En programación funcional, el enfoque es distinto, pero el objetivo es similar: ocultar detalles de implementación y exponer un comportamiento general.

Un ADT define un tipo por sus operaciones, no por cómo están implementadas. Por ejemplo:


data Pila a = Vacía | Empujar a (Pila a)


Este tipo Pila podría representar una pila genérica, y podríamos tener funciones que operen sobre ella sin importar cómo esté construida internamente.

En la programación funcional se identifican varios tipos de polimorfismo:

Polimorfismo Paramétrico: Permite escribir funciones genéricas sobre cualquier tipo. Es como los genéricos de Java, pero más poderoso:


identidad :: a -> a

identidad x = x


La función identidad funciona para cualquier tipo a.


Polimorfismo ad-hoc (Typeclasses / Traits / Protocolos) : En Haskell, Rust, Scala o Elixir podemos definir interfaces de comportamiento según el tipo. Esto recuerda al "método virtual" de POO.


class Metrico a where

    distancia :: a -> a -> Double


instance Metrico (Double, Double) where

    distancia (x1, y1) (x2, y2) =

        sqrt ((x2 - x1)^2 + (y2 - y1)^2)


Veamos un ejemplo en Scala:


trait Metrico[T] {

  def distancia(a: T, b: T): Double

}


implicit object Punto2D extends Metrico[(Double, Double)] {

  def distancia(a: (Double, Double), b: (Double, Double)) =

    math.sqrt(math.pow(a._1 - b._1, 2) + math.pow(a._2 - b._2, 2))

}


def calcularDistancia[T](a: T, b: T)(implicit m: Metrico[T]) =

  m.distancia(a, b)


Pattern Matching como Polimorfismo Estructural: Otro recurso poderoso es el pattern matching, que permite seleccionar comportamiento según la "forma" del dato.


sealed trait Forma

case class Circulo(r: Double) extends Forma

case class Rectangulo(ancho: Double, alto: Double) extends Forma


def area(f: Forma): Double = f match {

  case Circulo(r)       => math.Pi * r * r

  case Rectangulo(a, h) => a * h

}


¿Y qué ganamos con esto?

  • Abstracción sin herencia: no hay jerarquías rígidas.
  • Mayor seguridad de tipos: muchos errores se detectan en tiempo de compilación.
  • Separación de datos y comportamiento: las funciones no "viven" dentro de las estructuras de datos, lo cual facilita la composición y el testing.


La programación funcional ofrece mecanismos muy sólidos y expresivos para manejar abstracción y polimorfismo. Aunque no se usa herencia, se logra el mismo efecto (o incluso uno más flexible) usando funciones genéricas, pattern matching y typeclasses.


miércoles, 14 de mayo de 2025

¿Por qué un lenguaje funcional como Elm?



Puedes obtener algunas ventajas de programar con un estilo funcional, pero hay cosas que solo se pueden conseguir con un lenguaje funcional como Elm:

  • Sin errores de ejecución en la práctica.
  • Mensajes de error intuitivos.
  • Refactorización fiable.
  • Control de versiones semántico automático para todos los paquetes de Elm.

Ninguna combinación de bibliotecas JS puede ofrecerte todas estas garantías. ¡Provienen del diseño del propio lenguaje! Y gracias a estas garantías, es bastante común que los programadores de Elm digan que nunca se sintieron tan seguros programando. Seguros para añadir funciones rápidamente. Seguros para refactorizar miles de líneas. ¡Pero sin la ansiedad de pensar que se te ha pasado algo importante!


El Archivo de Anna


Les quiero recomendar el sitio el Archivo de Anna (del inglés Anna's Archive) que es un metabuscador en línea gratuito y sin ánimo de lucro de bibliotecas fantasma que proporciona acceso a una colección de libros, creado por un equipo de archivistas anónimos (conocidos como Anna o el equipo Pirate Library Mirror, abreviado como PiLiMi), ​ y publicado en respuesta directa a los esfuerzos de las fuerzas de seguridad, con la ayuda formal de The Publishers Association y Authors Guild, para cerrar el sitio web de Z-Library en noviembre de 2022.

En este sentido, el equipo del Archivo de Anna afirma proporcionar acceso a los metadatos de los materiales de Open Library, ser una copia de seguridad de las bibliotecas fantasma Library Genesis y Z-Library, presentar información sobre ISBN, no almacenar materiales protegidos por derechos de autor en su sitio web y solo indexar metadatos que ya están disponibles públicamente. Anna's Archive señala que su sitio web, un proyecto sin ánimo de lucro, acepta donaciones para cubrir gastos (alojamiento, nombres de dominio, desarrollo y relacionados).

Dejo link:

https://es.annas-archive.org

sábado, 10 de mayo de 2025

Clases padres, clases hijas… ¿y las madres qué?


La programación orientada a objetos (POO) nos trajo muchas cosas lindas: encapsulación, herencia, polimorfismo, y sobre todo, la posibilidad de inventarnos familias disfuncionales de clases sin necesidad de pasar por terapia.

Pero hay algo que siempre nos hizo ruido:

  • ¿Por qué hablamos de clases padre y clases hijas? 
  • ¿Dónde quedaron las madres, los tíos, las primas, o la abuela que todo lo sabe?

Lo más raro es que una clase padre puede ser hija de otra clase. Rarisisimo...

Todo empieza con el inglés. En POO se habla de parent class para referirse a la clase de la cual heredan otras. Y parent, significa “padre o madre”.

Pero claro, en español, por alguna razón misteriosa que seguro involucra a la Real Academia, siempre traducimos parent class como “clase padre”, y no “clase madre” o “clase progenitor/a” (aunque esa última suena a trámites en ANSES).

Entonces… ¿por qué decimos “clase hija”? Acá la gramática mete la cuchara. Como la palabra “clase” es femenina, cuando hablamos de su descendencia lógica usamos “hija” para que concuerde: La clase padre tiene muchas clases hijas.

¿Y si dijéramos “clase madre”?

¡Podemos! No hay ninguna ley que lo impida. De hecho, si queremos romper esquemas y escribir:


class Mamífero // clase madre

class Perro extends Mamífero // clase hija


...nadie de Scala te va a venir a buscar. Al contrario, tal vez sumes puntos con tus profes de literatura.

Eso sí, el término "clase madre" no es tan común, así que si lo usás, preparate para explicar o educar. (O poner una nota al pie tipo “uso madre porque soy inclusivo/a y rebelde”).

La POO no distingue género, pero el lenguaje humano sí. Y en nuestra necesidad de ponerle nombre a todo, terminamos replicando convenciones culturales sin cuestionarlas.

¿Querés decir clase madre? ¡Decilo!

¿Preferís clase base? ¡También está bien!

Lo importante es que tus clases compilen… y que no traumen a sus hijas.



viernes, 9 de mayo de 2025

Trabajando con partes de colecciones sin copiar: slices, spans y más


Cuando trabajamos con colecciones o buffers de datos, es muy común necesitar operar solo sobre una parte de ellos: una porción de un array, un segmento de texto, o un rango de bytes. La solución más directa suele ser copiar esa parte a una nueva estructura… pero eso introduce sobrecarga innecesaria de memoria y CPU. Si queremos trabajar con partes de una colección (por ejemplo, un array, un string o un buffer de bytes) sin crear copias, es decir, tener una vista o referencia a un fragmento de la colección original.  

Es decir, queremos qu esta solución sea:

  • Ligera en memoria (sin asignaciones adicionales)
  • Segura (sin acceder fuera de los límites)
  • Eficiente (idealmente sin costo en tiempo de ejecución)

Muchos lenguajes modernos han ido incorporando construcciones para resolver este problema. Veamos cómo lo hacen C#, Go y Rust.

A partir de C# 7.2, se introdujo Span<T>, una estructura de tipo ref struct que representa una ventana sobre memoria contigua. 


int[] datos = { 10, 20, 30, 40 };

Span<int> segmento = datos.AsSpan(1, 2); // contiene {20, 30}


Podés usar Span<T> para:

  • Evitar copiar arrays o strings.
  • Trabajar con memoria en el stack (stackalloc).
  • Reutilizar buffers en pipelines o parsers.
  • Procesar archivos grandes en trozos.


Span<byte> buffer = stackalloc byte[1024]; // sin heap


Limitaciones:

  • No puede usarse como campo de clases (sólo en structs).
  • No se puede usar con async/await ni capturar en lambdas.
  • Solo dentro del alcance del stack (por diseño).


Go tiene slices desde siempre: son una capa por encima de los arrays. Un slice guarda un puntero al array subyacente, longitud y capacidad.


arr := [5]int{1, 2, 3, 4, 5}

s := arr[1:4] // contiene {2, 3, 4}


Ventajas:

  • Livianos y eficientes.
  • Se puede modificar el contenido (afecta al array original).
  • Permiten crecer mediante append si hay capacidad.


s[0] = 99 // también cambia arr[1]


La semántica de slicing en Go es natural y permite componer operaciones sin asignar memoria.


Rust maneja este problema con referencias segmentadas: &[T] para vistas inmutables y &mut [T] para mutables.


let arr = [1, 2, 3, 4];

let slice = &arr[1..3]; // &[2, 3]


Características:

  • Completamente seguras en tiempo de compilación.
  • El borrow checker impide aliasing mutable.
  • Altamente eficientes, sin sobrecarga.
  • Son la base de muchas APIs estándar.


fn print_slice(s: &[i32]) {

    for val in s {

        println!("{}", val);

    }

}


¿Y cuál conviene?

  • Si estás en un lenguaje GC-friendly como C#, Span<T> te da poder sin pagar costo de GC.
  • Si buscás simplicidad y velocidad de desarrollo, Go es imbatible con su slicing natural.
  • Si necesitás seguridad al máximo y performance nativa, Rust con slices es lo más robusto.

Es decir, depende del lenguaje que estes usando ... 

Y Otros lenguajes tambien tenemos cosas parecidas: 

  • C++20: std::span<T> cumple un rol casi idéntico a Span<T> de C#, y también es zero-copy.
  • Python: memoryview permite trabajar con buffers sin copiar, aunque menos seguro.
  • Java: No tiene slices como tal, pero ByteBuffer puede simularlos.
  • Nim, Zig, D: Todos ofrecen slices como vistas eficientes sobre datos.


En la práctica, estas estructuras son fundamentales para escribir código eficiente, especialmente en procesamiento de datos, parsers, sistemas embebidos o cualquier aplicación donde el rendimiento importa.  


jueves, 8 de mayo de 2025

Beans Singleton en Spring: ¿Son un riesgo en entornos concurrentes?


Cuando trabajamos con Spring Framework, una de las primeras cosas que aprendemos es que los beans por defecto son singleton. Es decir, Spring crea una única instancia de cada bean y la reutiliza a lo largo de toda la aplicación. Ojo aca, no es igual que el patron singleton, porque el patron singleton es una instancia por clase. Spring realiza una instancia por bean, pero varios beans pueden ser de la misma clase. 

Pero de igual manera nos podemos preguntar ¿No podría causar problemas si múltiples hilos usan el mismo objeto?

Veamos por qué no, y en qué casos sí.

Cuando marcamos una clase con @Component, @Service, @Repository, etc., sin indicar un @Scope, Spring crea una única instancia de esa clase cuando arranca el contexto, y luego la reutiliza para inyectarla donde haga falta.


@Service

public class MiServicio {

    public void hacerAlgo() {

        // lógica

    }

}

Este bean se compartirá entre todas las partes de la aplicación que lo necesiten.

¿Y si lo usan varios hilos?

Acá es donde entra el concepto de thread safety. Si el bean no guarda estado mutable (es stateless), no hay ningún problema. Puede ser accedido por múltiples hilos al mismo tiempo sin consecuencias.

Pero si el bean mantiene estado mutable (por ejemplo, una variable de instancia que se modifica en cada método), entonces puede haber condiciones de carrera, errores y comportamiento impredecible.

Veamos un ejemplo peligroso:


@Component

public class ContadorCompartido {

    private int contador = 0;


    public void incrementar() {

        contador++;

    }


    public int getContador() {

        return contador;

    }

}


Este bean no es thread-safe: si lo usan múltiples hilos al mismo tiempo, el contador puede tener resultados inesperados.

Otra pregunta puede ser ¿Spring crea múltiples instancias si hay mucha carga?

No. Este es un mito común. Spring no crea múltiples instancias de un bean singleton automáticamente bajo carga. La única forma en que podrías tener más de una instancia es:

  • Si usamos un @Scope("prototype"), @RequestScope, etc.
  • Si instanciás manualmente objetos sin pasar por el contenedor de Spring (lo cual rompe la inyección de dependencias).
  • O si usás proxies o mecanismos especiales para aislar contexto por request, como ocurre con algunos beans en entornos web.


Podemos concluir que : 

  • Si tu bean necesita guardar estado por usuario o por request, no lo hagas singleton. Usá un scope adecuado.
  • Si tu bean puede ser stateless, mantenelo así. Es más simple, más eficiente y seguro en entornos multihilo.
  • Si el bean debe tener estado compartido, usá sincronización o mecanismos thread-safe como AtomicInteger, ConcurrentHashMap, etc.

En Spring, los beans singleton son una gran herramienta para reutilización de lógica y eficiencia de memoria. Pero es tu responsabilidad asegurarte de que no mantengan estado mutable que pueda generar conflictos en concurrencia.

martes, 6 de mayo de 2025

Introducción a Elm: Programación Funcional para el Frontend


Elm es un lenguaje de programación funcional, tipado estáticamente, diseñado específicamente para construir interfaces web robustas y sin errores. Es conocido por su simplicidad, rendimiento y su sistema de tipos que prácticamente elimina los errores en tiempo de ejecución.

Elm es un lenguaje compilado que genera JavaScript. Fue creado por Evan Czaplicki y se enfoca en facilitar la construcción de aplicaciones web escalables y mantenibles. Entre sus características más destacadas están:

  • Inmutabilidad por defecto
  • Sistema de tipos fuerte y sin null
  • Compilador amigable
  • Arquitectura unificada (Elm Architecture)


¿Por qué usar Elm?

  • Cero excepciones en tiempo de ejecución: el sistema de tipos atrapa muchos errores antes de que tu aplicación se ejecute.
  • Aplicaciones web rápidas: el código generado es optimizado.
  • Mantenimiento más simple: ideal para proyectos grandes gracias a su claridad y estructura.
  • El compilador te guía: es uno de los mejores errores de compilación que verás.


Elm organiza las aplicaciones con un patrón simple basado en tres conceptos:

  1. Model: el estado de la aplicación
  2. Update: cómo cambia el estado
  3. View: cómo se representa visualmente el estado

Este patrón ha influido incluso en bibliotecas como Redux en JavaScript.


Veamos un poco de código: 


module Main exposing (..)


import Browser

import Html exposing (Html, button, div, text)

import Html.Events exposing (onClick)


-- Modelo

type alias Model = Int


-- Mensajes

type Msg = Increment | Decrement


-- Estado inicial

init : Model

init = 0


-- Actualización del modelo

update : Msg -> Model -> Model

update msg model =

    case msg of

        Increment -> model + 1

        Decrement -> model - 1


-- Vista

view : Model -> Html Msg

view model =

    div []

        [ button [ onClick Decrement ] [ text "-" ]

        , div [] [ text (String.fromInt model) ]

        , button [ onClick Increment ] [ text "+" ]

        ]


-- Programa principal

main =

    Browser.sandbox { init = init, update = update, view = view }


Este ejemplo muestra un contador que incrementa y decrementa con botones, ¡todo en unas pocas líneas claras!


Herramientas y Ecosistema:

  • elm repl: consola interactiva
  • elm make: compila Elm a JS
  • elm install: para instalar paquetes
  • elm-format: para mantener código limpio y uniforme


Elm es una opción excelente para quienes quieren construir interfaces web seguras, claras y libres de errores. Su enfoque funcional y su arquitectura consistente lo convierten en un lenguaje ideal para proyectos de frontend con alta demanda de calidad.

lunes, 5 de mayo de 2025

Span en C#: Acceso seguro y eficiente a la memoria


A partir de C# 7.2 y .NET Core 2.1, Microsoft introdujo una de las herramientas más poderosas para manipular memoria sin sacrificar seguridad: Span<T>. Esta estructura permite trabajar con porciones contiguas de memoria (arrays, segmentos de strings, buffers, etc.) de forma eficiente, sin generar asignaciones en el heap ni usar punteros directamente.

Span<T> es una estructura stack-only que representa una ventana mutable sobre un bloque contiguo de memoria. Podés usarlo para acceder, cortar o modificar datos de arrays, slices de strings, buffers nativos, y más, sin necesidad de copiar datos.

Veamos un ejemplo: 


int[] numbers = { 1, 2, 3, 4, 5 };

Span<int> slice = numbers.AsSpan(1, 3); // Contiene 2, 3, 4


slice[0] = 42;

Console.WriteLine(numbers[1]); // Muestra 42 (modificó el array original)


¿Por qué usar Span<T>?

  • Evita copias de memoria innecesarias
  • No genera asignaciones en el heap 
  • Mejora la performance en procesamiento de strings, buffers y arrays  
  • Ofrece seguridad de tipos y bounds-checking
  • Solo puede usarse dentro del stack (no puede almacenarse en campos de clase)


Limitaciones:

  • No puede usarse en métodos async o iteradores (async, yield return)  
  • No puede almacenarse en campos de clase o como parte de objetos del heap
  • Si necesitás algo similar pero heap-safe, podés usar Memory<T>


Veamos un ejemplo de string: 


ReadOnlySpan<char> span = "Hola Mundo".AsSpan(5);

Console.WriteLine(span.ToString()); // Mundo


Esto es ideal para parsear strings sin crear substrings intermedias.

Span<T> es una herramienta fundamental si querés escribir código de alto rendimiento en .NET. Es ideal para manipular datos binarios, strings o buffers, con el mínimo impacto en el garbage collector. Aunque tiene limitaciones (no se puede escapar del stack), su potencia compensa con creces en escenarios críticos de performance.

Simular punteros en Javascript


JavaScript no tiene punteros como en C o C++, pero sí se puede simular su comportamiento usando objetos y referencias. En JavaScript, los objetos se pasan por referencia, lo que significa que si modificas una propiedad del objeto dentro de una función, el cambio se refleja afuera.

Veamos un ejemplo:


function incrementar(valor) {

    valor.numero++;

}


let puntero = { numero: 10 };

incrementar(puntero);

console.log(puntero.numero); // 11


Aquí puntero simula un puntero: su campo numero puede ser modificado por funciones.

Se pasa la referencia al objeto, no una copia.


Otra forma es con arreglos para simular puntero a variables primitivas


function setValor(arr, nuevoValor) {

    arr[0] = nuevoValor;

}


let x = [5];

setValor(x, 42);

console.log(x[0]); // 42


x es un arreglo de un solo elemento. Se comporta como una caja que puede modificarse dentro de funciones.

Esto puede verse como una simulación de un int* en C++.

Veamos un ejemplo sin utilizar funciones: 


let a = { valor: 10 };

let b = a; // b apunta al mismo objeto que a


b.valor = 99;


console.log(a.valor); // 99

console.log(b.valor); // 99


a y b apuntan al mismo objeto.

Cambiar b.valor también afecta a.valor.

También se puede utilizar arreglos:


let x = [42];

let y = x;


y[0] = 100;


console.log(x[0]); // 100

console.log(y[0]); // 100


En JavaScript, los valores primitivos (números, strings, booleanos, etc.) se pasan por valor, pero los objetos y arrays se pasan por referencia y podemos utilizarlos para simular punteros.


sábado, 3 de mayo de 2025

¿Qué significa Nullable en el archivo .csproj de C#?


Desde C# 8, el lenguaje introdujo el análisis de nulabilidad (nullable reference types), una herramienta poderosa para ayudarte a detectar posibles null en tiempo de compilación. Esta funcionalidad se activa o desactiva con la propiedad <Nullable> en el archivo .csproj.

Cuando está activado, el compilador trata los tipos de referencia como no anulables por defecto, a menos que explícitamente los marques con ?.

Veamos un ejemplo: 


<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>

    <TargetFramework>net8.0</TargetFramework>

    <Nullable>enable</Nullable>

  </PropertyGroup>

</Project>


Y en el projecto: 


string name = null; // Warning: posible asignación nula

string? nickname = null; // Permitido


con Nullable deshabilitado:


string name = null; // No hay advertencias, aunque puede fallar en runtime


Activar Nullable te permite:

  • Recibir advertencias si estás usando variables que podrían ser null.
  • Hacer que tus APIs sean más expresivas (string? vs string).
  • Reducir errores en tiempo de ejecución por NullReferenceException.
  • Trabajar mejor con herramientas de análisis estático.


Usar <Nullable>enable</Nullable> en tus proyectos de C# modernos es una excelente práctica. Aumenta la robustez del código y te da más control sobre los errores relacionados con null, una de las fuentes más comunes de fallos en producción.

viernes, 2 de mayo de 2025

unsafe acceso directo a la memoria en C#


C# es un lenguaje seguro por defecto, lo que significa que evita errores comunes como accesos ilegales a memoria. Sin embargo, hay ocasiones en las que necesitamos un control más bajo nivel, como en interoperabilidad con código nativo o para optimizaciones de rendimiento. Para eso existe la palabra clave unsafe

La palabra clave unsafe en C# habilita el uso de punteros, acceso directo a memoria y operaciones que normalmente están fuera del alcance del entorno de ejecución administrado del .NET runtime. En otras palabras, permite usar características similares a C/C++, con sus pros y contras.

Para usarlo, debés marcar bloques, métodos o estructuras con la palabra clave unsafe. También tenés que habilitarlo en el proyecto.

En el `.csproj`:


<PropertyGroup>

  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

</PropertyGroup>


Veamos un ejemplo: 


unsafe

{

    int valor = 42;

    int* puntero = &valor;


    Console.WriteLine(*puntero); // Imprime 42

}


Cuando queremos apuntar a arrays o strings, que son gestionados por el Garbage Collector, necesitamos usar fixed para evitar que el objeto se mueva en memoria:


unsafe

{

    int[] numeros = { 10, 20, 30 };

    fixed (int* p = numeros)

    {

        Console.WriteLine(p[1]); // Imprime 20

    }

}



Que podemos hacer con unsafe?

  • Declaración y uso de punteros (`*`, `&`)
  • Indexación de punteros (`p[i]`)
  • Aritmética de punteros (`p++`, `p + 1`, etc.)
  • Conversión entre tipos de punteros
  • Uso de `sizeof(T)` para tipos primitivos


Pero ¿Cuándo usamos unsafe?

  • Interop con librerías nativas (C, C++, DLLs).
  • Manipulación avanzada de memoria.
  • Procesamiento de imágenes o buffers donde la performance es crítica.
  • Serialización binaria de alto rendimiento.

Como se pueden imaginar esto es tan util como peligroso, que es lo que puede salir mal?

  • Podemos introducir errores difíciles de depurar (como corrupción de memoria).
  • Desactiva algunas protecciones del CLR.
  • El código unsafe no se ejecuta en entornos con restricciones de seguridad (como ciertos sandboxes).
  • No es portable entre arquitecturas de forma garantizada.


Dentro de unsafe, podemos usar sizeof` para obtener el tamaño de tipos primitivos:


unsafe

{

    Console.WriteLine(sizeof(int));   // 4

    Console.WriteLine(sizeof(byte));  // 1

}


También podés usarlo con nint, nuint, float, double, etc.

Si queremos evitar unsafe pero aún así trabajar con memoria de forma eficiente:



El modo unsafe en C# te da acceso a un poder inmenso, pero con gran responsabilidad. Es una herramienta útil para situaciones específicas donde el rendimiento o la interoperabilidad lo justifican, pero debe usarse con precaución y conocimiento.


¿Qué es ImplicitUsings en C# y por qué es útil?


Con la llegada de .NET 6 y la idea de simplificar el código, Microsoft introdujo una nueva funcionalidad que puede ahorrarte varias líneas repetitivas en los archivos .cs: los usings implícitos, habilitados con la propiedad <ImplicitUsings>.

Cuando activás esta propiedad en el archivo .csproj, el compilador de C# agrega automáticamente un conjunto de using comunes a todos los archivos del proyecto, sin que tengas que escribirlos vos mismo.

Por ejemplo: 

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>

    <TargetFramework>net8.0</TargetFramework>

    <ImplicitUsings>enable</ImplicitUsings>

  </PropertyGroup>

</Project>


Gracias a esto, podés usar tipos como List<string>, Task, HttpClient, etc., directamente, sin declarar explícitamente:


using System.Collections.Generic;

using System.Threading.Tasks;

using System.Net.Http;


¿Qué usings incluye? Depende del tipo de proyecto (console, ASP.NET, etc.), pero algunos de los más comunes son:

  • System
  • System.Collections.Generic
  • System.IO
  • System.Linq
  • System.Net.Http
  • System.Threading.Tasks
  • Y muchos más según el SDK usado


Si querés ver la lista exacta, podés mirar el archivo generado: obj/YourProject.GlobalUsings.g.cs

<ImplicitUsings> es una pequeña gran herramienta que simplifica la escritura de código en C#, especialmente útil en proyectos nuevos. Al eliminar la necesidad de repetir los mismos using una y otra vez, te permite concentrarte en lo que realmente importa: la lógica de tu aplicación.

jueves, 1 de mayo de 2025

Clases, Métodos, Propiedades e Indexadores Parciales en C#


En C#, el modificador partial no se limita solo a clases. También puede aplicarse a métodos, propiedades y hasta indexadores.  

Esta característica permite dividir y extender la definición de estos elementos, haciendo que el código sea más limpio, modular y mantenible.

Una clase parcial es aquella que puede ser dividida en múltiples archivos.  

Cada fragmento de la clase debe usar el modificador partial.

Veamos un ejemplo:


// Archivo Persona_Datos.cs

public partial class Persona

{

    public string Nombre { get; set; }

}


// Archivo Persona_Operaciones.cs

public partial class Persona

{

    public string ObtenerNombreCompleto() => Nombre;

}


Al compilar, el compilador junta todas las partes como si estuvieran en un único archivo.

Un método parcial (partial void) permite declarar un método que puede (o no) ser implementado en otra parte de la clase.  

Si no se implementa, el compilador simplemente lo ignora, como si nunca hubiera existido.

Vamos con el ejemplo: 


// Declaración

public partial class Persona

{

    partial void ValidarNombre();

}


// Implementación opcional

public partial class Persona

{

    partial void ValidarNombre()

    {

        if (string.IsNullOrEmpty(Nombre))

        {

            Console.WriteLine("Nombre inválido");

        }

    }

}


  • Los métodos parciales siempre deben ser void.
  • No pueden tener modificadores de acceso (`public`, `private`, etc.).
  • No pueden ser virtuales ni static ni async.


Propiedades Parciales (desde C# 12)


Desde C# 12, se pueden crear propiedades parciales, permitiendo que la lógica de getters y setters sea definida en distintas partes del código.


public partial class Persona

{

    public partial string Nombre { get; set; }

}


// En otro archivo:

public partial class Persona

{

    public partial string Nombre

    {

        get => _nombre;

        set => _nombre = value.Trim();

    }


    private string _nombre;

}


Con esta característica se puede separar la definición de una propiedad de su comportamiento, facilitando la generación automática de propiedades o su personalización.

Un indexador parcial permite dividir la definición de un indexador (this[...]) en varias partes.

Por ejemplo:


public partial class MiColeccion

{

    public partial string this[int index] { get; set; }

}


// En otro archivo:

public partial class MiColeccion

{

    private string[] _datos = new string[10];


    public partial string this[int index]

    {

        get => _datos[index];

        set => _datos[index] = value;

    }

}

Partial es muy importante porque: 

  • Separar código generado automáticamente del código personalizado.
  • Mejorar la legibilidad y mantenimiento en proyectos grandes.
  • Permitir que distintos desarrolladores trabajen simultáneamente en distintas partes de una clase.
  • Facilitar extensiones futuras sin modificar directamente el código base.

El soporte de C# para clases, métodos, propiedades e indexadores parciales te da un gran poder para modularizar tu código de forma clara y ordenada.  

Esta es una característica que, bien aprovechada, puede hacer una gran diferencia en proyectos de cualquier tamaño.

martes, 29 de abril de 2025

Parámetros por Referencia en C#: ref, out, in y Punteros (unsafe)


Cuando programás en C#, tenés varias formas de pasar argumentos a un método: por valor (el comportamiento predeterminado) o por referencia. Esta última opción es muy útil cuando necesitás que el método pueda modificar directamente la variable original, sin copiarla.

En este post te cuento las distintas formas de hacerlo: ref, out, in, y punteros (unsafe). Además, te muestro cómo declararlos, cuándo conviene usarlos, y qué diferencias tienen entre sí.

ref: Lectura y escritura

Con ref, el método puede leer y modificar la variable pasada. Pero el valor debe estar inicializado antes de pasarla.

void SumarUno(ref int numero)
{
    numero += 1;
}

int x = 5;
SumarUno(ref x);  // x ahora vale 6


out: Solo escritura

Con out, el método debe asignar un valor. La variable no necesita estar inicializada previamente.

void ObtenerDoble(out int resultado)
{
    resultado = 84;
}

int r;
ObtenerDoble(out r);  // r ahora vale 84

Ideal cuando querés devolver más de un valor desde un método.


in: Solo lectura

Con in, pasás una variable por referencia, pero solo para lectura. Sirve especialmente con structs grandes, para evitar la copia de memoria.


void Mostrar(in int numero)
{
    Console.WriteLine(numero);
}

int y = 10;
Mostrar(in y);  // y no se puede modificar dentro del método


Punteros (unsafe): Bajo nivel

Si necesitás controlar la memoria directamente, podés usar punteros en código unsafe.

unsafe void Incrementar(int* ptr)
{
    *ptr += 1;
}

unsafe
{
    int z = 7;
    Incrementar(&z);  // z ahora vale 8
}


Declaración rápida

// ref
void Modificar(ref int x)

// out
void Inicializar(out int x)

// in
void SoloLeer(in int x)

// unsafe pointer
unsafe void Modificar(int* x)


Comparativa rápida

Keyword ¿Inicialización previa? ¿Se puede leer? ¿Se puede escribir? Uso típico
ref Modificar valor
out Devolver datos
in Structs grandes
* Bajo nivel


Ejemplos útiles

// struct con ref
struct Punto { public int X, Y; }

void Mover(ref Punto p) => p.X += 10;

// out con TryParse
bool TryParseNumero(string s, out int resultado)
{
    return int.TryParse(s, out resultado);
}

// in con cálculo
void MostrarDistancia(in Punto p)
{
    Console.WriteLine($"Distancia: {Math.Sqrt(p.X * p.X + p.Y * p.Y)}");
}

// unsafe con array
unsafe void Duplicar(int* arr, int n)
{
    for (int i = 0; i < n; i++) arr[i] *= 2;
}


C# te da muchas formas de pasar datos, y conocer ref, out, in y los punteros te permite optimizar tu código, mejorar la performance, y diseñar APIs más expresivas. Cada palabra clave tiene su contexto ideal, y entenderlas bien te va a dar más poder sobre lo que tu código realmente hace.


lunes, 28 de abril de 2025

El Poder del underscore (_) en Scala


Scala es un lenguaje conciso y expresivo, y una de sus herramientas más versátiles es el underscore (_). Aunque parece un simple guion bajo, su significado depende del contexto, y puede representar una variable anónima, un tipo genérico, una función parcial, o incluso un importador wildcard. Vamos a repasar sus usos principales con ejemplos simples.

Cuando usás una función que espera un argumento, podés usar _ para decir “acá va ese argumento”.

val nums = List(1, 2, 3)

val dobles = nums.map(_ * 2)  // equivale a nums.map(x => x * 2)

Esto es súper útil para evitar código repetitivo.

Se puede utilizar para ignorar parámetros no usados

Cuando definís una función pero no te interesa usar todos los parámetros:


val funcion = (_: Int) => 42  // ignora el valor que recibe y siempre retorna 42


También se usa para desestructuración parcial:

val (a, _) = (1, 2)  // Ignora el segundo valor


Importaciones tipo wildcard. Igual que en Java con *, pero en Scala se usa _:


import scala.collection.mutable._  // importa todas las clases de mutable


Referencia a métodos como funciones. Cuando pasás un método como función, usas _ para convertirlo a función de orden superior:


def cuadrado(x: Int): Int = x * x

val lista = List(1, 2, 3).map(cuadrado)     // OK

val lista2 = List(1, 2, 3).map(cuadrado _)  // También válido, por conversión explícita


Inicialización por defecto en clases o valores:

var x: String = _  // valor por defecto: null

var y: Int = _     // valor por defecto: 0


Esto es más común en Java-style code o interoperabilidad con frameworks como Spark o Akka.


Tipos genéricos anónimos, como vimos antes:

val lista: List[_] = List("a", 1, true)  // lista de algún tipo desconocido


También podés usar _ <: Animal o _ >: Perro para acotar subtipos o supertypos.

Podés dejar argumentos sin aplicar en una llamada y usar _ para marcar que falta ese valor:


def multiplicar(a: Int, b: Int): Int = a * b

val porDos = multiplicar(2, _: Int)  // función que multiplica por 2

println(porDos(5))  // 10


Dominar el uso del _ te permite escribir código Scala más idiomático y elegante.




sábado, 26 de abril de 2025

Nuevas características de C# 13


C# 13, junto con .NET 9, trae una serie de mejoras que buscan simplificar la escritura de código, mejorar el rendimiento y ofrecer mayor flexibilidad a los desarrolladores. Veamos que trajo:

Colecciones params mejoradas

Ahora, el modificador params se puede aplicar a tipos que admiten inicialización mediante expresiones de colección, no solo a arreglos. Esto permite una mayor flexibilidad al definir métodos que aceptan un número variable de argumentos.


public void EscribirNombres(params List<string> nombres)

    => Console.WriteLine(string.Join(", ", nombres));


Nuevo tipo de bloqueo: System.Threading.Lock

Se introduce un nuevo tipo de bloqueo que mejora la sincronización de subprocesos. A diferencia del uso tradicional de lock con objetos, System.Threading.Lock proporciona una API más clara y segura para manejar exclusiones mutuas.


var myLock = new Lock();

using (myLock.EnterScope())

{

    // Código protegido

}


Nueva secuencia de escape: \e

Se añade la secuencia de escape \e para representar el carácter de escape ASCII (U+001B). Anteriormente, se utilizaban \u001b o \x1b, pero esta nueva notación es más clara y evita ambigüedades.


Tipos naturales de grupo de métodos

Se mejora la inferencia de tipos al trabajar con grupos de métodos, permitiendo que el compilador determine de manera más precisa el tipo adecuado en contextos donde se utilizan delegados o expresiones lambda.


Propiedades e indexadores parciales

Al igual que los métodos parciales, ahora es posible declarar propiedades e indexadores parciales. Esto facilita la separación de la declaración y la implementación, especialmente útil al trabajar con generadores de código.


partial class MiClase

{

    public partial int MiPropiedad { get; set; }

}


Acceso implícito a indexadores en inicializadores de objetos

Se permite el uso de índices implícitos, como [^1], dentro de inicializadores de objetos, lo que simplifica la inicialización de colecciones y estructuras de datos complejas.


var miArreglo = new int[5] { [^1] = 10 };


Soporte para ref struct en interfaces y genéricos

Los tipos ref struct ahora pueden implementar interfaces y ser utilizados como argumentos de tipo en genéricos, ampliando su aplicabilidad en escenarios de alto rendimiento y manipulación de memoria.


Variables ref y contextos unsafe en iteradores y métodos asincrónicos

Se habilita el uso de variables ref locales y contextos unsafe dentro de métodos asincrónicos y iteradores, siempre que se cumplan ciertas restricciones para garantizar la seguridad del código.


Atributo OverloadResolutionPriority

Se introduce el atributo OverloadResolutionPriority que permite a los desarrolladores especificar la prioridad de resolución de sobrecargas, facilitando la evolución de las API sin romper compatibilidad con versiones anteriores.

Estas mejoras reflejan el compromiso continuo de la comunidad de C# por evolucionar el lenguaje, haciéndolo más potente y expresivo. 


Dejo link: https://developers.redhat.com/articles/2025/04/16/c-13-new-features#

martes, 22 de abril de 2025

Tipos Genéricos Anónimos en Scala: Wildcards y Subtipado


Antes de empezar este post tal vez sería bueno que leas este post antes :D

Scala permite definir colecciones y estructuras de datos genéricas, pero a veces no necesitamos saber con precisión qué tipo contienen, o simplemente queremos permitir varios tipos relacionados. Para esos casos existen los tipos genéricos anónimos, representados por el underscore (_).

Un tipo genérico anónimo en Scala se escribe con un guion bajo (_) en lugar de especificar un tipo concreto. Es útil cuando:

  • No necesitás conocer el tipo exacto.
  • Queremos aceptar varios subtipos o supertypos.

Veamos un ejemplo:

Lista de cualquier tipo (wildcard total)


val lista: List[_] = List(1, "hola", true)


Esto indica que lista es de algún tipo List[T], pero no importa cuál es T.


Subtipado: _ <: Tipo

Permite aceptar cualquier subtipo del tipo dado (covarianza).


class Animal

class Perro extends Animal

class Gato extends Animal


val animales: List[_ <: Animal] = List(new Perro, new Gato)


Esto significa: una lista de algo que es subtipo de Animal.


Supertyping: _ >: Tipo

Permite aceptar cualquier supertipo del tipo dado (contravarianza).


val cosas: List[_ >: Perro] = List(new Animal, new Perro)


Esto significa: una lista de algo que es supertipo de Perro.


Y ¿Por qué usar genéricos anónimos?

  • Cuando escribís funciones genéricas que pueden aceptar muchos tipos.
  • Para asegurar compatibilidad con estructuras covariantes/contravariantes.
  • Para restringir o abrir el tipo de manera controlada.

Los tipos anónimos no te permiten hacer mucho con los elementos (no podés acceder a sus métodos específicos), porque Scala no sabe exactamente qué tipo hay.


val lista: List[_] = List("hola", "chau")

// lista(0).toUpperCase()  // ERROR: no se puede garantizar el tipo


En Java existe el signo de pregunta para esto (?):


List<?> lista;

List<? extends Animal> subtipos;

List<? super Perro> supertypos;


En Scala es más limpio y expresivo:


List[_]

List[_ <: Animal]

List[_ >: Perro]


Los tipos genéricos anónimos en Scala te permiten trabajar con estructuras de datos más genéricas y flexibles, especialmente en APIs o librerías donde no se necesita o no se conoce el tipo exacto. Son ideales para mantener la seguridad de tipos sin perder generalidad.