Translate

jueves, 9 de octubre de 2025

Hablemos de ArrayList


La clase ArrayList en Java es una de las colecciones más utilizadas del paquete java.util.

Está implementada internamente sobre un arreglo dinámico, y su código fuente se encuentra en java.util.ArrayList dentro del JDK.

Veamos cómo funciona en detalle:

Internamente, ArrayList mantiene un array (arreglo) de tipo Object[] llamado elementData donde guarda los elementos:


public class ArrayList<E> extends AbstractList<E>

        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {


    private static final int DEFAULT_CAPACITY = 10;


    private static final Object[] EMPTY_ELEMENTDATA = {};


    private transient Object[] elementData;


    private int size;

}


  • elementData: el arreglo donde se almacenan los elementos.
  • size: la cantidad actual de elementos.
  • DEFAULT_CAPACITY: capacidad inicial (10).
  • EMPTY_ELEMENTDATA: usado cuando el ArrayList está vacío.


Cuando creás un ArrayList sin indicar capacidad:


List<String> list = new ArrayList<>();


internamente no se reserva espacio aún. Se crea con elementData = EMPTY_ELEMENTDATA.

Recién al agregar el primer elemento, se inicializa con una capacidad por defecto (10).


Cuando llamás a:


list.add("Hola");


Java hace esto internamente:


public boolean add(E e) {

    ensureCapacityInternal(size + 1);

    elementData[size++] = e;

    return true;

}


Y ensureCapacityInternal verifica si el arreglo tiene espacio suficiente:


private void ensureCapacityInternal(int minCapacity) {

    if (elementData == EMPTY_ELEMENTDATA) {

        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

    }

    ensureExplicitCapacity(minCapacity);

}



Si no hay espacio, llama a grow():


private void grow(int minCapacity) {

    int oldCapacity = elementData.length;

    int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5 veces más

    if (newCapacity - minCapacity < 0)

        newCapacity = minCapacity;

    elementData = Arrays.copyOf(elementData, newCapacity);

}



Cuando se queda sin espacio, duplica la capacidad en un 50% (crecimiento amortizado).


Acceder es O(1) porque es un arreglo:


public E get(int index) {

    rangeCheck(index);

    return (E) elementData[index];

}


Cuando quitás un elemento:


list.remove(2);


se mueve el resto de los elementos para no dejar “huecos”:


public E remove(int index) {

    rangeCheck(index);


    E oldValue = elementData(index);


    int numMoved = size - index - 1;

    if (numMoved > 0)

        System.arraycopy(elementData, index + 1, elementData, index, numMoved);


    elementData[--size] = null; // libera la referencia

    return oldValue;

}

Esto hace que remove sea O(n) en el peor caso (por el corrimiento de elementos).


Veamos ventajas y desventajas

Ventajas:

  • Acceso aleatorio rápido (O(1)).
  • Memoria contigua, lo que mejora la localidad de referencia.
  • Crece automáticamente.


Desventajas:

  • Insertar o eliminar en el medio es costoso (O(n)).
  • Aumentar la capacidad implica copiar el arreglo completo.


ArrayList es básicamente una versión moderna y no sincronizada de Vector.

Si necesitás sincronización, podés envolverlo así:

List<String> syncList = Collections.synchronizedList(new ArrayList<>());


miércoles, 8 de octubre de 2025

Array en Elm: eficiencia y acceso rápido a los datos


En Elm, las listas (List) son muy comunes, pero cuando necesitamos acceso rápido por índice, actualizaciones eficientes o manejar colecciones grandes, entra en juego Array.

Un Array en Elm es una estructura inmutable y eficiente que permite:

  • Acceder a elementos por índice (O(log n)).
  • Actualizar posiciones específicas sin mutar el original.
  • Convertirse fácilmente a listas (`List`) y viceversa.


Su módulo se importa así:


import Array exposing (Array)


Podés crearlo vacío o a partir de una lista:


-- Array vacío

vacio : Array Int

vacio = Array.empty


-- Desde una lista

numeros : Array Int

numeros = Array.fromList [10, 20, 30, 40]


Y también convertirlo nuevamente a lista:


Array.toList numeros

-- [10, 20, 30, 40]


Para obtener el valor en un índice determinado:


Array.get 2 numeros

-- Just 30


Observá que devuelve un Maybe, ya que el índice podría no existir.

Array.get 10 numeros

-- Nothing


-- Actualizar el valor en la posición 1

actualizado =

    Array.set 1 25 numeros


Array.toList actualizado

-- [10, 25, 30, 40]


Cada modificación devuelve un nuevo Array, sin alterar el original (inmutabilidad funcional).

Para agregar al final, usamos Array.push:


conNuevo =

    Array.push 50 numeros


Array.toList conNuevo

-- [10, 20, 30, 40, 50]


Y para quitar el último, usamos Array.pop:


sinUltimo =

    Array.pop conNuevo


Array.toList sinUltimo

-- [10, 20, 30, 40]


Podés recorrerlo de manera muy parecida a una lista:


Array.map (\x -> x * 2) numeros

-- {20, 40, 60, 80}


Y también filtrarlo o transformarlo:


Array.filter (\x -> x > 20) numeros

-- {30, 40}

Y hay más!! :


Array.length: Devuelve el tamaño          

Array.length numeros --> 4

                       |

Array.isEmpty: ¿Está vacío? 

Array.isEmpty Array.empty --> True


Array.initialize: Crea un array con una función

Array.initialize 5 (\i -> i * 2) --> {0,2,4,6,8}


Array.foldl/Array.foldr: Reduce el array

Array.foldl (+) 0 numeros --> 100                |


Veamos un ejemplo completo:


import Array exposing (Array)


main =

    let

        nums = Array.fromList [1,2,3,4]

        dobles = Array.map (\n -> n * 2) nums

        filtrados = Array.filter (\n -> n > 4) dobles

    in

    Array.toList filtrados

    

-- Resultado: [6,8]


Array en Elm te da:

  • Acceso rápido por índice
  • Inmutabilidad garantizada
  • Operaciones eficientes
  • Interoperabilidad con List


Usá List cuando pienses en secuencias y Array cuando necesites posiciones.


Dict en Elm: Diccionarios funcionales para datos ordenados


En Elm, un Dict (abreviatura de Dictionary) es una estructura de datos inmutable que almacena pares clave–valor, de forma ordenada y segura por tipos.

Un Dict se define como:


Dict comparable value


  • comparable → tipo de la clave (por ejemplo, `String`, `Int`, etc.)
  • value → tipo del valor asociado


Solo podés usar tipos comparables como claves, es decir, tipos con un orden definido (Int, String, Char, etc.).

No podés usar listas, records o funciones como claves.

Podés crear un diccionario vacío o con elementos iniciales:


import Dict exposing (Dict)


-- Vacío

usuarios : Dict Int String

usuarios = Dict.empty


-- Con valores

usuariosIniciales : Dict Int String

usuariosIniciales =

    Dict.fromList

        [ (1, "Ana")

        , (2, "Luis")

        , (3, "María")

        ]


Para buscar un valor por su clave:


Dict.get 2 usuariosIniciales

-- Resultado: Just "Luis"


Dict.get 5 usuariosIniciales

-- Resultado: Nothing


El resultado es un Maybe, lo que evita errores por claves inexistentes.

Podés manejarlo así:


case Dict.get 5 usuariosIniciales of

    Just nombre ->

        "Usuario encontrado: " ++ nombre

    Nothing ->

        "No existe ese usuario."


Insertar y eliminar elementos:


-- Agregar o actualizar

usuarios2 =

    Dict.insert 4 "Sofía" usuariosIniciales


-- Eliminar

usuarios3 =

    Dict.remove 2 usuarios2


Todo es inmutable: estas operaciones devuelven un nuevo Dict, no modifican el original.

Podés convertirlo a lista y trabajar con sus elementos:


Dict.toList usuariosIniciales

-- [(1, "Ana"), (2, "Luis"), (3, "María")]


List.map (\(id, nombre) -> nombre ++ " (" ++ String.fromInt id ++ ")")

    (Dict.toList usuariosIniciales)


O usar funciones específicas:


Dict.map (\_ nombre -> String.toUpper nombre) usuariosIniciales


Dict.member key dict: ¿Existe la clave?                   

Dict.size dict: Cantidad de elementos               

Dict.keys dict: Lista de claves                     

Dict.values dict: Lista de valores                    

Dict.filter pred dict: Filtra según condición              

Dict.union d1 d2: Une dos diccionarios                

Dict.merge: Mezcla con control sobre conflictos 

Dict en Elm es:

  • Inmutable
  • Ordenado por clave
  • Seguro (usa `Maybe` para búsquedas)
  • Funcional y expresivo


En Elm, un Dict es más que un mapa: es una garantía de orden, seguridad y pureza funcional.


La STL en C++: tu mejor aliada del día a día


La STL (Standard Template Library) es uno de los pilares más poderosos de C++.

Nos brinda estructuras de datos genéricas, algoritmos reutilizables y utilidades funcionales para escribir código más limpio, rápido y seguro.

Vamos a repasar sus componentes principales y las funciones más usadas.

La STL nos da varios tipos de contenedores, cada uno con su propósito:

  • vector: Lista dinámica, similar a un array redimensionable 
    • std::vector<int> v = {1,2,3};       
  • list: Lista doblemente enlazada  
    • std::list<std::string> nombres;
  • deque: Doble cola (insertar por ambos extremos) 
    • std::deque<int> d;
  • set: Conjunto ordenado, sin duplicados
    • std::set<int> numeros;
  • map: Diccionario (clave → valor) ordenado 
    • std::map<std::string, int> edades;
  • unordered_map: Diccionario sin orden, pero más rápido
    • std::unordered_map<int, std::string> alumnos;

Los algoritmos de la STL trabajan sobre iteradores, lo que los hace genéricos y potentes.


#include <algorithm>

#include <vector>

#include <iostream>


int main() {

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


    // sort: ordena

    std::sort(numeros.begin(), numeros.end());


    // reverse: invierte

    std::reverse(numeros.begin(), numeros.end());


    // count: cuenta ocurrencias

    int cantidad = std::count(numeros.begin(), numeros.end(), 1);


    // find: busca un elemento

    auto it = std::find(numeros.begin(), numeros.end(), 5);


    if (it != numeros.end())

        std::cout << "Encontrado: " << *it << std::endl;

    // accumulate: suma o acumula valores

    int suma = std::accumulate(numeros.begin(), numeros.end(), 0);

    std::cout << "Suma total: " << suma << std::endl;

}


Estos algoritmos no dependen del tipo de contenedor.

Si el contenedor tiene iteradores, funciona igual.

Veamos funciones:


std::copy_if: Copia elementos que cumplan una condición 

copy_if(v.begin(), v.end(), back_inserter(res), [](int n){ return n > 3; });


std::transform: Aplica una función a cada elemento

transform(v.begin(), v.end(), v.begin(), [](int n){ return n * 2; });


std::for_each: Itera con una función lambda

for_each(v.begin(), v.end(), [](int n){ cout << n << " "; });                     


std::remove_if: Elimina (lógicamente) los elementos que cumplan una condición

v.erase(remove_if(v.begin(), v.end(), [](int n){ return n % 2 == 0; }), v.end());


Veamos funciones matematicas comunes:


std::min_element(v.begin(), v.end());

std::max_element(v.begin(), v.end());

std::accumulate(v.begin(), v.end(), 0);

std::inner_product(v.begin(), v.end(), v2.begin(), 0);


Y por ultimo unos trucos:

  • Usá auto para no repetir tipos largos.
  • Preferí algoritmos STL + lambdas antes que bucles manuales.
  • Aprovechá std::optional, std::variant y std::tuple (desde C++17) para código más expresivo.


La STL no es solo una librería: es una forma de pensar en C++.

En lugar de escribir bucles y estructuras manuales, usás herramientas ya optimizadas, seguras y expresivas.

lunes, 6 de octubre de 2025

Mónadas en Elm: Encadenando Cálculos con Elegancia


Las mónadas son un concepto central en la programación funcional. Aunque suenen complicadas, en Elm ya las usamos todo el tiempo sin darnos cuenta.

En términos simples: Una mónada es un funtor con una forma de encadenar operaciones que devuelven estructuras.

En Elm esto se hace con funciones como andThen (también llamada bind en otros lenguajes).

Maybe como mónada: Cuando trabajamos con valores opcionales (Maybe), podemos encadenar operaciones sin preocuparnos por el caso Nothing.


dividir : Int -> Int -> Maybe Int

dividir a b =

    if b == 0 then

        Nothing

    else

        Just (a // b)


calculo : Maybe Int

calculo =

    Just 100

        |> Maybe.andThen (\x -> dividir x 2)

        |> Maybe.andThen (\y -> dividir y 5)


-- Resultado: Just 10


Si en algún paso se produce un Nothing, toda la cadena devuelve Nothing automáticamente.


Result como mónada


Con Result, podemos propagar errores sin necesidad de escribir mucho código repetitivo:


parseEntero : String -> Result String Int

parseEntero s =

    case String.toInt s of

        Just n -> Ok n

        Nothing -> Err "No es un número"


invertir : Int -> Result String Float

invertir n =

    if n == 0 then

        Err "División por cero"

    else

        Ok (1 / toFloat n)


calculo : Result String Float

calculo =

    parseEntero "10"

        |> Result.andThen invertir


-- Resultado: Ok 0.1


Si el parseo falla, se corta la cadena con Err. Si no, se sigue con el siguiente cálculo.


List como mónada


Con listas, una mónada nos permite generar todas las combinaciones posibles de elementos:


pares : List (Int, Int)

pares =

    [1, 2]

        |> List.concatMap (\x ->

            [3, 4] |> List.map (\y -> (x, y))

        )


-- Resultado: [(1,3),(1,4),(2,3),(2,4)]


Esto es equivalente a las list comprehensions en Haskell.


Resumen de funciones clave en Elm

  • Maybe.andThen → encadena operaciones opcionales.
  • Result.andThen → encadena operaciones que pueden fallar con error.
  • List.concatMap → encadena operaciones que generan más listas.


Todas siguen el mismo patrón monádico.

En Elm, aunque no hablemos directamente de “Mónadas” en la sintaxis, las usamos constantemente.

domingo, 5 de octubre de 2025

fold en Elm: Reducción de Listas Paso a Paso


En programación funcional, fold (también conocido como reducción) es una técnica para recorrer una lista y acumular un resultado.

En Elm tenemos dos variantes principales:

List.foldl → recorre la lista de izquierda a derecha.

List.foldr → recorre la lista de derecha a izquierda.


Ambos reciben:

  1. Una función acumuladora.
  2. Un valor inicial.
  3. La lista a recorrer.

Y devuelven un único valor.


Veamos un ejemplo:


suma : List Int -> Int

suma lista =

    List.foldl (\x acc -> x + acc) 0 lista

-- suma [1,2,3,4] == 10


Aquí:

  • (\x acc -> x + acc) es la función acumuladora.
  • 0 es el valor inicial.
  • lista es la lista que recorremos.


Otro ejemplo:


concatenar : List String -> String

concatenar palabras =

    List.foldl (\palabra acc -> palabra ++ " " ++ acc) "" palabras


-- concatenar ["Hola", "mundo"] == "Hola mundo "

Ojo: el orden importa. Si usás `foldr`, el resultado cambia:


List.foldr (\palabra acc -> palabra ++ " " ++ acc) "" ["Hola", "mundo"]

-- "Hola mundo "


En este caso no hay diferencia visible, pero con listas grandes o con operaciones no conmutativas sí la hay.


Otro ejemplo: Calcular el producto (foldl vs foldr)


producto : List Int -> Int

producto =

    List.foldl (\x acc -> x * acc) 1

-- producto [1,2,3,4] == 24


Con foldr se obtiene el mismo resultado porque la multiplicación es conmutativa.


Veamos el orden de evaluación:

foldl (izquierda a derecha):


  foldl f acc [1,2,3]

  == f 3 (f 2 (f 1 acc))


foldr (derecha a izquierda):


  foldr f acc [1,2,3]

  == f 1 (f 2 (f 3 acc))


Esto puede cambiar el resultado cuando la operación no es conmutativa, o puede impactar en la performance con listas grandes.


Veamos otro ejemplo: 


invertir : List a -> List a

invertir lista =

    List.foldl (\x acc -> x :: acc) [] lista


-- invertir [1,2,3] == [3,2,1]


En Elm, fold es una herramienta poderosa para:

  • Reducir listas a un único valor.
  • Evitar bucles explícitos.
  • Expresar cálculos de manera declarativa.


Usá foldl cuando quieras acumular de izquierda a derecha (más común y eficiente).

Usá foldr cuando necesites preservar el orden de construcción en estructuras recursivas.


miércoles, 1 de octubre de 2025

Ignorar vs. Descartar Valores de Retorno en C#


En C# es común encontrarnos con métodos que devuelven un valor que a veces no necesitamos. En esos casos, hay dos formas de manejarlo: ignorar o descartar explícitamente. Aunque parecen lo mismo, hay una diferencia sutil pero importante.

1. Ignorar el valor


File.Exists("config.json");


Aquí llamamos al método, pero el valor (true o false) se ignora directamente.

El compilador lo interpreta como si el resultado no fuera relevante.


2. Descartar explícitamente con _ =


_ = File.Exists("config.json");


En este caso, el valor de retorno se asigna al identificador especial _, conocido como discard. Esto indica explícitamente que sabemos que existe un valor, pero decidimos no usarlo.


  • Si el método devuelve void, ambas formas son equivalentes.
  • Si el método devuelve un valor, la diferencia es semántica:
    • File.Exists(...); → Ignorás el valor.
    • _ = File.Exists(...); → Descartás el valor de manera explícita.


Esto es útil con reglas de análisis estático, como CA1806 (Do not ignore method results), que pueden marcar advertencias si el resultado se ignora, pero no si se descarta explícitamente.


Veamos un ejemplo completo:


bool success = int.TryParse("123", out int number);


// Opción 1: ignorar el retorno

int.TryParse("456", out int _);   // algunos analizadores generan warning


// Opción 2: descartar explícitamente el retorno

_ = int.TryParse("789", out int _); // sin warning, el descarte es intencional


  • Usar _ = no cambia el comportamiento del programa.
  • Sirve para comunicar intencionalidad al compilador y a otros desarrolladores.
  • Si querés ser más explícito (y evitar advertencias de análisis), preferí la forma con _ =.


Functores en Elm


En programación funcional, un funtor es una estructura de datos que sabe cómo aplicar una función a los valores que contiene, sin que tengamos que preocuparnos por los detalles internos.

En Elm, el concepto de funtor aparece principalmente con el uso de map en distintos tipos.


 ¿Qué es un funtor?

En términos simples:

Un funtor es algo sobre lo que podemos hacer map.

Nos permite aplicar una función a un valor dentro de un contexto (List, Maybe, Result, etc.).

Veamos unos ejemplos: 


Maybe como List:


dobles : List Int

dobles =

    List.map (\x -> x * 2) [1, 2, 3]

-- Resultado: [2, 4, 6]


Acá List es un funtor, porque podemos aplicar una función a todos sus elementos usando map.


Maybe como funtor:


incrementar : Maybe Int -> Maybe Int

incrementar valor =

    Maybe.map (\x -> x + 1) valor


-- incrementar (Just 5) == Just 6

-- incrementar Nothing == Nothing


El map de Maybe aplica la función solo si hay un valor (Just), y deja todo igual si es Nothing.


Result como funtor


toUppercase : Result String String -> Result String String

toUppercase resultado =

    Result.map String.toUpper resultado


-- toUppercase (Ok "elm") == Ok "ELM"

-- toUppercase (Err "Error") == Err "Error"


El map de Result aplica la función al valor exitoso (Ok), y en caso de error (Err) no hace nada.


Todo funtor debe cumplir dos leyes:

1. Identidad:

   List.map identity xs == xs

   Aplicar la función identity no debe cambiar nada.


2. Composición:

   List.map (f >> g) xs == (List.map f >> List.map g) xs

   Aplicar dos funciones compuestas debe ser lo mismo que aplicarlas de a una.


En Elm, los funtores nos permiten:

  • Aplicar funciones de forma elegante a valores dentro de contextos.
  • Evitar escribir lógica repetida para casos como Nothing o Err.
  • Pensar en términos más declarativos y expresivos.

Siempre que uses map en List, Maybe o Result, ¡estás usando funtores en Elm!

viernes, 26 de septiembre de 2025

Simulando Listas por Comprensión en Elm


En lenguajes como Haskell o Python, las listas por comprensión permiten generar y transformar listas con una sintaxis muy compacta.

En Elm no existen listas por comprensión como sintaxis, pero sí podemos expresar lo mismo con funciones de orden superior como map, filter y concatMap.

En Haskell:


[x * 2 | x <- [1..5]]

-- Resultado: [2,4,6,8,10]


En Elm:


dobles : List Int

dobles =

    [1,2,3,4,5]

        |> List.map (\x -> x * 2)


Resultado: [2,4,6,8,10]


Con condición (filter)


En Haskell:


[x * 2 | x <- [1..5], x > 2]

-- Resultado: [6,8,10]


En Elm:


doblesMayoresQueDos : List Int

doblesMayoresQueDos =

    [1,2,3,4,5]

        |> List.filter (\x -> x > 2)

        |> List.map (\x -> x * 2)


-- Resultado: [6,8,10]


Generando pares (concatMap)


En Haskell:


[(x,y) | x <- [1,2], y <- [3,4]]

-- Resultado: [(1,3),(1,4),(2,3),(2,4)]


En Elm:


pares : List (Int, Int)

pares =

    [1,2]

        |> List.concatMap (\x ->

            [3,4] |> List.map (\y -> (x, y))

        )


-- Resultado: [(1,3),(1,4),(2,3),(2,4)]


Aunque Elm no tiene listas por comprensión como sintaxis, podemos lograr la misma expresividad combinando funciones de orden superior:

  • List.map → transformación.
  • List.filter → condiciones.
  • List.concatMap → anidación (equivalente a los múltiples generadores de Haskell).

De esta forma, Elm mantiene un estilo declarativo y expresivo, alineado con su filosofía de simplicidad y claridad.

Lazy Evaluation en Elm: ¿Existe?


En muchos lenguajes funcionales, como Haskell, la lazy evaluation (evaluación perezosa) es una característica central: los valores no se calculan hasta que realmente se necesitan.

En Elm, en cambio, la evaluación es estricta por defecto (eager evaluation), lo que significa que todos los argumentos de una función se evalúan inmediatamente antes de ser usados.


Veamos un ejemplo simple:


calcular : Int -> Int -> Int

calcular x y =

    x + 1


resultado =

    calcular 5 (Debug.log "evaluando..." 10)


Al ejecutar este código, veremos en la consola:


evaluando...


Aunque el parámetro y nunca se use, Elm lo evalúa igual, porque siempre evalúa sus argumentos antes de ejecutar la función.


Aunque Elm no es lazy por diseño, sí ofrece el módulo Lazy, que permite diferir la evaluación de expresiones costosas hasta que realmente las necesitemos.


import Lazy exposing (Lazy)


perezoso : Lazy Int

perezoso =

    Lazy.lazy (\_ -> 1 + 2)


resultado : Int

resultado =

    Lazy.force perezoso


En este caso:

  • Lazy.lazy encapsula un cálculo.
  • Lazy.force ejecuta el cálculo cuando es necesario.


¿Cuándo usar Lazy?


El módulo Lazy es útil en:

  • Estructuras grandes que no siempre se recorren por completo.
  • Cálculos costosos que solo se necesitan en ciertas condiciones.
  • Simulación de comportamiento perezoso, para optimizar rendimiento.


Sin embargo, Elm sigue favoreciendo la evaluación estricta en la mayoría de los casos, para mantener la simplicidad y la predictibilidad del lenguaje.

default en C#: El valor por defecto en tipos genéricos


Cuando trabajamos con genéricos en C#, nos enfrentamos a una pregunta clave:

¿cómo inicializo un valor de un tipo que aún no conozco?

Para esto existe la palabra clave default, que nos permite obtener el valor por defecto de cualquier tipo, sea valor o referencia.

En C#, cada tipo tiene un valor por defecto:

  • Tipos por referencia (class, string, object) → null.
  • Tipos numéricos (int, double, etc.) → 0.
  • bool → false.
  • Estructuras (struct) → todos sus campos inicializados a 0 o null.


Ejemplo:


int numero = default;      // 0

bool bandera = default;    // false

string texto = default;    // null


En un método genérico no sabemos de antemano si T es un tipo de referencia o de valor.

default(T) resuelve este problema devolviendo el valor inicial correcto según el tipo.


public T ObtenerPrimerElemento<T>(List<T> lista)

{

    if (lista.Count > 0)

        return lista[0];

    else

        return default(T);  // null si T es referencia, 0/false/etc si es valor

}


Ejemplo de uso:


Console.WriteLine(ObtenerPrimerElemento(new List<int>()));     // 0

Console.WriteLine(ObtenerPrimerElemento(new List<string>()));  // null


A partir de C# 7.1, se introdujo una sintaxis más corta: simplemente default sin necesidad de escribir el tipo entre paréntesis.


T valor = default; // equivalente a default(T)


Esto simplifica la escritura en genéricos y métodos.

Un ejemplo muy común es al buscar un valor en un diccionario sin estar seguros de que exista:


public TValue Buscar<TKey, TValue>(Dictionary<TKey, TValue> dic, TKey clave)

{

    if (dic.TryGetValue(clave, out TValue valor))

        return valor;


    return default; // null si TValue es referencia, 0/false si es valor

}


La palabra clave default es una herramienta poderosa para manejar genéricos en C#.

Permite escribir código seguro y flexible sin preocuparnos si T es una clase o un struct, y desde C# 7.1 la sintaxis se volvió aún más simple y expresiva.

Pattern Matching en Elm: Desestructurando Datos de Forma Segura


El pattern matching en Elm es una herramienta poderosa para trabajar con listas, tuplas, registros y tipos algebraicos.

Permite desestructurar datos y cubrir todos los casos posibles de manera clara y segura, gracias al compilador.

Pattern Matching con listas:


primerElemento : List Int -> String

primerElemento lista =

    case lista of

        [] ->

            "Lista vacía"

        x :: xs ->

            "El primer elemento es " ++ String.fromInt x


[] → lista vacía.

x :: xs → descompone la lista en el primer elemento x y el resto xs.


Ejemplo:


primerElemento []          -- "Lista vacía"

primerElemento [10,20,30]  -- "El primer elemento es 10"


Pattern Matching con tuplas


sumaTupla : (Int, Int) -> Int

sumaTupla tupla =

    case tupla of

        (a, b) ->

            a + b


sumaTupla (3, 4)   -- 7


También podés combinar:


describeTupla : (String, Int) -> String

describeTupla (nombre, edad) =

    nombre ++ " tiene " ++ String.fromInt edad ++ " años"


Pattern Matching con registros


type alias Persona =

    { nombre : String

    , edad : Int

    }


saludo : Persona -> String

saludo persona =

    case persona of

        { nombre, edad } ->

            "Hola " ++ nombre ++ ", tenés " ++ String.fromInt edad ++ " años"


Ejemplo:

saludo { nombre = "Ana", edad = 30 }

-- "Hola Ana, tenés 30 años"


Pattern Matching con tipos algebraicos


Elm brilla con sus tipos definidos por el usuario.


type Resultado

    = Exito String

    | Error String


procesar : Resultado -> String

procesar res =

    case res of

        Exito mensaje ->

            "Todo salió bien: " ++ mensaje

        Error mensaje ->

            "Ocurrió un error: " ++ mensaje


Ejemplo:


procesar (Exito "Archivo guardado")

-- "Todo salió bien: Archivo guardado"


procesar (Error "Permiso denegado")

-- "Ocurrió un error: Permiso denegado"


Algo clave en Elm: el compilador exige cubrir todos los casos posibles en un case.

Si olvidás uno, el código no compila → esto evita errores en tiempo de ejecución.


  • case ... of permite desestructurar listas, tuplas, registros y tipos algebraicos.
  • Es una forma segura y declarativa de trabajar con datos.
  • El compilador obliga a cubrir todos los casos, garantizando que no haya estados no contemplados.


 El pattern matching es uno de los pilares que hace a Elm un lenguaje expresivo y confiable.


lunes, 22 de septiembre de 2025

Records en Java: Clases Inmutables de Forma Sencilla



A partir de Java 14 (como preview) y de forma estable en Java 16, se introdujeron los records.

Un record es una forma concisa de declarar clases inmutables que sirven principalmente para transportar datos (clases DTO, value objects, etc.).

La sintaxis básica es:

public record Persona(String nombre, int edad) {}


Esto genera automáticamente:

  • Los campos privados y finales nombre y edad.
  • Un constructor que inicializa esos campos.
  • Los métodos getters nombre() y edad().
  • Una implementación de toString().
  • Métodos equals() y hashCode() basados en los campos.


Veamos un ejemplo de uso: 


public class Main {

    public static void main(String[] args) {

        Persona p = new Persona("Ana", 30);


        System.out.println(p.nombre()); // "Ana"

        System.out.println(p.edad());   // 30

        System.out.println(p);          // Persona[nombre=Ana, edad=30]

    }

}


Notá que no se usan getNombre() ni getEdad(), sino directamente nombre() y edad().

Los campos de un record son inmutables (implícitamente final).

No existe p.setEdad(31) → en su lugar, deberías crear un nuevo objeto Persona.


Aunque el record genera mucho código automáticamente, también podés definir métodos adicionales:


public record Persona(String nombre, int edad) {

    public boolean esMayorDeEdad() {

        return edad >= 18;

    }

}


Persona juan = new Persona("Juan", 17);

System.out.println(juan.esMayorDeEdad()); // false


Podés agregar lógica en el constructor, pero siempre respetando la inicialización de todos los campos:


public record Persona(String nombre, int edad) {

    public Persona {

        if (edad < 0) {

            throw new IllegalArgumentException("La edad no puede ser negativa");

        }

    }

}


Este es el constructor compacto: no es necesario repetir la asignación de campos, Java lo hace automáticamente.


Los records funcionan muy bien como DTOs, por ejemplo en listas o streams:


List<Persona> personas = List.of(

    new Persona("Ana", 30),

    new Persona("Luis", 25)

);


personas.stream()

    .filter(p -> p.edad() > 26)

    .forEach(System.out::println);


En Resumen: 

  • Los records simplifican la creación de clases para transportar datos.
  • Generan automáticamente: constructor, getters, equals, hashCode y toString.
  • Son inmutables por diseño.
  • Se pueden añadir métodos y validaciones de construcción.


Son ideales para DTOs, value objects y cualquier situación en la que antes usabas una clase con solo atributos y getters.

sábado, 20 de septiembre de 2025

Registros en Elm


En Elm, un registro es una colección de pares clave = valor, similar a un objeto en otros lenguajes, pero inmutable y tipado.

Son muy usados para agrupar datos de manera clara y segura.


persona : { nombre : String, edad : Int }

persona =

    { nombre = "Ana", edad = 30 }


  • nombre es de tipo String.
  • edad es de tipo Int.


nombreDeAna : String

nombreDeAna =

    persona.nombre

-- Resultado: "Ana"


También se puede usar una función anónima para acceder:


List.map .nombre [ persona, { nombre = "Luis", edad = 25 } ]

-- ["Ana", "Luis"]


Los registros en Elm son inmutables.

Para "cambiar" un campo, se crea una copia con el campo modificado:


personaMayor : { nombre : String, edad : Int }

personaMayor =

    { persona | edad = 31 }


Esto no altera persona, sino que genera un nuevo registro.

Las funciones pueden recibir registros directamente:


saludar : { nombre : String } -> String

saludar r =

    "Hola, " ++ r.nombre

saludar persona

-- "Hola, Ana"


Cuando un registro es usado en varios lugares, conviene definir un alias de tipo:


type alias Persona =

    { nombre : String

    , edad : Int

    }


juan : Persona

juan =

    { nombre = "Juan", edad = 40 }


cumplirAnios : Persona -> Persona

cumplirAnios p =

    { p | edad = p.edad + 1 }


Podemos "abrir" un registro en sus campos:


mostrar : Persona -> String

mostrar { nombre, edad } =

    nombre ++ " tiene " ++ String.fromInt edad ++ " años"

mostrar juan

-- "Juan tiene 40 años"


En Resumen:

  • Un registro agrupa datos con nombres de campo.
  • Son inmutables, para "modificarlos" se usa la sintaxis { r | campo = nuevoValor }.
  • Los alias de tipo (type alias) hacen el código más claro.
  • Se puede usar desestructuración para acceder fácilmente a los campos.


En Elm, los registros reemplazan el concepto de objetos de otros lenguajes, pero sin herencia ni mutabilidad.

viernes, 19 de septiembre de 2025

10 formas de generar valor con IA


Copyright © 2025 Oracle y sus asociados. Todos los derechos reservados. Oracle Corporation - Worldwide Headquarters, 2300 Oracle Way, Austin, TX 78741, United States
Oracle