Translate

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.


Tipos de Herencia en C++


En C++, cuando una clase hereda de otra, podemos especificar cómo se deben heredar los miembros públicos y protegidos de la clase base. Esto se hace mediante el tipo de herencia, que puede ser public, protected o private.

public: Herencia Pública


class Animal {

public:

    void respirar() {}

};


class Perro : public Animal {

    // "respirar" sigue siendo público

};



Es la más común para relaciones del tipo es-un (ej: un perro es un animal). Y queda todo como el padre, los metodos publicos y protejidos siguen publicos y protejidos en el hijo. 


protected: Herencia Protegida


class Perro : protected Animal {

    // "respirar" pasa a ser protegido

};

Esta forma rara vez se usa, pero puede servir para limitar el acceso desde fuera sin perder herencia funcional. Los metodos publicos y protejidos pasan a protejidos en el hijo. 


private: Herencia Privada


class Perro : private Animal {

    // "respirar" pasa a ser privado

};


Es útil cuando querés usar la funcionalidad de la clase base sin exponer su interfaz públicamente. Los metodos publicos y protejidos pasan a privados en el hijo. 


Y si no pongo nada ¿Cuál es el valor por defecto?

El valor por defecto en las clases es private y en los structs public. 


domingo, 20 de abril de 2025

Genéricos en Scala: Covarianza y Contravarianza


Scala es un lenguaje poderoso que combina programación funcional y orientada a objetos. Uno de sus conceptos más sofisticados es el sistema de tipos paramétricos, o genéricos, que permiten escribir código flexible y seguro. 

Los genéricos permiten parametrizar clases, traits y métodos con tipos. Por ejemplo:


class Caja[T](valor: T) {

  def get: T = valor

}


Esta clase puede contener un Int, un String, o cualquier otro tipo.

Imaginemos que tenemos las siguientes clases:


class Animal

class Perro extends Animal


Ahora, supongamos que existe una clase genérica Caja[T]. ¿Debería Caja[Perro] ser un subtipo de Caja[Animal]?


En Scala, la relación de subtipos entre tipos genéricos no se asume automáticamente. Vos decidís explícitamente cómo se comporta con respecto a la varianza.

Scala permite controlar la varianza de un tipo genérico con anotaciones en la declaración del tipo.


Invarianza (sin anotación):

class Caja[T](valor: T)

No hay relación de subtipos entre Caja[Perro] y Caja[Animal].


Covarianza (+T)

class Caja[+T](valor: T)

Caja[Perro] es subtipo de Caja[Animal] si Perro es subtipo de Animal.

Usá covarianza cuando vas a leer datos del tipo genérico, pero no escribir.


Ejemplo:

class ListaCovariante[+A](val cabeza: A)


No podés definir un método como:

def setCabeza(a: A): Unit // ERROR con +A


Porque podría romper la seguridad de tipos.


Contravarianza (-T)


class Procesador[-T] {

  def procesar(t: T): Unit = println("Procesando")

}


Procesador[Animal] es subtipo de Procesador[Perro].

Esto es útil cuando recibís datos del tipo genérico (por ejemplo, funciones o procesadores).

Veamos un ejemplo:


class Animal

class Perro extends Animal

class Gato extends Animal


// Covariante: productor

class CajaProductora[+A](val valor: A)


// Contravariante: consumidor

class Procesador[-A] {

  def procesar(a: A): Unit = println(s"Procesando: $a")

}


Uso:


val perro = new Perro

val gato = new Gato


val caja: CajaProductora[Animal] = new CajaProductora[Perro](perro)

val procesador: Procesador[Perro] = new Procesador[Animal]()


procesador.procesar(perro)


En Scala, los tipos de las funciones son contravariantes en los parámetros y covariantes en el resultado.


val f: Perro => Animal = (a: Animal) => a // válido


¿Por qué? Porque si necesitás una función que acepte Perro, es seguro usar una función que acepte Animal, ya que Animal puede incluir a Perro.


La varianza en Scala te permite expresar con precisión las relaciones de subtipos entre clases genéricas:

  • +T (covariante): útil para leer
  • -T (contravariante): útil para escribir
  • T (invariante): comportamiento por defecto


Comprender este sistema es clave para diseñar APIs robustas, especialmente en programación funcional y colecciones.


jueves, 17 de abril de 2025

Full Stack Open


Le quiero recomendar la página fullstackopen, la cual brinda un curso sobre el desarrollo web moderno. Para no hacerlo tan largo, lo que nos dice esta pagina es lo siguiente: 

¡Aprende React, Redux, Node.js, MongoDB, GraphQL y TypeScript de una sola vez! Este curso te presentará el desarrollo web moderno basado en JavaScript. El enfoque principal es crear aplicaciones de una sola página con ReactJS que usando REST API creadas con Node.js.


Dejo link: https://fullstackopen.com/es/

Covarianza y contravarianza en C++


La covarianza y contravarianza en C++ con templates es un poco más manual que en otros lenguajes como Java o C#, porque C++ no tiene soporte directo para la varianza en plantillas. Pero hay formas de simularla o controlarla explícitamente usando herencia, punteros, referencias, std::variant, std::any, SFINAE y concepts.

Primero: ¿Qué es covarianza y contravarianza?

  • Covarianza: cuando un tipo genérico Container<Derived> puede ser tratado como Container<Base>.
  • Contravarianza: lo contrario Container<Base> puede ser usado donde se espera Container<Derived>.
  • Invarianza: no se permite ninguna conversión.


Las Plantillas en C++ son invariantes, esto no es válido:


template <typename T>

class Caja<T> {};


class Animal {};

class Perro : public Animal {};


Caja<Perro>* cp = new Caja<Perro>();

Caja<Animal>* ca = cp;  //  ERROR: no se permite, son tipos distintos


Y listo, fin del post ... Pero pero sí hay covarianza con punteros y referencias a clases (no templates)

Esto funciona:


class Animal {

public:

    virtual void hablar() {}

};


class Perro : public Animal {

public:

    override void hablar() {}

};


Animal* a = new Perro();  // Anda por Covarianza de punteros


¿Cómo resolver o simular varianza con templates?


Usar herencia con punteros o referencias


template <typename T>

class Caja {

public:

    T* valor;

};


Perro* p = new Perro();

Caja<Perro> c1;

c1.valor = p;

Caja<Animal> c2 = c1;  //  No compila directamente

Podemos hacer funciones que trabajen con T* o T& para aprovechar la covarianza de punteros.


Hacer una jerarquía polimórfica manual


class ICajaBase {

public:

    virtual ~ICajaBase() = default;

};


template <typename T>

class Caja : public ICajaBase {

public:

    T valor;

};


Luego podemos trabajar con ICajaBase* y aplicar RTTI o dynamic_cast.


Usar std::variant o std::any para manejar múltiples tipos


#include <variant>

std::variant<Perro, Gato> animal;


Esto evita plantillas covariantes, pero permite flexibilidad en tiempo de ejecución.


Usar std::enable_if, SFINAE o concepts para limitar compatibilidades


template <typename T>

class Caja {

public:

    static_assert(std::is_base_of<Animal, T>::value, "T debe ser un Animal");

};


En C++, las plantillas son invariantes. pero podemos usar covarianza con punteros y referencias a clases base. Si queremos modelar jerarquías con templates, tenemos que hacerlo manualmente usando herencia, punteros, std::variant o SFINAE.