Translate

miércoles, 31 de diciembre de 2025

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...