Translate

domingo, 21 de diciembre de 2025

Métodos Monádicos en C++


Una mónada es un tipo que encapsula un valor junto con un contexto (por ejemplo, “puede fallar” o “puede faltar”).

Los métodos monádicos permiten operar dentro de ese contexto sin “salir” de él.

En C++:

std::optional<T> encapsula un valor o nada.

std::expected<T, E> encapsula un valor o un error.


Veamos los metodos: 

transform

Aplica una función al valor si existe, y devuelve un nuevo objeto del mismo tipo.


std::optional<int> x = 5;

auto y = x.transform([](int n){ return n * 2; }); // y = 10


Si x no tiene valor, transform no hace nada.


and_then

Encadena operaciones que también devuelven std::optional o std::expected.


auto divide = [](int n) -> std::optional<int> {

    return n == 0 ? std::nullopt : 100 / n;

};


std::optional<int> x = 5;

auto r = x.and_then(divide);  // aplica divide(5)


Si en cualquier paso hay std::nullopt, toda la cadena devuelve std::nullopt.


or_else

Permite especificar una acción alternativa si no hay valor.


std::optional<int> x = std::nullopt;

x.or_else([]{ return std::optional(42); }); // devuelve 42


Ideal para valores por defecto o recuperación de errores.


En std::expected<T, E>, los métodos monádicos también existen:

std::expected<int, std::string> parse(std::string s);


auto result = parse("42")

    .transform([](int x){ return x * 2; })

    .and_then([](int x) -> std::expected<int, std::string> {

        return x > 50 ? std::unexpected("too big") : x;

    })

    .or_else([](auto err){

        std::cerr << "Error: " << err << "\\n";

        return std::expected<int, std::string>(0);

    });


Así se evita usar try/catch o múltiples if para cada paso.


¿Y por qué tengo que usar estos métodos? 

  • Código más limpio y expresivo.
  • Sin necesidad de desempaquetar (.value() / .has_value()).
  • Naturalmente componible y seguro.
  • Permite escribir funciones sin excepciones pero con semántica clara de error.


Los métodos monádicos son una de las adiciones más elegantes del C++ moderno, te permiten escribir código más funcional, más seguro y más declarativo.


viernes, 19 de diciembre de 2025

std::optional vs std::variant vs std::expected en C++


El C++ moderno (C++17–C++23) nos ofrece varias formas seguras de representar alternativas o ausencia de valor, sin recurrir a punteros nulos o excepciones.

Tres herramientas destacan: std::optional, std::variant y std::expected. Aunque parecidas, resuelven problemas distintos.

std::optional<T> : Presencia o ausencia de un valor. Introducido en C++17, modela un valor que puede o no existir.


std::optional<int> findId(std::string name);


Si la búsqueda tiene éxito → return id;

Si no → return std::nullopt;


Ventajas:

  • Ligero y simple: ideal para “valor o nada”.
  • No necesita excepciones.
  • Soporta monadic ops (transform, and_then en C++23).


Cuándo no usarlo:

  • No distingue causas de error (solo “existe o no”).
  • No reemplaza un tipo con múltiples variantes.


std::variant<Ts...> — Uno de varios tipos posibles


También desde C++17, std::variant es un sum type que puede contener exactamente uno de los tipos listados.


std::variant<int, std::string> value = 10;


Podés inspeccionar su contenido con std::visit:


std::visit([](auto&& v) { std::cout << v; }, value);


Ventajas

  • Representa alternativas tipadas.
  • Útil cuando las opciones no son errores, sino formas distintas de un mismo dato.
  • Type-safe, reemplaza union.


Cuándo no usarlo:

  • No expresa “éxito o fallo” naturalmente.
  • Puede ser más complejo que un optional.


std::expected<T, E> — Resultado o error. Agregado oficialmente en C++23, inspirado en Rust y en la monada Either.


std::expected<int, std::string> parseInt(std::string s);


Si tuvo éxito → return 42;

Si falló → return std::unexpected("error");


Ventajas

  • Modelo explícito de éxito / error sin excepciones.
  • Evita throws y captura el por qué del fallo.
  • Monádico (transform, and_then, etc.).


Cuándo no usarlo

  • Cuando solo importa si hay o no valor (`std::optional` es más simple).
  • Si necesitás representar varias formas de datos no relacionadas (std::variant es mejor).


En resumen, std::optional modela la ausencia, std::variant modela alternativas, y std::expected modela el éxito o el error.

Juntas, son el núcleo de la programación expresiva y segura del C++ moderno.


std::variant en C++ — Unión segura y moderna


Desde C++17, std::variant forma parte de la STL como una alternativa segura y tipada a las uniones clásicas (union) y a jerarquías con herencia cuando solo necesitamos representar uno de varios tipos posibles.

std::variant<Ts...> es un tipo discriminado que puede contener exactamente uno de los tipos listados en Ts....


#include <variant>


std::variant<int, double, std::string> v;


En todo momento, v contiene un solo valor, y el compilador sabe qué tipos son válidos.

¿Por qué no usar union? Los union tradicionales:

  • No son type-safe
  • No manejan bien tipos no triviales
  • Requieren bookkeeping manual


std::variant soluciona esto:

  • Seguridad de tipos
  • Manejo correcto de constructores/destructores
  • Integración con la STL


std::variant<int, std::string> v1 = 10;

v1 = std::string("hola");


Si intentás asignar un tipo que no está en el variant, el código no compila.

Veamos como accedemos al valor: 


int x = std::get<int>(v1);


Lanza std::bad_variant_access si el tipo activo no coincide.


std::get_if<T> (forma segura)


if (auto p = std::get_if<std::string>(&v1)) {

    std::cout << *p;

}


Devuelve nullptr si el tipo no coincide.

Cómo saber qué tipo está activo


if (v1.index() == 0) {

    // es int

}


Usar index() suele ser menos expresivo que std::visit.

std::visit permite aplicar una función al valor contenido, sin saber su tipo concreto.


std::visit([](auto&& value) {

    std::cout << value;

}, v1);


El compilador genera una implementación segura para cada alternativa.


Overload pattern (muy usado)


template<class... Ts>

struct overloaded : Ts... {

    using Ts::operator()...;

};


template<class... Ts>

overloaded(Ts...) -> overloaded<Ts...>;


std::visit(overloaded{

    [](int i) { std::cout << "int: " << i; },

    [](const std::string& s) { std::cout << "string: " << s; }

}, v1);


Si una excepción ocurre durante una asignación:


if (v1.valueless_by_exception()) {

    // estado inválido

}


Es raro, pero importante en código robusto.


  • variant evita jerarquías artificiales
  • No necesita `virtual`
  • Más eficiente en muchos casos


Veamos el caso de uso típico: 


using Result = std::variant<int, std::string>;


Result parse(const std::string& s) {

    if (s.empty()) return "error";

    return std::stoi(s);

}


En C++26 o 29 contaremos con pattern matching, hoy tenemos que hacer :


std::visit(overloaded{

    [](int i) { /* ... */ },

    [](std::string s) { /* ... */ }

}, v);


Futuro (propuesto):


v match {

    int i        => /* ... */,

    std::string s => /* ... */

};


std::variant es la base natural para el pattern matching moderno.

  • std::variant es la unión segura del C++ moderno
  • Reduce errores de diseño
  • Elimina jerarquías innecesarias
  • Es clave para código expresivo y funcional


Si usás C++17 o superior, debería ser tu primera opción cuando necesitás representar una de varias alternativas.


miércoles, 17 de diciembre de 2025

Consumir una API REST desde Elm


La idea es que simplemente pintemos "hola mundo" con él consumiendo una API Rest: 


module Main exposing (..)


import Browser

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

import Html.Events exposing (onClick)

import Http

import Json.Decode exposing (Decoder, field, string)



-- MODEL


type alias Model =

    { message : String

    , loading : Bool

    }


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

init _ =

    ( { message = "", loading = False }, Cmd.none )


-- UPDATE


type Msg

    = FetchMessage

    | GotMessage (Result Http.Error String)



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

update msg model =

    case msg of

        FetchMessage ->

            ( { model | loading = True }

            , getMessage

            )


        GotMessage (Ok message) ->

            ( { model | message = message, loading = False }, Cmd.none )


        GotMessage (Err _) ->

            ( { model | message = "Error fetching message", loading = False }, Cmd.none )




-- HTTP REQUEST


getMessage : Cmd Msg

getMessage =

    Http.get

        { url = "http://localhost:8080/hello"

        , expect = Http.expectString GotMessage

        }


-- VIEW


view : Model -> Html Msg

view model =

    div []

        [ button [ onClick FetchMessage ] [ text "Fetch Message" ]

        , div []

            [ text

                (if model.loading then

                    "Loading..."

                 else

                    model.message

                )

            ]

        ]




-- MAIN


main : Program () Model Msg

main =

    Browser.element

        { init = init

        , update = update

        , subscriptions = always Sub.none

        , view = view

        }


  • El modelo (Model) guarda el mensaje recibido y un flag de carga (loading).
  • Al hacer clic en el botón, se dispara el mensaje FetchMessage, que a su vez ejecuta getMessage.
  • Http.get realiza una petición a la URL http://localhost:8080/hello.
  • Si la respuesta es exitosa, el mensaje GotMessage (Ok ...) actualiza el modelo con el texto devuelto por el servidor.
  • La vista muestra un botón y el resultado de la petición (o un “Loading...” mientras espera).


Podés usar cualquier backend —por ejemplo, un mini servidor Node.js o Express:


// server.js

import express from "express";

const app = express();


app.get("/hello", (req, res) => {

  res.send("Hello from the API!");

});


app.listen(8080, () => console.log("API running on http://localhost:8080/hello"));


lunes, 15 de diciembre de 2025

Pattern Matching en C++26 — ¿Qué es y cómo cambiará tu código?

 


C++ ha sido históricamente rico en herramientas de selección de flujo (if, switch, visitantes sobre std::variant, etc.), pero carece de una estructura nativa y unificada de pattern matching como la que vemos en Rust, Haskell o Swift. C++26 apunta (a través de la propuesta P2688R5) a llenar ese vacío. 

En ciencias de la computación, pattern matching es una forma declarativa de comparar una estructura de datos con uno o más patrones y —si coincide— ejecutar código asociado, extraer valores de forma segura y reducir mucho el boilerplate de código de control. 


Si alguna vez usaste pattern matching en lenguajes funcionales o en Rust:


match value {

    Some(x) => println!("Tiene valor: {}", x),

    None    => println!("No tiene valor"),

}


C++ tiene herramientas poderosas (como std::variant + std::visit), pero:

  • No existe un constructo nativo para comparar y destructurar tipos de forma concisa.
  • El tradicional switch solo funciona con valores integrales y carece de destructuración o bindings.


Pattern matching permite cosas como:

  • Comprobar la forma estructural de un valor.
  • Extraer datos de una std::tuple, std::variant, o tipos compuestos.
  • Reducir código repetitivo en código de control complejo.


Esto hace que tu código sea más claro, seguro y mantenible. 

¿Pattern Matching estará en C++26? Todavía no es definitivo.

El estándar C++26 aún está en borrador y pattern matching aún está en discusión dentro del comité WG21. La propuesta principal —P2688R5— introduce un nuevo constructo match muy similar a lo que otros lenguajes usan. 

Algunos desarrolladores creen que puede entrar en C++26 si todo marcha rápido. 

Otros opinan que podría retrasarse hasta C++29 debido a temas de diseño/sintaxis. 


Desde la propuesta P2688, se perfila un enfoque parecido a:


if (expr match [0, let foo]) {

    // foo está ligado a algo útil aquí

}


expr match -> result_type {

    pattern1 => /* acción 1 */,

    pattern2 => /* acción 2 */,

    _        => /* caso por defecto */

}


Aquí, match intenta casar expr con cada patrón.

let introduce variable(s) extraídas.

La sintaxis con => recuerda a Rust/Haskell, pero adaptada a C++ con sus propias reglas. 


Esta sintaxis aún no es parte definitiva del estándar — puede cambiar hasta la ratificación final.


miércoles, 10 de diciembre de 2025

Inicialización uniforme en C++


Desde C++11, C++ incorporó la inicialización uniforme, una forma unificada de inicializar variables, objetos y contenedores usando llaves {}.

Veamos un ejemplo: 


#include <iostream>

#include <vector>


int main() {

    int x{10};                     // variable

    std::vector<int> v{1, 2, 3, 4}; // lista de inicialización


    for (int n : v)

        std::cout << n << " ";

}


Salida:

1 2 3 4


Ventajas:

  • Evita conversiones implícitas peligrosas.
  • Funciona con cualquier tipo de dato.
  • Es más coherente y clara que las formas anteriores (=, ()).


Por ejemplo, esto no compila porque perdería precisión:


int n{3.5}; // error: pérdida de información


Mientras que con = sí lo haría (truncando el valor).

Por eso, la inicialización uniforme hace el código más seguro y predecible.


jueves, 4 de diciembre de 2025

Desafíos, límites y futuro de la IA generativa


Aunque las inteligencias artificiales generativas parecen “mágicas”, están lejos de ser perfectas.

Entender sus limitaciones, riesgos y posibilidades es clave para usarlas de forma responsable y aprovechar su verdadero potencial.

Uno de los mayores desafíos de los modelos generativos es que pueden inventar información con total confianza. Esto se conoce como una alucinación.

Por ejemplo, un modelo puede afirmar que un autor escribió un libro inexistente, o que una función de Python se llama de una forma incorrecta.


¿Por qué ocurre? Porque los modelos no “saben” cosas; predicen patrones.

Si un conjunto de palabras “suena correcto” según su entrenamiento, lo dirán, aunque no sea verdad.

Los modelos no distinguen entre lo probable y lo verdadero.

Por eso, cada vez más investigaciones buscan reducir las alucinaciones mediante:

  • integración con bases de conocimiento verificables,
  • modelos híbridos (razonamiento simbólico + redes neuronales),
  • y retroalimentación constante con información actualizada.


Los modelos aprenden del lenguaje humano… y el lenguaje humano está lleno de sesgos: culturales, políticos, de género, raciales, etc.

Por eso, un modelo puede reflejar o amplificar esos sesgos si no se controla cuidadosamente.

Las empresas e instituciones que desarrollan IA trabajan con:

  • equipos de ética y auditorías externas,
  • filtrado de datos para reducir prejuicios,
  • y técnicas como RLHF para mejorar la alineación con valores humanos.


Aun así, es un problema abierto:

> ¿cómo definimos lo que es “ético” o “correcto” en contextos culturales tan distintos?


Otro debate importante gira en torno a de dónde provienen los datos de entrenamiento.

Muchos modelos se entrenaron con grandes cantidades de texto de Internet, lo que plantea preguntas como:

  • ¿Quién es dueño del contenido generado?
  • ¿Puede un modelo “aprender” de obras con copyright?
  • ¿Qué pasa si memoriza datos sensibles?


Por eso, surgen nuevos enfoques:

  • entrenamientos con datos privados o sintéticos,
  • modelos open source auditables,
  • y regulaciones en camino, como la AI Act en la Unión Europea.


Entrenar un modelo generativo grande puede costar millones de dólares y consumir enormes cantidades de energía.

Por ejemplo, un solo entrenamiento puede requerir miles de GPU durante semanas.


Esto llevó al desarrollo de:

  • modelos más pequeños y eficientes (como LLaMA, Mistral o Phi),
  • técnicas de compresión y cuantización,
  • y estrategias de entrenamiento más ecológicas, como el sparse training o distillation.


El futuro apunta a modelos más sostenibles y distribuidos, accesibles incluso para equipos pequeños o dispositivos personales.


Uno de los mayores malentendidos es pensar que la IA viene a reemplazar a las personas.

En realidad, los mejores resultados se logran cuando humanos e IA trabajan juntos.


Diseñadores, programadores, escritores, docentes y científicos ya usan IA como:

  • asistente de ideas,
  • generador de borradores,
  • corrector o analista,
  • y herramienta de simulación o exploración creativa.

La IA amplifica la inteligencia humana, no la sustituye.


En los próximos años veremos una expansión hacia modelos:

  • multimodales completos, capaces de entender y generar texto, imagen, audio y video de forma unificada;
  • razonadores, que combinen generación con pensamiento lógico y planificación;
  • y personales, ajustados a nuestros hábitos, tono y estilo de comunicación.


Todo esto impulsará nuevas disciplinas como: AI Engineering, Prompt Design, AI Safety y Cognitive AI.


La inteligencia artificial generativa es una herramienta poderosa, pero también un espejo: refleja nuestras virtudes y nuestros límites como sociedad.


Su desarrollo plantea una pregunta fundamental:

> ¿Queremos máquinas que hablen como nosotros…

> o que piensen junto a nosotros?


El futuro dependerá de cómo respondamos a esa pregunta hoy.

Cómo se ajustan y mejoran los modelos generativos


Hay una pregunta clave que todavía no respondimos:

¿Cómo pasa un modelo de simplemente “predecir palabras” a comportarse como un asistente conversacional capaz de seguir instrucciones, responder con criterio o incluso tener “personalidad”?

La respuesta está en una serie de procesos que ocurren después del entrenamiento base, conocidos como fine-tuning, instruction tuning y RLHF (Reinforcement Learning from Human Feedback).

Estos pasos son los que transforman un modelo genérico en algo útil, amigable y confiable.

Durante el entrenamiento base, el modelo aprende cómo funciona el lenguaje: gramática, semántica, relaciones, contexto.

Pero todavía no sabe qué tipo de comportamiento esperamos de él.

Por ejemplo, un modelo base podría responder:

> “No sé quién sos ni por qué me hablás así.”

> cuando le pedimos algo tan simple como “Explicame qué es la fotosíntesis.”


Por eso, se aplica una segunda etapa de entrenamiento: el fine-tuning.

El fine-tuning (ajuste fino) consiste en volver a entrenar el modelo con un conjunto de datos más pequeño y específico, para especializarlo en una tarea o comportamiento.


Por ejemplo: un modelo ajustado para atención al cliente, otro para generar código en Python, o uno especializado en medicina o derecho.


Durante el fine-tuning, el modelo aprende qué tipo de respuestas son deseables para su dominio.

Así, su conocimiento general se adapta a un propósito particular.

Una evolución del fine-tuning es el instruction tuning, que consiste en entrenar al modelo con ejemplos de pares instrucción → respuesta.


Ejemplo:

Instrucción: "Explicá la teoría de la evolución en pocas palabras."

Respuesta: "La teoría de la evolución describe cómo las especies cambian con el tiempo mediante la selección natural."


Después de ver miles de estos ejemplos, el modelo aprende que cuando alguien escribe algo como:

> “Contame brevemente cómo funciona X”

…debe responder de forma informativa, concisa y alineada con la intención del usuario.


Este es el paso que convierte un modelo base en algo más parecido a un asistente útil.


El Reinforcement Learning from Human Feedback (Aprendizaje por refuerzo a partir de retroalimentación humana) va un paso más allá.

En lugar de entrenar solo con ejemplos escritos, el modelo se ajusta usando la opinión de evaluadores humanos.


El proceso funciona así:

  1. El modelo genera varias posibles respuestas a una misma pregunta.
  2. Personas reales eligen cuál es la mejor, más útil o más segura.
  3. El sistema aprende a preferir las respuestas mejor valoradas.


De esta forma, el modelo no solo aprende lenguaje, sino también criterios de comportamiento: ser claro, respetuoso, evitar sesgos o rechazar solicitudes inapropiadas.


Incluso con todos estos ajustes, el modelo todavía depende de cómo lo usamos.

Ahí entra en juego el prompt engineering, o ingeniería de instrucciones: la práctica de formular entradas (prompts) de manera que el modelo produzca el resultado que buscamos.


Por ejemplo:

En lugar de: “Explicame Python.”

Mejor: “Explicame Python como si fuera mi primer lenguaje de programación.”

En lugar de: “Escribí un poema.”

Mejor: “Escribí un poema corto y humorístico sobre un programador que no duerme.”


Un buen prompt actúa como un mapa mental que guía al modelo hacia el tipo de respuesta deseada.

Y aunque los modelos actuales son más robustos, la forma de preguntar sigue siendo clave.


Gracias a estas técnicas, hoy existen:

  • Modelos generales (como GPT o Gemini),
  • Modelos especializados (como los de código, salud o educación),
  • y modelos adaptativos, que se ajustan dinámicamente según la conversación o el contexto.


El fine-tuning también puede hacerse de manera local o privada, permitiendo que empresas o instituciones adapten un modelo general a sus propios datos sin compartirlos públicamente.


Un modelo generativo no nace “inteligente”: aprende primero cómo hablar, luego cómo comportarse, y finalmente cómo adaptarse a cada situación.


El proceso completo es:

  1. Entrenamiento base: aprende el lenguaje.
  2. Fine-tuning / Instruction tuning: aprende tareas específicas y cómo responder.
  3. RLHF: se alinea con la forma en que las personas esperan que actúe.
  4. Prompt engineering: lo guiamos en tiempo real con buenas instrucciones.



miércoles, 3 de diciembre de 2025

for basado en rango vs std::for_each en C++


Desde C++11, C++ ofrece dos formas elegantes de recorrer contenedores:

el bucle for basado en rango y el algoritmo std::for_each de la STL (Standard Template Library).

Aunque ambos hacen lo mismo, hay diferencias importantes en estilo, expresividad y flexibilidad.


for basado en rango


La forma más simple y legible para recorrer elementos:


#include <iostream>

#include <vector>


int main() {

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


    for (int n : numeros)

        std::cout << n << " ";

}


Ventajas:

  • Sintaxis clara y directa.
  • Ideal para recorrer contenedores completos.
  • Soporta modificación con referencias (int& n).

Desventajas:

  • No se puede interrumpir fácilmente ni combinar con otros algoritmos STL.
  • No retorna nada.

std::for_each


std::for_each pertenece al encabezado <algorithm> y aplica una función o lambda a cada elemento del rango.


#include <iostream>

#include <vector>

#include <algorithm>


int main() {

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


    std::for_each(numeros.begin(), numeros.end(),

                  [](int n) { std::cout << n << " "; });

}


Ventajas:

  • Permite integrar lambdas o funciones personalizadas.
  • Compatible con pipelines de algoritmos STL.
  • Puede retornar un iterador al final del rango.

Desventajas:

  • Sintaxis más verbosa.
  • Menos legible para tareas simples.


Ambos pueden modificar los elementos si se usan referencias:


// Con for basado en rango

for (int& n : numeros)

    n *= 2;


// Con std::for_each

std::for_each(numeros.begin(), numeros.end(),

              [](int& n) { n *= 2; });


En Resumen: 

  • for basado en rango: simple, claro y moderno.
  • std::for_each: más flexible, ideal cuando querés combinarlo con otros algoritmos STL.


Ambos son válidos y conviven perfectamente en el C++ moderno.

lunes, 1 de diciembre de 2025

El bucle foreach en C++


En C++ no existe una palabra clave foreach como tal, pero desde C++11 se incorporó una sintaxis que cumple exactamente la misma función: el bucle for basado en rango (range-based for loop).


#include <iostream>

#include <vector>


int main() {

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


    for (int n : numeros) {

        std::cout << n << " ";

    }

}


Salida:

1 2 3 4 5


Este bucle recorre automáticamente todos los elementos del contenedor (como un std::vector, std::array, std::list, etc.) sin necesidad de usar índices ni iteradores manuales.

Si querés modificar los valores, podés usar una referencia:


for (int& n : numeros) {

    n *= 2;

}


Esto duplica cada elemento del vector.

Si no necesitás modificar los elementos, es buena práctica usar const:


for (const int& n : numeros) {

    std::cout << n << " ";

}

De esa forma, evitás copias innecesarias y el compilador puede optimizar mejor el código.

En Resumen: 

  • for (tipo var : contenedor) es la forma moderna y legible de recorrer colecciones en C++.
  • Usá referencias si querés modificar los elementos.
  • Usá const si solo vas a leerlos.
  • Funciona con cualquier contenedor que implemente begin() y end().