Translate

domingo, 23 de noviembre de 2025

Cómo resuelve Scala los parámetros implícitos

En Scala, los parámetros implícitos permiten que una función reciba argumentos sin que el programador los pase explícitamente.

El compilador se encarga de buscar un valor adecuado en el ámbito para completar la llamada.

Veamos un ejemplo simple:


def saludar(nombre: String)(implicit saludo: String): Unit =

  println(s"$saludo, $nombre!")


implicit val saludoPorDefecto: String = "Hola"


saludar("Emanuel") // Usa el valor implícito definido arriba


Cuando Scala ve que falta un argumento para un parámetro implicit, sigue este proceso:

  1. Busca en el ámbito local: valores o funciones marcados como implicit del tipo requerido.
  2. Si no encuentra ninguno, busca en el objeto compañero (companion object) del tipo esperado.
  3. Si encuentra más de uno y no puede decidir cuál usar, el compilador lanza un error por ambigüedad.
  4. Si no encuentra ninguno, lanza un error por parámetro implícito no encontrado.

Veamos un ejemplo de ambigüedad:


implicit val saludo1: String = "Hola"

implicit val saludo2: String = "Buenas"


saludar("Emanuel") // Error: ambiguous implicits


El compilador no puede elegir entre `saludo1` y `saludo2`.


Scala resuelve los parámetros implícitos buscando un valor del tipo adecuado en:

El ámbito local,

Los imports activos,

Y los companion objects relacionados.


Es un mecanismo potente que permite propagar contextos automáticamente (por ejemplo, ExecutionContext, Ordering, Numeric, etc.), reduciendo la necesidad de pasar dependencias manualmente.

viernes, 21 de noviembre de 2025

Parámetros implícitos en Scala ¿y en otros languages?



Scala ofrece una poderosa característica llamada parámetros implícitos (implicit parameters), que permite pasar argumentos a funciones sin tener que especificarlos explícitamente cada vez. Esta capacidad se utiliza mucho para inyección de dependencias, contextos compartidos o type classes.


def saludar(nombre: String)(implicit saludo: String): Unit =

  println(s"$saludo, $nombre!")


implicit val saludoPorDefecto: String = "Hola"


saludar("Emanuel") // imprime "Hola, Emanuel!"


Aquí, el parámetro saludo se pasa de manera implícita gracias a la definición previa de un valor implicit.

Si se define otro valor implícito en el mismo alcance, ese será el utilizado, lo que permite una gran flexibilidad contextual.

Aunque el concepto de implícito es característico de Scala, existen ideas similares:

Haskell usa type classes, que se resuelven de forma implícita por el compilador.

Por ejemplo, la clase `Eq` o `Show` se comporta como una inyección automática de comportamientos según el tipo.


show 42 -- el compilador infiere automáticamente la instancia de Show Int


Rust usa traits y type inference, que cumplen un rol similar. Las implementaciones de traits se aplican automáticamente sin especificarlas cada vez.


println!("{}", 42); // Usa automáticamente el trait Display para i32


C# no tiene parámetros implícitos como tal, pero existen aproximaciones:

  • Inyección de dependencias en frameworks como ASP.NET.
  • Attributes y default parameters.
  • Desde C# 12, Primary constructors y default interface methods permiten inyectar comportamientos contextuales, aunque no son implícitos en tiempo de compilación.


Python tampoco tiene parámetros implícitos, pero se puede emular con:

  • Decoradores.
  • Context managers (with).
  • Argumentos por defecto o variables globales.


Los parámetros implícitos de Scala logran un equilibrio interesante entre claridad y potencia, especialmente en contextos funcionales.

Su uso debe ser cuidadoso, ya que abusar de ellos puede hacer que el flujo de datos sea menos evidente.

Sin embargo, cuando se aplican bien, son una herramienta que simplifica enormemente el código y reduce la verbosidad.

lunes, 17 de noviembre de 2025

¿Cómo funciona realmente #include en C++?


Cuando escribimos algo como:

#include <iostream>


podría parecer que el compilador “importa” la librería, pero en realidad el #include no es parte del lenguaje, sino una instrucción del preprocesador.

Lo que hace es copiar literalmente el contenido del archivo incluido dentro del código antes de compilar.


Por ejemplo, si tenés:

#include "miarchivo.h"


el preprocesador reemplaza esa línea por el texto completo de miarchivo.h.

Así, el compilador ve un solo archivo unificado.


¿Y qué pasa con las librerías estándar? Cuando hacés #include <iostream>, el compilador busca ese archivo en los directorios del sistema  (por ejemplo, /usr/include/c++/ en Linux).

Ese archivo sí existe y contiene declaraciones, no implementaciones.

Por ejemplo:


namespace std {

  extern ostream cout;

}


El archivo de cabecera sólo declara las funciones y objetos que vas a usar. Las implementaciones están en archivos binarios precompilados (.a, .lib, .so, .dll), que se vinculan en la etapa de linking.


El proceso completo es así:

  • Preprocesador: copia los headers (#include) y expande macros.
  • Compilación: convierte cada .cpp en un archivo objeto.
  • Linker: une esos objetos con las librerías que contienen las funciones reales.


Como los headers se copian literalmente, si se incluyen varias veces puede haber redefiniciones.

Por eso se usan las llamadas guardas de inclusión:


#ifndef MIARCHIVO_H

#define MIARCHIVO_H

// contenido

#endif


o la forma moderna:


#pragma once


En resumen, #include no importa código ejecutable:

  • solo inserta declaraciones que el compilador necesita conocer.
  • El código real vive en las librerías que se enlazan al final del proceso.


sábado, 15 de noviembre de 2025

Cómo funciona un modelo generativo


En el post anterior vimos qué es la Inteligencia Artificial Generativa y cómo puede crear contenido nuevo a partir de patrones aprendidos.

Ahora vamos a mirar debajo del capó: ¿cómo hace realmente un modelo para “inventar” texto, imágenes o música?

La respuesta puede resumirse en una idea:

> Un modelo generativo aprende a predecir lo que viene después.

Sí, suena simple. Pero detrás de esa predicción hay millones (o billones) de parámetros, una enorme cantidad de datos y un entrenamiento matemático fascinante.

Imaginemos que queremos que una máquina aprenda a escribir frases en español.

Para eso le damos millones de ejemplos: libros, artículos, correos, conversaciones.

El modelo analiza esas frases y aprende cómo se relacionan las palabras entre sí.


Por ejemplo, si ve muchas veces frases como:

> “El gato duerme en el sofá.”

> “El perro duerme en la cama.”


entonces entiende que después de “El gato” o “El perro” es muy probable que aparezca un verbo como duerme, corre o come.

Así, el modelo no memoriza frases completas, sino que aprende distribuciones de probabilidad:


> Dado un contexto (por ejemplo, “El gato”), ¿cuál es la palabra más probable que sigue?


Ese es el corazón de un modelo generativo.


Para que una máquina pueda trabajar con texto, primero debe convertir las palabras en números.

Cada fragmento de texto (una palabra, una sílaba o incluso una letra) se transforma en un token.


Por ejemplo:


“El gato duerme” → [101, 45, 202]


Estos números no tienen significado por sí mismos, pero el modelo los usa para representar el texto de forma matemática.

Con el tiempo, aprende que ciertos tokens aparecen juntos y en qué contextos.


Durante el entrenamiento, el modelo se enfrenta a miles de millones de ejemplos donde debe predecir la siguiente palabra.

Por ejemplo:


Entrada: "El gato"

Salida esperada: "duerme"


Cada vez que acierta, refuerza sus conexiones internas.

Cada vez que se equivoca, ajusta sus parámetros para acercarse un poco más a la respuesta correcta.

Ese proceso se repite millones de veces.


Con el tiempo, el modelo aprende cómo suena el lenguaje humano, y puede generar texto fluido simplemente repitiendo el proceso de predicción: elige una palabra, la agrega, vuelve a predecir la siguiente, y así sucesivamente.

Un modelo generativo moderno está formado por capas de neuronas artificiales conectadas entre sí.

Cada capa transforma la información, detecta patrones y pasa resultados a la siguiente.


Los modelos actuales, como los basados en la arquitectura Transformer, utilizan un mecanismo llamado atención (attention), que les permite decidir qué partes del texto son más relevantes para generar la siguiente palabra.


Por ejemplo, si el texto dice:

> “El gato que persiguió al perro estaba cansado.”


El modelo necesita “prestar atención” a *gato* (y no a *perro*) para entender que quien estaba cansado era el gato.

Eso es exactamente lo que hace el mecanismo de atención: ponderar el contexto de manera inteligente.


Supongamos que el modelo ya aprendió.

Ahora escribimos el inicio de una frase:

"El sol se"


El modelo analiza ese contexto y calcula probabilidades:


pone 0.8 a “pone”

0.1 a “oculta”

0.05 a “refleja”

0.05 a “enciende”


Puede elegir la más probable (pone), o una al azar según la distribución.

Luego repite el proceso con el nuevo contexto:


> “El sol se pone”


Y así, palabra por palabra, va construyendo texto coherente.

Lo mismo ocurre con píxeles en imágenes, notas en música o fotogramas en video.


Cuando vemos a ChatGPT escribir poesía o a DALL·E inventar ilustraciones, parece magia.

Pero en realidad, la creatividad de un modelo generativo proviene de su capacidad estadística para combinar patrones conocidos de forma nueva y coherente.


En cierto sentido, es una mezcla entre:

  • la memoria del lenguaje aprendido, y
  • la improvisación probabilística en cada predicción.


Comportamiento especial según el tipo genérico en C#


En C# no se puede sobrescribir un método solo porque el parámetro genérico es un tipo particular. El lenguaje no admite la especialización de clases genéricas como en C++, y la herencia no distingue entre MiClase<int> y MiClase<IMiInterfaz>.

Aun así, hay formas de lograr un comportamiento distinto según el tipo.

Una opción simple es decidir el comportamiento dentro del propio método:


class MiClase<T>

{

    public void Procesar(T valor)

    {

        if (valor is IMiInterfaz especial)

            ProcesarEspecial(especial);

        else

            ProcesarNormal(valor);

    }


    void ProcesarEspecial(IMiInterfaz x) => Console.WriteLine("Especial!");

    void ProcesarNormal(T x) => Console.WriteLine("Normal");

}


Si querés un despacho más flexible, podés usar dynamic:


class MiClase<T>

{

    public void Procesar(T valor)

        => ProcesarInterno((dynamic)valor);


    void ProcesarInterno(object v) => Console.WriteLine("Normal");

    void ProcesarInterno(IMiInterfaz v) => Console.WriteLine("Especial!");

}


También podés crear una subclase que se active solo para tipos que implementen una interfaz:


class MiClase<T>

{

    public virtual void Procesar(T x) => Console.WriteLine("Normal");

}


class MiClaseEspecial<T> : MiClase<T> where T : IMiInterfaz

{

    public override void Procesar(T x) => Console.WriteLine("Especial!");

}


En resumen, C# no permite sobrescribir métodos genéricos por tipo, pero sí es posible lograr comportamiento especializado combinando pattern matching, dynamic o herencia con restricciones.

miércoles, 12 de noviembre de 2025

Elm vs Haskell: dos caminos del paradigma funcional


Haskell y Elm comparten una raíz común: ambos son lenguajes funcionales puros, con tipado estático, inferencia de tipos y un fuerte énfasis en la inmutabilidad.

Sin embargo, cada uno tomó un camino distinto.

Haskell apostó por la abstracción, la teoría de tipos avanzada y la expresividad; Elm, por la simplicidad, la seguridad y la experiencia del desarrollador.

Haskell nació en el ámbito académico, con el objetivo de ser un lenguaje funcional puro que sirviera como base de investigación.

Por eso prioriza la expresividad, la abstracción y la corrección formal.

Su lema podría ser: “todo puede expresarse en tipos”.

Elm, en cambio, nació del mundo web.

Su meta no es la investigación, sino la confiabilidad.

Fue diseñado para construir interfaces web sin errores en tiempo de ejecución.

Su lema podría ser: “ningún runtime exception, nunca”.


En resumen:

Haskell es una herramienta para explorar los límites del paradigma funcional.

Elm es una herramienta para aplicar ese paradigma con seguridad y pragmatismo.


Ambos tienen tipado estático e inferencia, pero el sistema de tipos de Haskell es mucho más poderoso.

Haskell permite type classes, kind polymorphism, type families, GADTs, monads, existentials y un sinfín de extensiones.

Elm tiene un sistema de tipos mucho más pequeño, pero más legible y predecible.

En Haskell, el tipo puede expresar conceptos avanzados:


fmap :: Functor f => (a -> b) -> f a -> f b


Mientras que en Elm, los tipos se mantienen simples y directos:


List.map : (a -> b) -> List a -> List b


En Haskell podés crear tus propias type classes (Eq, Ord, Monad, etc.).

En Elm no existen type classes: sólo hay un conjunto fijo de restricciones (number, comparable, appendable).


Haskell es un lenguaje donde los tipos son una herramienta de abstracción.

Elm los usa más bien como una herramienta de seguridad.


Tanto Haskell como Elm son lenguajes funcionales puros: ninguna función puede tener efectos secundarios sin declararlo explícitamente.

Pero los abordan de forma distinta.


Haskell utiliza el sistema de monads para modelar efectos: IO, Maybe, State, etc.

Cada efecto se encapsula en un tipo, y se encadenan usando do notation.


main :: IO ()

main = do

    name <- getLine

    putStrLn ("Hola, " ++ name)


Elm evita completamente las monads.

En su lugar, usa un modelo explícito de efectos: los Commands (Cmd) y Subscriptions (Sub), que forman parte del Elm Architecture.

Esto mantiene la pureza del lenguaje sin exponer al programador a conceptos teóricos complejos.


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

    case msg of

        CargarDatos ->

            ( model, Http.get {...} )


        DatosCargados datos ->

            ( { model | items = datos }, Cmd.none )


Haskell ofrece poder; Elm ofrece control.

En Haskell, el manejo de efectos es flexible y extensible.

En Elm, es seguro y predecible.


Elm está diseñado para ser simple y consistente.

La sintaxis es limpia, sin operadores ambiguos ni extensiones opcionales.

Haskell, en cambio, permite una gran expresividad, pero también puede resultar críptico.


En Elm:


sumar : Int -> Int -> Int

sumar x y =

    x + y


En Haskell:


sumar :: Int -> Int -> Int

sumar x y = x + y


Parecen casi iguales, pero Haskell permite redefinir operadores, crear infix personalizados o usar point-free style, lo que puede aumentar la complejidad.

Elm evita deliberadamente esa flexibilidad para mantener el código legible para todos.


Haskell es un lenguaje generalista: se usa en compiladores, sistemas financieros, backends web, análisis estático y más.

Su ecosistema es vasto y diverso, aunque muchas librerías varían en calidad y mantenimiento.


Elm se centra exclusivamente en el frontend web.

Todo su diseño gira en torno a construir aplicaciones en el navegador.

No hay ambigüedad: un proyecto Elm siempre es una aplicación web.

A cambio de esa limitación, ofrece una experiencia coherente, con un compilador extremadamente útil y mensajes de error ejemplares.


La diferencia más grande entre ambos lenguajes quizá sea emocional. Haskell a veces puede parecer un rompecabezas: poderoso, elegante, pero con una curva de aprendizaje pronunciada.

Elm, en cambio, busca que programar sea agradable, incluso para quienes no tienen experiencia previa en programación funcional.

El compilador de Elm no sólo te dice qué está mal, sino cómo arreglarlo.

El de Haskell, aunque más sofisticado, puede ser más críptico si no conocés sus fundamentos teóricos.


Haskell y Elm son dos lenguajes que muestran dos filosofías complementarias del mundo funcional.

Haskell te da un universo para explorar la abstracción.

Elm te da un terreno firme donde construir sin errores.

martes, 11 de noviembre de 2025

Type constraints en Elm


Elm es un lenguaje de tipado estático e inferencia fuerte: el compilador deduce los tipos automáticamente, pero también te permite declarar funciones genéricas que funcionan con más de un tipo.

Por ejemplo:


identity : a -> a

identity x = x


Esta función acepta cualquier tipo a.

Sin embargo, a veces queremos restringir qué tipos son válidos.

Ahí entran en juego los type constraints (restricciones de tipo).

En Elm, los type constraints permiten decir:

> “Este tipo genérico debe cumplir con ciertas propiedades (por ejemplo, ser comparable o numérico)”.


A diferencia de Haskell o Scala, Elm no tiene type classes, pero ofrece un pequeño conjunto de restricciones integradas que cubren los casos más comunes.

Elm define cuatro categorías de tipos con restricciones que podés usar en tus firmas de tipo:

  • number: Tipos que soportan operaciones aritméticas como Int, Float
  • comparable: Tipos que pueden ordenarse o compararse  como Int, Float, Char, String, tuples de comparables
  • appendable: Tipos que pueden concatenarse como String, List a
  • compappend:| Tipos que son a la vez comparable y appendable 


Podés restringir una función a operar solo sobre números:


sumar : number -> number -> number

sumar x y =

    x + y


Esto funciona con Int o Float, pero no con String.


Si necesitás ordenar o comparar valores:


menor : comparable -> comparable -> comparable

menor a b =

    if a < b then

        a

    else

        b


O incluso:


ordenar : List comparable -> List comparable

ordenar lista =

    List.sort lista


Cuando querés concatenar elementos:


concatenar : appendable -> appendable -> appendable

concatenar a b =

    a ++ b


Funciona con:


concatenar "Hola, " "mundo!"        -- "Hola, mundo!"

concatenar [1,2] [3,4]              -- [1,2,3,4]


En Elm no se pueden definir tipos personalizados que sean comparable o appendable.

Por ejemplo, este tipo:


type alias Persona =

    { nombre : String, edad : Int }


No puede usarse en una función List.sort directamente.

Pero podés ordenarlo con una clave usando List.sortBy:


ordenarPorEdad : List Persona -> List Persona

ordenarPorEdad personas =

    List.sortBy .edad personas


O definir un criterio personalizado:


ordenarPorNombreDesc : List Persona -> List Persona

ordenarPorNombreDesc personas =

    List.sortWith (\a b -> compare b.nombre a.nombre) personas


Elm mantiene su sistema de tipos simple pero poderoso: no hay typeclasses ni herencia, pero sí restricciones útiles y seguras para los casos más comunes.


domingo, 9 de noviembre de 2025

¿Qué significa new en los métodos de C# ?


Cuando vemos la palabra clave new en C#, lo primero que pensamos es en crear objetos:


var persona = new Persona();


Pero cuando aparece en la declaración de un método, como:


public new static void Saludar() { ... }



…muchos se quedan con cara de 🤔.

Porque en ese contexto, new no crea nada.

Su función es ocultar un método heredado de la clase base.

Veamos qué significa y cuándo conviene usarlo.

En C#, cuando una clase hereda de otra, también hereda sus métodos.

Pero si en la clase hija declarás un método con el mismo nombre y firma, el compilador te lanza una advertencia:


> ⚠️ “El miembro X oculta el miembro heredado Y. Usá la palabra clave new si la ocultación es intencional.”


En ese caso, el compilador te está avisando:

“Estás tapando un método que ya existía en la clase base.”


Y si realmente querés hacerlo, usás el modificador new para decirle al compilador que es intencional.


Veamos un ejemplo: 


class Base

{

    public static void Saludar()

    {

        Console.WriteLine("Hola desde Base");

    }

}


class Derivada : Base

{

    public new static void Saludar()

    {

        Console.WriteLine("Hola desde Derivada");

    }

}


class Program

{

    static void Main()

    {

        Base.Saludar();     // Hola desde Base

        Derivada.Saludar(); // Hola desde Derivada

    }

}


Lo importante:


Derivada.Saludar() oculta el método Base.Saludar().

No hay “sobrescritura” real, porque los métodos estáticos no se pueden sobreescribir.


La palabra clave override sí sobrescribe un método de la clase base, pero solo se puede usar con métodos virtuales o abstractos.


Veamos la diferencia:


class Base

{

    public virtual void Hablar() => Console.WriteLine("Base habla");

}


class Derivada : Base

{

    public override void Hablar() => Console.WriteLine("Derivada habla");

}


Y si en lugar de override usás new:


class Derivada : Base

{

    public new void Hablar() => Console.WriteLine("Derivada habla (ocultando)");

}


Entonces ocurre esto:


Base b = new Derivada();

b.Hablar();

             // Si usás override → Derivada habla

             // Si usás new → Base habla


En otras palabras:

  • override reemplaza el comportamiento de la base.
  • new simplemente lo oculta (pero el de la base sigue existiendo y puede usarse).


Casos comunes donde aparece new


1. En métodos estáticos, donde override no está permitido:


   public new static void Mostrar() => Console.WriteLine("Versión nueva");


2. Para redefinir métodos heredados no virtuales (por ejemplo, ToString() o Equals() de object, aunque no es habitual usar `new` allí).

3. Cuando querés evitar advertencias del compilador si tu método accidentalmente tiene el mismo nombre que uno de la base.


El modificador new en un método no tiene nada que ver con crear objetos.

Sirve para decirle al compilador:


> “Sí, sé que hay un método igual en la clase base, pero quiero ocultarlo y definir el mío propio.”


Sutil, pero importante:

no estás reemplazando comportamiento, estás escondiendo uno.



domingo, 2 de noviembre de 2025

Estado del Octoverso

Github publicó el informe anual Octoverse, en el cual se pueden ver datos interesantes de los proyectos. El informe es grande y tiene muchos detalles.

En especial me intereso los lenguajes más utilizados:


Pero a partir de 2025, la curva de crecimiento de Python comenzó a seguir una trayectoria casi idéntica en paralelo con JavaScript y TypeScript, lo que sugiere que la adopción de la IA está influyendo en la elección del lenguaje en estos ecosistemas.




Dejo link: https://octoverse.github.com/

¿Qué es la Inteligencia Artificial Generativa?


En los últimos años, la Inteligencia Artificial Generativa (o Generative AI) pasó de ser un concepto académico a convertirse en una tecnología que usamos todos los días. Herramientas como ChatGPT, DALL·E o Gemini son ejemplos de cómo las máquinas no solo procesan información, sino que ahora también crean contenido nuevo: textos, imágenes, código, música o incluso videos.

Pero ¿qué significa exactamente que una inteligencia artificial sea “generativa”?

Y más importante: ¿cómo logra generar algo que parece hecho por una persona?

Tradicionalmente, la Inteligencia Artificial se enfocaba en reconocer patrones: identificar un objeto en una foto, predecir el precio de una casa o clasificar un correo como spam.

Es decir, la IA analizaba datos para tomar decisiones o realizar predicciones.

La IA generativa, en cambio, usa los mismos principios de aprendizaje automático, pero con un objetivo distinto: crear nuevas muestras que se parezcan a los datos con los que fue entrenada.


Por ejemplo:

  • Si aprende del texto de millones de libros, puede escribir frases coherentes y originales.
  • Si aprende de imágenes, puede dibujar nuevas combinaciones visuales.
  • Si aprende de sonidos, puede componer melodías o voces humanas.


No “copia”, sino que aprende patrones estadísticos del lenguaje, la imagen o el sonido, y luego los usa para generar algo nuevo dentro de esos patrones.

Todo comienza con el entrenamiento. Un modelo generativo se alimenta con grandes cantidades de datos: textos, fotos, grabaciones, código fuente, etc.

Durante este proceso, el modelo aprende cómo se estructura ese contenido, encontrando relaciones, estilos y secuencias probables.

En el caso de los modelos de texto (como ChatGPT), el principio es simple pero poderoso:

> “Dado un conjunto de palabras, predecir cuál es la palabra más probable que sigue.”


Repitiendo ese proceso miles de millones de veces, el modelo aprende las reglas implícitas del lenguaje: gramática, contexto, tono, coherencia.

Eso le permite luego generar textos originales, sin necesidad de tener una “base de datos” de frases guardadas.

La mayoría de los sistemas generativos modernos se basan en una arquitectura llamada transformer, que revolucionó la forma en que las máquinas procesan secuencias como el lenguaje.

Los transformers permiten entender el contexto y generar contenido coherente a lo largo de párrafos o incluso conversaciones completas.

Estos modelos, cuando alcanzan un tamaño y entrenamiento suficientes, se denominan LLM (Large Language Models), y son la base de las IAs conversacionales actuales.

La IA generativa no solo automatiza tareas: amplía la creatividad humana. Permite a programadores escribir código más rápido, a artistas explorar nuevos estilos, y a científicos analizar datos con una comprensión semántica mucho más rica.

Su impacto se siente en educación, diseño, comunicación y desarrollo de software.

Y lo más interesante es que todavía estamos viendo solo el principio.

sábado, 1 de noviembre de 2025

Diseñando interfaces declarativas con Elm UI


Elm es conocido por su modelo funcional puro y su arquitectura confiable (el Elm Architecture).

Sin embargo, muchos desarrolladores se frustran al escribir HTML y CSS en el código de Elm tradicional.

Ahí entra Elm UI, una librería creada por Matthew Griffith que propone algo radical:

“Construir interfaces sin HTML ni CSS.”

mdgriffith/elm-ui reemplaza los módulos Html y Html.Attributes por un conjunto de tipos y funciones que representan elementos visuales y propiedades de diseño.

En vez de mezclar markup y estilo, describís la interfaz de forma estructurada y tipada.


Por ejemplo:


import Element exposing (..)

import Element.Font as Font


main =

    layout [] <|

        column [ spacing 20, centerX, centerY ]

            [ el [ Font.bold ] (text "Hola Elm UI!")

            , el [] (text "Una forma diferente de pensar las interfaces.")

            ]


Este código produce una interfaz centrada con dos textos.

Sin CSS, sin HTML — todo se define a través de funciones puras.


Elm UI se basa en unos pocos conceptos muy consistentes:

Element: Unidad visual principal, equivalente a un “nodo” del DOM

layout: Punto de entrada para renderizar la interfaz 

row, column: Contenedores para alinear elementos horizontal o verticalmente 

el: Envuelve un elemento simple (por ejemplo, texto o imagen) 

text: Muestra texto

Attribute msg: Propiedad de estilo o comportamiento (como `spacing`, `padding`, `centerX`, `Font.size`, etc.)


Veamos un ejemplo más completo: una pequeña tarjeta de usuario.


import Element exposing (..)

import Element.Background as Background

import Element.Border as Border

import Element.Font as Font


viewUser : Element msg

viewUser =

    el

        [ Background.color (rgb255 240 240 255)

        , Border.rounded 12

        , padding 16

        , width (px 250)

        , centerX

        ]

    <|

        column [ spacing 8 ]

            [ el [ Font.bold, Font.size 20 ] (text "Emanuel Goette")

            , el [ Font.color (rgb255 100 100 100) ] (text "Desarrollador de software")

            , el [] (text "🌎 Argentina")

            ]


Y en el main:


main =

    layout [] viewUser


El resultado: una tarjeta limpia, centrada, con esquinas redondeadas y colores suaves.

Todo expresado como funciones y tipos, sin estilos externos.


Elm UI funciona perfectamente dentro del patrón clásico Model, Update, View.

Ejemplo simple:


import Browser

import Element exposing (..)

import Element.Font as Font


type alias Model =

    { counter : Int }


init : Model

init =

    { counter = 0 }


type Msg

    = Increment

    | Decrement


update msg model =

    case msg of

        Increment ->

            { model | counter = model.counter + 1 }


        Decrement ->

            { model | counter = model.counter - 1 }


view model =

    layout []

        (column [ spacing 16, centerX, centerY ]

            [ el [ Font.size 24 ] (text ("Contador: " ++ String.fromInt model.counter))

            , row [ spacing 10 ]

                [ button "-" Decrement

                , button "+" Increment

                ]

            ]

        )


button label msg =

    el

        [ Background.color (rgb255 100 149 237)

        , Font.color (rgb255 255 255 255)

        , Border.rounded 6

        , paddingXY 16 8

        , mouseDown [ onClick msg ]

        ]

        (text label)


main =

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


Un pequeño contador con estilo nativo y sin HTML.


Elm UI lleva la programación declarativa al diseño visual.

En lugar de pensar en “cómo” maquetar una interfaz, pensás en qué representa cada parte de ella.

“Elm UI te obliga a pensar en tu interfaz como un modelo de datos, no como una plantilla.”

Ideal para quienes buscan precisión, seguridad y coherencia en sus diseños.


Dejo link: 

https://package.elm-lang.org/packages/mdgriffith/elm-ui/latest/

https://ellie-app.com



viernes, 31 de octubre de 2025

Lamdera: el poder de Elm en el frontend y el backend


Elm es conocido por su modelo funcional puro, su tipado fuerte y la ausencia de errores en tiempo de ejecución.

Pero históricamente ha tenido una gran limitación: no puede ejecutarse en el backend.

Ahí entra Lamdera, un lenguaje derivado de Elm que propone algo ambicioso:

“Desarrollar aplicaciones fullstack en Elm, con estado compartido entre cliente y servidor, sin escribir ni una línea de JavaScript ni HTTP.”

Lamdera es un lenguaje y un entorno de ejecución creado por James Carlson, completamente compatible con Elm, pero que agrega:

  • Servidor integrado (no hace falta backend separado)
  • Comunicación automática cliente-servidor
  • Persistencia de estado
  • Despliegue con un solo comando


En otras palabras, Lamdera hace que Elm sea fullstack.

El compilador transforma tu código en una aplicación que corre tanto del lado del cliente como del servidor, sin necesidad de gestionar APIs, JSON o sincronización manual.

Lamdera extiende The Elm Architecture (TEA), pero con dos bucles de actualización:

uno en el cliente y otro en el servidor.

Ambos comparten el mismo modelo (Model) y el mismo tipo de mensaje (Msg), por lo que el tipado es consistente en toda la aplicación.

Un proyecto Lamdera se organiza con tres módulos principales:

  • Main.elm
  • Client.elm
  • Server.elm
Main.elm : Define los tipos compartidos entre cliente y servidor:


module Main exposing (..)


import Lamdera exposing (ClientProgram)

import Shared exposing (..)

import Client

import Server


main : ClientProgram Shared.Model Shared.Msg Shared.ToServer Shared.ToClient

main =

    Lamdera.client

        { init = Client.init

        , update = Client.update

        , view = Client.view

        , subscriptions = Client.subscriptions

        , onServerMsg = Client.onServerMsg

        , onClientConnect = Client.onClientConnect

        , onClientDisconnect = Client.onClientDisconnect

        }


Contiene los tipos comunes:


module Shared exposing (..)


type alias Model =

    { counter : Int }


type Msg

    = Increment

    | Decrement


type ToServer

    = SaveCounter Int


type ToClient

    = CounterUpdated Int


Client.elm : Maneja la vista y la interacción del usuario:


module Client exposing (..)


import Lamdera exposing (sendToServer)

import Element exposing (..)

import Shared exposing (..)


init : (Model, Cmd Msg)

init =

    ({ counter = 0 }, Cmd.none)


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

    case msg of

        Increment ->

            let

                new = model.counter + 1

            in

            ( { model | counter = new }

            , sendToServer (SaveCounter new)

            )


        Decrement ->

            let

                new = model.counter - 1

            in

            ( { model | counter = new }

            , sendToServer (SaveCounter new)

            )


Server.elm: Contiene la lógica del servidor (persistencia, validación, etc.):


module Server exposing (..)


import Lamdera exposing (sendToClient)

import Shared exposing (..)


update : ToServer -> Model -> (Model, Cmd Msg)

update msg model =

    case msg of

        SaveCounter value ->

            ( { model | counter = value }

            , sendToClient (CounterUpdated value)

            )


Con esto ya tenés una aplicación fullstack con comunicación cliente-servidor y estado sincronizado, todo en Elm.


Lamdera incluye herramientas propias:

  • lamdera run → ejecuta el servidor local y abre el navegador
  • lamdera check → analiza tipos y compilación
  • lamdera deploy → compila, sube y publica tu app
  • Dashboard online → permite gestionar el estado y logs


Lamdera todavía no es software libre (su runtime es cerrado, aunque gratuito para uso educativo y hobby).

Tampoco permite acceso directo a archivos o bases de datos externas (aunque se planea extender esto).

Aun así, su estabilidad es sorprendente: muchas aplicaciones en producción usan Lamdera desde hace años sin un solo error en runtime.

Lamdera lleva la promesa de Elm un paso más allá:

> “Sin errores, sin JavaScript, y ahora… sin backend.”


Con él, podés crear aplicaciones reactivas, tipadas y persistentes con una única base de código funcional pura.

Ideal para quienes aman Elm y quieren llevar sus principios hasta el servidor.


Dejo link: https://lamdera.com

https://lamdera.com/doc


miércoles, 29 de octubre de 2025

Diferencias entre const y static readonly en C#


En C# existen dos formas principales de definir valores constantes: const y static readonly.

A simple vista parecen similares, pero no son equivalentes. La diferencia entre ambos puede tener implicaciones importantes en rendimiento, versionado y comportamiento en tiempo de ejecución.

Cuando definimos un campo como const, su valor se conoce y se fija en tiempo de compilación.

Esto significa que el compilador reemplaza todas las referencias a esa constante por su valor literal.


public class MathUtils

{

    public const double Pi = 3.14159265359;

}


Si en otro proyecto escribimos:


Console.WriteLine(MathUtils.Pi);


El compilador incrusta el valor 3.14159265359 directamente en el código IL.

Por eso, si luego cambiamos Pi a 3.14 y recompilamos solo la librería, las aplicaciones que la usaban seguirán mostrando 3.14159265359, a menos que se recompilen también.

En resumen:

  • Resuelto en tiempo de compilación.
  • Solo admite tipos primitivos o string.
  • Se incrusta literalmente en el código.
  • No puede cambiar nunca.


Un campo static readonly se evalúa en tiempo de ejecución, no en compilación.

Su valor puede asignarse en la declaración o dentro del constructor estático de la clase.


public class Config

{

    public static readonly DateTime StartTime = DateTime.Now;

}


Aquí StartTime toma el valor actual al cargarse la clase, algo que sería imposible con const.

Otra diferencia clave: si modificamos el valor en el código fuente y recompilamos solo la librería, las aplicaciones que la referencian verán el cambio sin necesidad de recompilarse.

En resumen:

  • Evaluado en tiempo de ejecución.
  • Admite cualquier tipo (incluso objetos complejos).
  • No se incrusta en el código cliente.
  • Puede inicializarse en constructores estáticos.

Usá const cuando el valor sea verdaderamente inmutable y universal (como Math.PI, o un nombre de tabla en una base de datos).

Usá static readonly cuando el valor dependa de la ejecución o pueda cambiar sin recompilar los clientes.

martes, 28 de octubre de 2025

Unit Testing en Elm: confianza tipada desde el compilador hasta las pruebas


Elm ya es famoso por su compilador que evita errores en tiempo de ejecución, pero eso no significa que las pruebas no sean necesarias.

De hecho, los tests en Elm complementan su sistema de tipos, ayudándote a verificar la lógica de negocio, las transformaciones de datos y el comportamiento de tus funciones puras.

A diferencia de otros lenguajes donde los tests buscan prevenir errores de nulls, tipos o efectos secundarios, en Elm los tests sirven principalmente para:

  • Asegurar que una función devuelva el resultado esperado.
  • Comprobar que una actualización de modelo (en TEA) cambie el estado correctamente.
  • Validar transformaciones de datos o funciones puras de negocio.


Gracias a la pureza funcional, las pruebas en Elm son simples y predecibles.

El framework oficial de testing es elm-test.


Instalalo con:


npm install -g elm-test

elm-test init


Esto crea una carpeta:


tests/

 └── Example.elm


donde podrás escribir tus pruebas.


Supongamos que tenés una función en src/MathUtils.elm:


module MathUtils exposing (add)


add : Int -> Int -> Int

add a b =

    a + b


Podés crear un test en tests/MathUtilsTest.elm:


module MathUtilsTest exposing (tests)


import Expect

import Test exposing (..)

import MathUtils exposing (add)


tests : Test

tests =

    describe "Pruebas de MathUtils"

        [ test "Suma básica" <|

            \_ -> Expect.equal 4 (add 2 2)

        , test "Suma con negativos" <|

            \_ -> Expect.equal 0 (add 2 -2)

        ]


Ejecutalo con:

elm-test


y deberías ver algo como:


TEST RUN PASSED

Duration: 15 ms


Podés probar también las funciones update de tu arquitectura Elm.

Supongamos una aplicación simple que incrementa un contador:


type alias Model =

    { counter : Int }


type Msg

    = Increment


update : Msg -> Model -> Model

update msg model =

    case msg of

        Increment ->

            { model | counter = model.counter + 1 }


El test sería:


module CounterTest exposing (tests)


import Expect

import Test exposing (..)

import Main exposing (update, Model(..), Msg(..))


tests : Test

tests =

    describe "Update del contador"

        [ test "Incrementa el contador" <|

            \_ ->

                let

                    model = { counter = 0 }

                    updated = update Increment model

                in

                Expect.equal 1 updated.counter

        ]



Expect.notEqual – asegura que dos valores sean diferentes.

Expect.true / Expect.false – comprueba booleanos.

Expect.all – combina varios asserts sobre el mismo valor.

Fuzz tests – pruebas con datos aleatorios (muy útiles para funciones matemáticas o validaciones).


Ejemplo de fuzzing:


import Fuzz exposing (int)


fuzzTest : Test

fuzzTest =

    fuzz int "Suma con cero no cambia el número" <|

        \n -> Expect.equal n (add n 0)


El fuzzer genera cientos de casos aleatorios para asegurar que la propiedad siempre se cumple.

El sistema de tipos de Elm ya evita gran parte de los errores comunes, pero las pruebas unitarias llevan esa confianza un paso más allá. Te permiten documentar y verificar el comportamiento de tus funciones de forma declarativa, legible y segura.

domingo, 26 de octubre de 2025

Manejo de efectos en Elm: Tasks, Commands y Subscriptions


Elm es conocido por su promesa audaz: “sin errores en tiempo de ejecución”.

Pero, ¿cómo logra mantener esa pureza funcional incluso cuando necesita interactuar con el mundo real —hacer peticiones HTTP, leer el tiempo o escuchar eventos del navegador?

La respuesta está en su sistema de efectos controlados, manejados a través de tres conceptos clave:

  • Task
  • Cmd (Command)
  • Sub (Subscription)

En lenguajes imperativos, un efecto secundario (como imprimir en consola, hacer un fetch, o leer el reloj) puede suceder en cualquier parte del código.

En Elm, en cambio, toda función debe ser pura: dado el mismo input, siempre devuelve el mismo output.

Esto significa que no podés ejecutar efectos directamente dentro de tus funciones —en su lugar, los describís y Elm se encarga de ejecutarlos de forma controlada.

Un Cmd (Command) representa una acción que Elm debe realizar fuera del mundo puro, y que luego generará un mensaje (msg) cuando termine.


Ejemplo: hacer una solicitud HTTP.


import Http

import Json.Decode exposing (string)


type Msg

    = GotGreeting (Result Http.Error String)


fetchGreeting : Cmd Msg

fetchGreeting =

    Http.get

        { url = "https://api.example.com/hello"

        , expect = Http.expectString GotGreeting

        }


Este Cmd Msg no ejecuta la solicitud, solo le dice al runtime de Elm:

Por favor, hacé esta petición y cuando tengas el resultado, mandame un GotGreeting.

En tu update, procesás el resultado:


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

    case msg of

        GotGreeting (Ok text) ->

            ({ model | greeting = text }, Cmd.none)

        GotGreeting (Err _) ->

            ({ model | greeting = "Error al conectar" }, Cmd.none)


Task: trabajos que pueden fallar o devolver un valor

Un Task es una descripción más general de un efecto que puede producir un resultado o un error.


Por ejemplo, si quisieras obtener el tiempo:


import Task

import Time exposing (Posix)


type Msg

    = GotTime Posix


getTime : Cmd Msg

getTime =

    Task.perform GotTime Time.now


Task.perform convierte un Task en un Cmd, para que pueda ser ejecutado por Elm.


El flujo sería:

  1. Time.now devuelve un Task Never Posix (una tarea que no falla y produce un tiempo).
  2. Task.perform lo transforma en un Cmd Msg.
  3. Elm ejecuta el comando y manda GotTime cuando termina.


Sub msg: escuchar eventos externos

Mientras que Cmd representa acciones que Elm inicia,

Sub representa cosas que suceden fuera y Elm escucha.

Por ejemplo, escuchar el paso del tiempo:


subscriptions : Model -> Sub Msg

subscriptions model =

    Time.every 1000 Tick


Cada segundo, Elm enviará un mensaje Tick, que procesás en tu update:


type Msg

    = Tick Posix


update : Msg -> Model -> (Model, Cmd Msg)

update msg model =

    case msg of

        Tick time ->

            ({ model | currentTime = time }, Cmd.none)


Elm logra un equilibrio brillante:

  • mantiene la pureza funcional del lenguaje,
  • pero sin renunciar al mundo real.


Gracias a Task, Cmd y Sub, todo efecto está tipado, controlado y predecible, lo que permite que el compilador te ayude a manejar cada posible resultado.


Por eso, en Elm, incluso el caos del mundo exterior se maneja con elegancia funcional.


miércoles, 22 de octubre de 2025

Cómo tener varias líneas en un switch expression en C#


Desde C# 8, el lenguaje introdujo los switch expressions, una forma más concisa y expresiva de realizar asignaciones condicionales.

Pero hay una limitación importante, cada caso solo puede contener una expresión, no un bloque de varias sentencias.


Si intentás esto:


var result = value switch

{

    1 =>

    {

        Console.WriteLine("Caso 1");

        return "Uno";

    },

    _ => "Otro"

};


El compilador se queja: > *CS1525: Invalid expression term '{'*


Esto ocurre porque una switch expression espera una sola expresión por rama, no un bloque {}.

La pregunta es cómo resolverlo: 


Opción 1: Lambda invocada inmediatamente

Si necesitás ejecutar varias líneas y devolver un resultado, podés usar una lambda expression que se ejecuta al instante:


var result = value switch

{

    1 => (() =>

    {

        Console.WriteLine("Caso 1");

        var temp = 10 * 2;

        return $"Resultado: {temp}";

    })(),

    2 => "Dos",

    _ => "Otro"

};


El truco está en (() => { ... })(): una función anónima definida y ejecutada inmediatamente.


Opción 2: Método auxiliar

Cuando el bloque es más grande o se usa varias veces, conviene delegar la lógica a un método:


var result = value switch

{

    1 => CalcularCasoUno(),

    2 => "Dos",

    _ => "Otro"

};


string CalcularCasoUno()

{

    Console.WriteLine("Caso 1");

    var temp = 10 * 2;

    return $"Resultado: {temp}";

}


Más limpio, legible y fácil de testear.


Los switch expressions son ideales para lógica declarativa y corta, pero cuando necesitás más pasos, la solución está en envolver el bloque dentro de una lambda o extraerlo a un método auxiliar.

viernes, 17 de octubre de 2025

Queue y Deque en Java


En el Java Collections Framework, las interfaces Queue<E> y Deque<E> representan estructuras de datos diseñadas para manejar elementos de forma ordenada y disciplinada, siguiendo patrones clásicos como FIFO (First-In, First-Out) o LIFO (Last-In, First-Out).

Aunque en muchos casos usamos List<E> para mantener una secuencia, las colas ofrecen operaciones especializadas que reflejan su propósito: procesar elementos en un orden determinado.

Queue<E>: la cola clásica

Una Queue (cola) sigue la política FIFO: el primer elemento en entrar es el primero en salir.

Entre los métodos principales tenemos:

  • add(E e): Inserta un elemento, lanza excepción si no hay espacio. 
  • offer(E e): Inserta un elemento, devuelve false si falla (no lanza excepción). 
  • remove(): Elimina y devuelve el primer elemento, lanza excepción si está vacía.
  • poll(): Elimina y devuelve el primer elemento, devuelve null si está vacía.
  • element(): Devuelve el primer elemento sin eliminarlo, lanza excepción si está vacía.
  • peek(): Devuelve el primer elemento sin eliminarlo, o null si está vacía.  

Usá offer, poll y peek cuando no quieras manejar excepciones, y add, remove, element si esperás que siempre haya elementos.

Veamos un ejemplo con LinkedList: LinkedList implementa Queue, así que se puede usar como una cola:


import java.util.*;


public class EjemploQueue {

    public static void main(String[] args) {

        Queue<String> cola = new LinkedList<>();


        cola.offer("A");

        cola.offer("B");

        cola.offer("C");


        System.out.println("Frente de la cola: " + cola.peek());


        while (!cola.isEmpty()) {

            System.out.println("Atendiendo: " + cola.poll());

        }

    }

}


Salida:

Frente de la cola: A

Atendiendo: A

Atendiendo: B

Atendiendo: C



Aquí los elementos se procesan en el mismo orden en que fueron agregados.

Implementaciones comunes de Queue:

  • LinkedList: Cola basada en lista enlazada (también implementa Deque). 
  • PriorityQueue: Cola que ordena sus elementos según su prioridad o un comparador.
  • ArrayDeque: Implementación eficiente basada en array.
  • ConcurrentLinkedQueue: Versión no bloqueante, segura para hilos.
  • BlockingQueue: Interfaz extendida con operaciones que bloquean el hilo (en java.util.concurrent).


Deque<E>: doble cola


Deque (Double-Ended Queue) es una cola doble, permite insertar y eliminar elementos tanto por el frente como por el final.


Veamos un ejemplo con ArrayDeque:


import java.util.*;


public class EjemploDeque {

    public static void main(String[] args) {

        Deque<String> deque = new ArrayDeque<>();


        deque.addFirst("A");

        deque.addLast("B");

        deque.addLast("C");

        deque.addFirst("Inicio");


        System.out.println("Primero: " + deque.peekFirst());

        System.out.println("Último: " + deque.peekLast());


        while (!deque.isEmpty()) {

            System.out.println("Procesando: " + deque.pollFirst());

        }

    }

}


Salida:

Primero: Inicio

Último: C

Procesando: Inicio

Procesando: A

Procesando: B

Procesando: C


Antes de Java 1.6 se usaba la clase Stack, pero hoy se recomienda usar Deque como pila:


Deque<Integer> pila = new ArrayDeque<>();


pila.push(10); // addFirst

pila.push(20);

pila.push(30);


while (!pila.isEmpty()) {

    System.out.println("Sacando: " + pila.pop());

}


Salida:

Sacando: 30

Sacando: 20

Sacando: 10


Aquí la Deque actúa como una pila LIFO (Last-In, First-Out).


Las interfaces Queue<E> y Deque<E> amplían el poder del Java Collections Framework al modelar estructuras de datos de procesamiento secuencial, ideales para tareas como:

  • Procesamiento en colas de mensajes
  • Modelos productor-consumidor
  • Pilas o buffers circulares
  • Algoritmos de recorrido (como BFS)


miércoles, 15 de octubre de 2025

La Encuesta para Desarrolladores 2025 de stackoverflow


Stackoverflow hizo su tradicional encuesta y dijo esto: 

"La Encuesta para Desarrolladores 2025 es el informe definitivo sobre el estado del desarrollo de software. En su decimoquinta edición, Stack Overflow recibió más de 49 000 respuestas de 177 países en 62 preguntas centradas en 314 tecnologías diferentes, incluyendo un nuevo enfoque en herramientas de agentes de IA, LLM y plataformas comunitarias. Esta Encuesta anual para Desarrolladores ofrece una visión general crucial de las necesidades de la comunidad global de desarrolladores, centrándose en las herramientas y tecnologías que utilizan o sobre las que desean aprender más."


Dejo link:

https://survey.stackoverflow.co/2025/

martes, 14 de octubre de 2025

Set en Elm: conjuntos funcionales, simples y seguros


En Elm, un Set representa un conjunto de valores únicos y ordenados, sin repeticiones y con operaciones típicas de teoría de conjuntos: unión, intersección, diferencia, etc.


Un Set se define como:


Set comparable


comparable: el tipo de los valores que puede contener (por ejemplo, Int, String, Char, etc.).


Todos los valores deben ser comparables, es decir, Elm debe poder ordenarlos.

Esto significa que no podés usar listas, records o funciones dentro de un Set.

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


import Set exposing (Set)


-- Conjunto vacío

numeros : Set Int

numeros = Set.empty


-- Desde una lista

pares : Set Int

pares = Set.fromList [2, 4, 6, 8, 10]


Los duplicados se eliminan automáticamente:


Set.fromList [1, 2, 2, 3]

-- Resultado: {1, 2, 3}


Set.member 4 pares

-- True


Set.member 5 pares

-- False


-- Agregar un elemento

paresActualizados =

    Set.insert 12 pares


-- Eliminar un elemento

paresFiltrados =

    Set.remove 8 paresActualizados


Recordá: todo es inmutable. Cada operación devuelve un nuevo Set, sin modificar el original.

Podés convertir un Set a lista:


Set.toList pares

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


Y viceversa:


Set.fromList [3,1,2]

-- {1,2,3}


Para aplicar transformaciones, primero lo convertís a lista y luego de vuelta:


paresDoblados =

    Set.fromList (List.map (\x -> x * 2) (Set.toList pares))


Elm ofrece las funciones clásicas de teoría de conjuntos:


Set.union: Unión de dos conjuntos

Set.union (Set.fromList [1,2]) (Set.fromList [2,3])  --> {1,2,3}


Set.intersect: Intersección 

Set.intersect (Set.fromList [1,2,3]) (Set.fromList [2,3,4])  --> {2,3}


Set.diff: Diferencia

Set.diff (Set.fromList [1,2,3]) (Set.fromList [2])  --> {1,3}


Set.isEmpty:¿Está vacío?

Set.isEmpty Set.empty --> True


Set.size: Cantidad de elementos

Set.size (Set.fromList [1,2,3]) --> 3



Gracias al sistema de tipos de Elm:

  • No hay null ni valores inesperados.
  • No podés mezclar tipos diferentes dentro del mismo Set.
  • Todas las operaciones son puras e inmutables.


Por ejemplo:


-- Esto da error de compilación

Set.fromList [1, "dos", 3]




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.