Translate

viernes, 2 de enero de 2026

Métodos Monádicos en Go — Parte 2


En el post anterior definimos Result[T], un tipo genérico para representar operaciones que pueden fallar, junto con las funciones Map, FlatMap y OrElse.

Ahora vamos a llevar ese patrón al mundo concurrente, donde los errores y la sincronización suelen volverse un caos si no se estructuran bien.

Go facilita lanzar tareas concurrentes con goroutines, pero mezclar concurrencia y manejo de errores puede ser tedioso:


go func() {

    v, err := doSomething()

    if err != nil {

        // manejar error...

    }

    res, err := doSomethingElse(v)

    if err != nil {

        // otro error...

    }

    // ...

}()


Cada paso necesita su if err != nil, y los canales deben sincronizar resultados y errores manualmente.

Podemos crear una versión concurrente del patrón Result, donde cada operación devuelve un canal que emite un Result[T].


func Async[T any](f func() Result[T]) <-chan Result[T] {

    ch := make(chan Result[T], 1)

    go func() {

        defer close(ch)

        ch <- f()

    }()

    return ch

}


Creamos versiones asíncronas de Map y FlatMap:


func MapAsync[A, B any](in <-chan Result[A], f func(A) B) <-chan Result[B] {

    ch := make(chan Result[B], 1)

    go func() {

        defer close(ch)

        r := <-in

        ch <- Map(r, f)

    }()

    return ch

}


func FlatMapAsync[A, B any](in <-chan Result[A], f func(A) <-chan Result[B]) <-chan Result[B] {

    ch := make(chan Result[B], 1)

    go func() {

        defer close(ch)

        r := <-in

        if r.Err != nil {

            ch <- Err[B](r.Err)

            return

        }

        out := f(r.Value)

        ch <- <-out

    }()

    return ch

}


Supongamos que tenemos operaciones concurrentes que pueden fallar:


func FetchData() Result[int] {

    time.Sleep(time.Millisecond * 100)

    return Ok(10)

}


func Compute(x int) Result[int] {

    return Ok(x * 2)

}


func SaveResult(x int) Result[string] {

    if x > 15 {

        return Ok(fmt.Sprintf("Saved %d", x))

    }

    return Err[string](fmt.Errorf("too small"))

}


Podemos encadenarlas de forma limpia:


result := FlatMapAsync(

    MapAsync(Async(FetchData), Compute),

    func(x int) <-chan Result[string] { return Async(func() Result[string] { return SaveResult(x) }) },

)


fmt.Println(OrElse(<-result, "failed")) // "Saved 20"

Las ventajas son :

  • Las tareas se ejecutan en goroutines separadas.
  • Si ocurre un error en cualquier paso, se propaga automáticamente.
  • No hay if err != nil, ni sincronización manual.


Podés crear fácilmente un combinador All para ejecutar varias tareas concurrentes que devuelvan Result:


func All[T any](tasks ...func() Result[T]) Result[[]T] {

    ch := make(chan Result[T], len(tasks))

    for _, f := range tasks {

        go func(fn func() Result[T]) { ch <- fn() }(f)

    }


    var results []T

    for i := 0; i < len(tasks); i++ {

        r := <-ch

        if r.Err != nil {

            return Err[[]T](r.Err)

        }

        results = append(results, r.Value)

    }

    return Ok(results)

}


Si alguna tarea falla, el error se devuelve inmediatamente.

Si todas tienen éxito, se devuelven los resultados combinados.


El enfoque monádico aplicado a Go te permite combinar funciones puras y goroutines sin perder control del flujo ni del manejo de errores.

Aporta claridad a sistemas concurrentes y simplifica el código de coordinación.

Métodos Monádicos en Go


Go no usa palabras como mónada, pero su estilo basado en funciones y retorno explícito de errores se adapta perfectamente a la idea de encadenar operaciones que pueden fallar, sin perder claridad.

Con los genéricos (desde Go 1.18), podemos escribir funciones reutilizables que se comportan como map, flatMap, orElse de otros lenguajes.


El estilo Go para manejar errores:


value, err := DoSomething()

if err != nil {

    return 0, err

}

result, err := DoSomethingElse(value)

if err != nil {

    return 0, err

}

return result, nil


Funciona bien, pero escala mal cuando hay muchos pasos encadenados.

Podemos mejorarlo aplicando ideas monádicas.

Vamos a representar una operación que puede tener éxito o error:


type Result[T any] struct {

    Value T

    Err   error

}


Y creamos constructores simples:


func Ok[T any](v T) Result[T]   { return Result[T]{Value: v} }

func Err[T any](e error) Result[T] { return Result[T]{Err: e} }


Aplica una función al valor, si no hay error.


func Map[A, B any](r Result[A], f func(A) B) Result[B] {

    if r.Err != nil {

        return Err[B](r.Err)

    }

    return Ok(f(r.Value))

}


Aplica una función que también devuelve un Result, y lo aplana.


func FlatMap[A, B any](r Result[A], f func(A) Result[B]) Result[B] {

    if r.Err != nil {

        return Err[B](r.Err)

    }

    return f(r.Value)

}


OrElse: Devuelve un valor alternativo si hubo error.


func OrElse[T any](r Result[T], fallback T) T {

    if r.Err != nil {

        return fallback

    }

    return r.Value

}


Y como lo usamos? 


func ParseInt(s string) Result[int] {

    n, err := strconv.Atoi(s)

    if err != nil {

        return Err[int](err)

    }

    return Ok(n)

}


func DivideByTwo(n int) Result[int] {

    if n%2 != 0 {

        return Err[int](fmt.Errorf("odd number"))

    }

    return Ok(n / 2)

}


func main() {

    result := FlatMap(ParseInt("42"),

        func(n int) Result[int] {

            return Map(DivideByTwo(n), func(x int) int { return x * 3 })

        })


    fmt.Println(OrElse(result, 0)) // 63

}

¿Cuáles son las ventajas?

  • Si ocurre un error en cualquier paso, se propaga automáticamente.
  • Sin if err != nil en cada línea.
  • Código más funcional y declarativo.


Aunque Go no tenga sintaxis monádica ni azúcar funcional, su modelo basado en valores de retorno y funciones puras encaja perfectamente con la idea.

Con un par de funciones genéricas, podés escribir código más declarativo y mantenible.


jueves, 1 de enero de 2026

Métodos Monádicos en C# parte 3


En el post anterior, implementamos una clase Result<T> con métodos monádicos (Map, FlatMap, OrElse).

Ahora combinaremos esa idea con Task<T> para obtener un flujo asíncrono y seguro ante errores, sin try/catch y sin anidar await.

Supongamos que tenemos funciones asíncronas que pueden fallar:


Task<Result<User>> GetUserAsync(int id);

Task<Result<Order>> GetOrderAsync(User user);

Task<Result<Invoice>> CreateInvoiceAsync(Order order);


Queremos encadenarlas de forma limpia, propagando el error automáticamente, sin esto 👇:


var userResult = await GetUserAsync(id);

if (userResult is ErrorResult<User>) return userResult;


var orderResult = await GetOrderAsync(userResult.Value);

// etc...


Creamos extensiones que aplanan el contexto doble (Task<Result<T>>):


public static class TaskResultExtensions

{

    public static async Task<Result<U>> Map<T, U>(

        this Task<Result<T>> task, Func<T, U> f)

    {

        var result = await task;

        return result is OkResult<T> ok ? Result<U>.Ok(f(ok.Value)) 

                                        : Result<U>.Error(((ErrorResult<T>)result).Message);

    }


    public static async Task<Result<U>> FlatMap<T, U>(

        this Task<Result<T>> task, Func<T, Task<Result<U>>> f)

    {

        var result = await task;

        return result is OkResult<T> ok ? await f(ok.Value) 

                                        : Result<U>.Error(((ErrorResult<T>)result).Message);

    }

}


Veamos como usarlo: 


var invoice = await GetUserAsync(10)

    .FlatMap(GetOrderAsync)

    .FlatMap(CreateInvoiceAsync)

    .Map(invoice => invoice.WithDiscount(10))

    .FlatMap(SaveInvoiceAsync)

    .OrElse(Result<Invoice>.Error("No se pudo generar factura"));


¿Cuál es la ventaja?

  • Si cualquier paso falla, la cadena se corta automáticamente
  • No se necesitan try/catch ni comprobaciones manuales
  • El código se lee de arriba a abajo, como una secuencia lógica de pasos


Veamos otro ejemplo:


public async Task<Result<string>> GenerateInvoice(int userId)

{

    return await GetUserAsync(userId)

        .FlatMap(GetOrderAsync)

        .FlatMap(CreateInvoiceAsync)

        .Map(invoice => invoice.Id.ToString());

}


Si alguna función devuelve un Result.Error, ese error se propaga sin ejecutar los pasos siguientes.

El resultado final es un Result<string> con éxito o mensaje de error.


miércoles, 31 de diciembre de 2025

Feliz Navidad y buen año para todos!!



Como todos los años les deseo una feliz navidad y un buen 2026. 

Gracias por leerme! 

Métodos Monádicos en C# parte 2



Podemos crear nuestra propia clase monádica, similar a std::expected o Result de Rust:


public abstract class Result<T>

{

    public abstract Result<U> Map<U>(Func<T, U> f);

    public abstract Result<U> FlatMap<U>(Func<T, Result<U>> f);

    public abstract T OrElse(T fallback);

    public static Result<T> Ok(T value) => new OkResult<T>(value);

    public static Result<T> Error(string message) => new ErrorResult<T>(message);

}


public class OkResult<T> : Result<T>

{

    private readonly T value;

    public OkResult(T v) => value = v;

    public override Result<U> Map<U>(Func<T, U> f) => Result<U>.Ok(f(value));

    public override Result<U> FlatMap<U>(Func<T, Result<U>> f) => f(value);

    public override T OrElse(T fallback) => value;

}


public class ErrorResult<T> : Result<T>

{

    private readonly string message;

    public ErrorResult(string msg) => message = msg;

    public override Result<U> Map<U>(Func<T, U> f) => Result<U>.Error(message);

    public override Result<U> FlatMap<U>(Func<T, Result<U>> f) => Result<U>.Error(message);

    public override T OrElse(T fallback) => fallback;

}


Uso:


Result<int> parse(string s) => int.TryParse(s, out var n) ? Result<int>.Ok(n) : Result<int>.Error("Invalid");

var r = parse("42")

    .FlatMap(x => Result<int>.Ok(x * 2))

    .Map(x => x + 1)

    .OrElse(0);

Console.WriteLine(r); // 85


Los métodos monádicos permiten escribir código más expresivo y seguro en C#, eliminando condicionales repetitivos y manejo manual de errores.


Métodos Monádicos en C#


El paradigma monádico permite encadenar operaciones que transforman valores sin salir del contexto en el que viven: puede ser una secuencia, una operación asíncrona o un valor que puede no existir.

En C#, estas ideas se encuentran en estructuras muy familiares.

Una mónada encapsula un valor junto con su contexto:

  • puede faltar → Nullable<T> o Option<T>
  • varios valores → IEnumerable<T>
  • asíncrono → Task<T>


Lo importante es que provea dos operaciones fundamentales:

  • Map (o en C# Select) — aplica una función al valor dentro del contexto
  • FlatMap (o en C# SelectMany) — aplica una función que devuelve otro contenedor del mismo tipo y lo aplana


Nullable<T> — el valor opcional

Cualquier tipo T puede transformarse en Nullable<T> usando T?.

C# no expone métodos como map o flatMap, pero podemos simularlos con extensiones.


public static class NullableExtensions

{

    public static TResult? Map<T, TResult>(this T? value, Func<T, TResult> f)

        where T : struct where TResult : struct =>

        value.HasValue ? f(value.Value) : (TResult?)null;


    public static TResult? FlatMap<T, TResult>(this T? value, Func<T, TResult?> f)

        where T : struct where TResult : struct =>

        value.HasValue ? f(value.Value) : (TResult?)null;

}


Uso:

int? x = 5;

int? result = x.Map(n => n * 2).FlatMap(n => n > 5 ? n : null);

Console.WriteLine(result); // 10


No hay if (x.HasValue) explícitos — todo se encadena naturalmente.


IEnumerable<T> — mónada de listas


LINQ es, de hecho, una sintaxis monádica.

Los métodos Select y SelectMany son equivalentes a map y flatMap.


var result = from x in new[] { 1, 2, 3 }

             from y in new[] { 10, 20 }

             select x + y;


foreach (var r in result)

    Console.WriteLine(r);


SelectMany combina múltiples secuencias y aplana el resultado.

Esto es exactamente la mónada lista del álgebra funcional.


Task<T> — mónada asíncrona


En C#, Task<T> encapsula una operación que producirá un valor en el futuro.

Los métodos ContinueWith, await, o Task.WhenAll son sus operaciones monádicas.


Task<int> getValue = Task.FromResult(5);


var result = await getValue

    .ContinueWith(t => t.Result * 2)

    .ContinueWith(t => t.Result + 3);


Console.WriteLine(result.Result); // 13


await actúa como un flatMap implícito, “saca” el valor del contexto y permite encadenar cálculos asíncronos de forma natural.


Y podemos definir nuestras propias monadas pero eso es para otro post... 

sábado, 27 de diciembre de 2025

Implementando nuestra propia mónada en Java


En lenguajes funcionales (y en C++23 con std::expected), existe un tipo que representa éxito o error sin excepciones.

En Java podemos implementarlo fácilmente y hacerlo monádico.

Result<T, E> encapsula dos posibles estados:

  • Ok(T) → el cálculo fue exitoso
  • Err(E) → ocurrió un error


Así evitamos lanzar excepciones, y podemos encadenar operaciones de manera declarativa.


public sealed interface Result<T, E> permits Ok, Err {

    boolean isOk();

    boolean isErr();


    <U> Result<U, E> map(Function<? super T, ? extends U> f);

    <U> Result<U, E> flatMap(Function<? super T, Result<U, E>> f);

    T orElse(T fallback);

}


public record Ok<T, E>(T value) implements Result<T, E> {

    public boolean isOk() { return true; }

    public boolean isErr() { return false; }

    public <U> Result<U, E> map(Function<? super T, ? extends U> f) {

        return new Ok<>(f.apply(value));

    }

    public <U> Result<U, E> flatMap(Function<? super T, Result<U, E>> f) {

        return f.apply(value);

    }

    public T orElse(T fallback) { return value; }

}


public record Err<T, E>(E error) implements Result<T, E> {

    public boolean isOk() { return false; }

    public boolean isErr() { return true; }

    public <U> Result<U, E> map(Function<? super T, ? extends U> f) {

        return new Err<>(error);

    }

    public <U> Result<U, E> flatMap(Function<? super T, Result<U, E>> f) {

        return new Err<>(error);

    }

    public T orElse(T fallback) { return fallback; }

}


Podemos usarlo, de esta manera: 


Result<Integer, String> parseInt(String s) {

    try {

        return new Ok<>(Integer.parseInt(s));

    } catch (NumberFormatException e) {

        return new Err<>("Not a number");

    }

}


Result<Integer, String> divideByTwo(int n) {

    return (n % 2 == 0)

        ? new Ok<>(n / 2)

        : new Err<>("Odd number");

}


var result = parseInt("42")

    .flatMap(this::divideByTwo)

    .map(x -> x * 3)

    .orElse(0);


System.out.println(result); // 63


Si en cualquier paso se produce un error (Err), las transformaciones se detienen automáticamente.

No hay try/catch, ni comprobaciones manuales de estado.


Ventajas del enfoque monádico

  • Evita excepciones y null.
  • Encadena operaciones de manera natural.
  • Explicita el flujo de éxito/error.
  • Es fácilmente testeable y composable.


En Java no hace falta un lenguaje funcional completo para pensar funcionalmente.

Con un poco de sintaxis moderna (record, sealed interface, lambdas) podés crear tus propios tipos monádicos.


miércoles, 24 de diciembre de 2025

Programación funcional con Optional, Stream y CompletableFuture en Java

 


Aunque Java no hable directamente de mónadas, muchas de sus clases modernas (desde Java 8) implementan comportamiento monádico: permiten encadenar operaciones que transforman o combinan valores sin salir del contexto (por ejemplo: puede ser nulo, puede fallar, puede no haber terminado aún).


Una mónada encapsula un valor junto con un contexto.

Ejemplos de contextos:

  • “puede no haber valor” → Optional<T>
  • “flujo de valores” → Stream<T>
  • “resultado asíncrono” → CompletableFuture<T>


Una mónada define operaciones para:

  • aplicar funciones al valor (map, flatMap)
  • propagar la ausencia o el error automáticamente
  • encadenar transformaciones sin condicionales explícitos


Optional<T> — ausencia de valor


El caso más simple: representa un valor o nada.


Optional<Integer> x = Optional.of(5);


var y = x.map(n -> n * 2)

          .flatMap(n -> Optional.of(n + 3))

          .orElse(0); // (5 * 2) + 3 = 13



Métodos monádicos principales

  • map(f): Aplica f si el valor existe
  • flatMap(f): Aplica f que devuelve otro Optional
  • orElse(v): Valor por defecto si está vacío
  • orElseGet(supplier): Valor por defecto perezoso
  • or(fallback) (desde Java 9): Usa otro Optional si está vacío


Stream<T> — secuencias monádicas

Stream también es una mónada: un contexto que contiene una secuencia de valores y permite transformaciones encadenadas.


Stream.of(1, 2, 3)

    .map(x -> x * 2)

    .flatMap(x -> Stream.of(x, x + 10))

    .forEach(System.out::println);


  • map transforma cada elemento
  • flatMap expande (aplana) los resultados
  • filter, reduce, etc. siguen el mismo espíritu


En términos monádicos: Stream es la mónada de la lista.


CompletableFuture<T> — mónada asíncrona


CompletableFuture encapsula un valor que aún no está disponible.


CompletableFuture.supplyAsync(() -> 5)

    .thenApply(x -> x * 2)           // map

    .thenCompose(x -> asyncAdd(x))   // flatMap

    .exceptionally(e -> 0)           // orElse

    .thenAccept(System.out::println);


  • thenApply ≈ map
  • thenCompose ≈ flatMap
  • exceptionally ≈ orElse


Los métodos monádicos permiten escribir código sin condicionales explícitos, sin null checks y sin bloqueos innecesarios.


martes, 23 de diciembre de 2025

Métodos Monádicos en C++ — Parte 2


En la el post anterior vimos cómo transform, and_then y or_else permiten escribir código más expresivo con std::optional y std::expected.

Ahora vamos a ir un paso más allá: cómo componer funciones monádicas y diseñar código fluido sin excepciones.

El poder real de los métodos monádicos aparece cuando se encadenan varias transformaciones.

Veamos un ejemplo usando std::optional:


auto half = [](int x) -> std::optional<int> {

    return x % 2 == 0 ? x / 2 : std::nullopt;

};


auto addTen = [](int x) { return x + 10; };


std::optional<int> result =

    std::optional(8)

        .and_then(half)

        .transform(addTen); // ((8 / 2) + 10) = 14


Si en algún punto se devuelve std::nullopt, toda la cadena se corta automáticamente.


Con std::expected, el patrón es el mismo, pero ahora los errores son valores explícitos:


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

std::expected<int, std::string> divideByTwo(int x);


auto r = parse("42")

    .and_then(divideByTwo)

    .transform([](int n){ return n * 3; })

    .or_else([](auto err){

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

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

    });


Si cualquiera de las funciones falla, se propaga el error automáticamente.

No se necesitan try/catch ni comprobaciones manuales.

Si querés que tus funciones participen en estos encadenamientos, deben:

  • Retornar un std::optional<T> o std::expected<T, E>.
  • No lanzar excepciones.
  • No asumir que el valor siempre existe.


Ejemplo:


std::expected<int, std::string> toInt(std::string s) {

    try {

        return std::stoi(s);

    } catch (...) {

        return std::unexpected("invalid integer");

    }

}


Esta función ya puede encadenarse con .and_then() o .transform().

Los métodos monádicos de C++23 no solo son una mejora sintáctica, son una nueva forma de estructurar la lógica — más declarativa, más segura y más fácil de leer.

Y recuerda:

  • Usá transform para funciones simples (no cambian el contexto).
  • Usá and_then cuando la función devuelva otro optional o expected.
  • Usá or_else para recuperación o logging.
  • Evitá mezclar estos métodos con if/else sobre .has_value() — se pierde la gracia funcional.
  • Pensá tus funciones como operaciones puras.


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.