Programación funcional en C#: de la teoría a la práctica
La programación funcional (PF) es un paradigma en el que los cálculos se consideran como la evaluación de funciones matemáticas, evitando cambios de estado y datos mutables. Aunque C# originalmente era un lenguaje orientado a objetos, a partir de la versión 3.0 ha ido incorporando activamente capacidades funcionales. Hoy en día, C# admite conceptos como expresiones lambda, LINQ, tipos inmutables e incluso pattern matching. En este artículo analizaremos los principios clave de la PF y mostraremos cómo aplicarlos en proyectos reales con C#.
1. Funciones puras y ausencia de efectos secundarios
Función pura es una función que:
- Siempre devuelve el mismo resultado para los mismos argumentos (transparencia referencial).
- No tiene efectos secundarios (no modifica el estado global, no escribe en archivos, no envía solicitudes HTTP).
En C#, una función pura puede verse así:
public static int Add(int a, int b) => a + b;Y aquí un ejemplo de función impura:
public static int AddAndLog(int a, int b){ Console.WriteLine($"Sumando {a} y {b}"); // efecto secundario return a + b;}Ventajas de las funciones puras: son fáciles de probar, depurar y paralelizar.
2. Inmutabilidad (Immutability)
En PF, los datos son inmutables por defecto. En C# se utilizan para esto:
- La palabra clave
readonlypara campos. - Las estructuras
record(C# 9+), que crean automáticamente objetos inmutables. - Las colecciones del espacio de nombres
System.Collections.Immutable.
Ejemplo con record:
public record Person(string Name, int Age);Si se necesita cambiar la edad, se crea una nueva instancia:
Person person = new("Iván", 30);Person olderPerson = person with { Age = 31 };La inmutabilidad previene mutaciones accidentales y simplifica la concurrencia.
3. Funciones de orden superior y expresiones lambda
Las funciones de orden superior toman otras funciones como argumentos o las devuelven. En C# esto se implementa mediante delegados: Func<>, Action<>, Predicate<>.
Un ejemplo simple es el método Where de LINQ:
List<int> numbers = new() { 1, 2, 3, 4, 5 };var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();Aquí n => n % 2 == 0 es una expresión lambda (función anónima).
Se puede escribir una función de orden superior propia:
public static List<T> Filter<T>(List<T> list, Func<T, bool> predicate){ var result = new List<T>(); foreach (var item in list) if (predicate(item)) result.Add(item); return result;}4. LINQ: potente herramienta de estilo funcional
LINQ (Language Integrated Query) es, quizás, el ejemplo más destacado de programación funcional en C#. Permite procesar colecciones de forma declarativa, como en SQL.
Ejemplo: obtener los nombres de usuarios mayores de 18 años, ordenados por edad:
var users = new List<User>{ new("Ana", 22), new("Boris", 17), new("Víctor", 30)};
var result = users .Where(u => u.Age >= 18) .OrderBy(u => u.Age) .Select(u => u.Name) .ToList();LINQ admite ejecución diferida (lazy evaluation), lo que ahorra recursos.
5. Mónadas y tipo Maybe en C#
Las mónadas son una abstracción para cadenas de cálculos con contexto (por ejemplo, manejo de null o asincronía). En C# no hay una mónada Maybe incorporada, pero es fácil de implementar:
public class Maybe<T>{ private readonly T _value; public bool HasValue { get; } p