1. Introducción
La programación funcional (PF) es una paradigma de programación en la que la unidad básica no es un objeto ni un procedimiento/método, sino una función en el sentido matemático. En PF se pone el foco en describir "qué calcular", no "cómo calcular".
Ya te habrás encontrado con ideas de PF al trabajar con expresiones lambda y LINQ. ¿Pero cuál es la diferencia real? En la práctica: OOP describe objetos y sus interacciones, la programación procedimental son pasos, y PF es composición de funciones, pasar comportamiento como valor, evitar cambios de estado (immutability) y no tener efectos secundarios.
¿Por qué necesitaríamos esta nueva paradigma?
- Código más limpio, predecible y testeable.
- Soporte más sencillo para multihilo ("sin estado — sin problemas").
- Concisión y expresividad (menos código — menos bugs).
- Abstracciones de alto nivel, fáciles de reutilizar.
Analogía
Imagina que un restaurante recibe un pedido: "preparar una tortilla". El cocinero imperativo sigue instrucciones: coger huevos, romper, batir, freír. El cocinero funcional dice: res = omelette(huevos) — opera con funciones, abstraído del estado interno de la cocina (bueno, casi).
En C# podemos usar ambos enfoques. Eso hace el lenguaje muy flexible y potente — especialmente para proyectos reales.
Conceptos clave de PF
1. Funciones de orden superior
Las funciones se pueden pasar como parámetros, devolver desde otras funciones y almacenar en variables. Ya lo hiciste con lambdas y delegates. En PF estas "funciones sobre funciones" son la base de todo.
2. Funciones puras
Una función es "pura" si su resultado depende solo de sus parámetros y no modifica nada fuera de ella (sin efectos secundarios). Dos llamadas iguales con los mismos argumentos devuelven el mismo resultado.
3. Inmutabilidad (Immutability)
Los datos no se mutan "in situ": un nuevo estado es un nuevo objeto. Esto simplifica razonar sobre el programa y ayuda en multihilo.
4. Ausencia de efectos secundarios
La función no escribe en un archivo, no cambia variables globales, no dibuja en pantalla — solo devuelve un resultado. En la práctica los efectos secundarios son inevitables, pero se intenta aislarlos en los bordes del sistema.
5. Composición de funciones
Una función puede construirse a partir de otras, como piezas. Por ejemplo: filtrar números positivos, elevarlos al cuadrado y sumarlos. Cada operación es una función separada y se combinan fácilmente (Where → Select → Sum).
2. PF en C#: de la teoría a la práctica
C# es un lenguaje multiparadigma: soporta OOP, estilo procedimental y un estilo funcional potente (con lambdas, delegates, extension methods y LINQ).
Veámoslo con el ejemplo de nuestra app de estudio
Imagina que desarrollamos una app que trabaja con listas de números y cadenas. La tarea es aplicar distintas operaciones a esos datos en estilo funcional.
Ejemplo 1: Uso de funciones de orden superior
// Aplica una acción a todos los elementos de la lista
public static void ForEach<T>(List<T> items, Action<T> action)
{
foreach (var item in items)
{
action(item);
}
}
Uso:
var numbers = new List<int> { 1, 2, 3, 4, 5 };
ForEach(numbers, n => Console.WriteLine(n * n)); // Parámetro-función
¿Ves? Una función puede "guardarse" en una variable o pasarse como un valor — igual que una manzana en la cocina.
Ejemplo 2: Función pura
Función que no cambia el estado del programa y depende solo de la entrada:
int MultiplyByTwo(int x)
{
return x * 2;
}
- No depende de nada externo.
- No cambia nada fuera.
- Para x = 5 siempre devolverá 10.
Compáralo con una función que usa y modifica una variable global:
int total = 0;
int AddToTotal(int x)
{
total += x;
return total;
}
Eso ya no es una función pura — el resultado depende del estado externo y lo modifica.
Ejemplo 3: Inmutabilidad de datos
En lugar de modificar los datos de entrada creamos nuevos:
List<int> AddOneToEach(List<int> numbers)
{
return numbers.Select(n => n + 1).ToList();
}
La lista de entrada no se cambia. En programas multihilo esto es especialmente útil: menos locks y menos race conditions.
Ejemplo 4: Composición de funciones
Obtener la suma de los cuadrados de todos los números pares:
int SumOfEvenSquares(List<int> numbers)
{
return numbers
.Where(n => n % 2 == 0) // Dejar sólo los pares
.Select(n => n * n) // Elevar al cuadrado
.Sum(); // Sumar
}
Legible y declarativo: cada operación es una función separada.
3. Detalles útiles
PF, LINQ y C#
LINQ es casi "PF en práctica" para colecciones: usas funciones de orden superior (Where, Select, etc.), obtienes nuevas secuencias sin mutar las originales, y cada transformación es una expresión separada. El resultado es IEnumerable<T>, que describe qué obtener, no cómo iterar.
Tabla de analogías
| Imperativo (procedural/OOP) | Funcional (LINQ/estilo PF) |
|---|---|
|
|
| «Mutar» la colección | Obtener una nueva colección |
| Estado (total += x) | Funciones puras (xs.Sum()) |
| Describir como: «haz esto» | Describir: «qué queremos obtener» |
PF vs OOP: dos mundos — un solo C#
No son bandos en guerra. En proyectos C# reales se combinan: el modelo del dominio suele construirse con clases (OOP), y el procesamiento de colecciones, agregación y transformaciones se hace en estilo funcional con LINQ, lambdas y extension methods.
Tu conocimiento sobre delegates es directamente útil: Func<T, TResult>, Predicate<T>, Action<T> — son bloques típicos del estilo PF.
Función universal de filtrado:
List<T> Filter<T>(List<T> items, Predicate<T> predicate)
{
var result = new List<T>();
foreach (var item in items)
{
if (predicate(item))
result.Add(item);
}
return result;
}
Llamadas:
var adults = Filter(people, person => person.Age >= 18);
var bigFiles = Filter(fileNames, name => name.EndsWith(".mp4") && name.Length > 10);
En vez de muchas funciones parecidas con condiciones distintas — una función universal.
¿Por qué los empleadores y entrevistas valoran a desarrolladores PF?
- PF ayuda a testear bloques pequeños de código sin levantar todo el sistema.
- La lógica es más fácil de mantener: menos estado — menos fuentes de error.
- Más fácil escribir código paralelo y asíncrono — no hay estado global, menos race conditions.
¿Y cómo no volverse fanático?
Sí, PF es poderoso. Pero C# no es puramente funcional y no todas las tareas requieren pureza absoluta. No temas usar variables locales y mutación razonable donde tenga sentido. Lo importante es legibilidad, predictibilidad y testabilidad. Los elementos de PF son una herramienta, no una religión.
4. Errores típicos de los principiantes
Es muy fácil caer en código que parece funcional pero no lo es realmente.
Por ejemplo, una función devuelve una nueva colección pero internamente muta la lista original — eso rompe la inmutabilidad y rompe las expectativas del llamador.
Otro ejemplo: una lambda captura una variable externa y la modifica. En la paradigma funcional eso se considera un efecto secundario y hace el comportamiento menos predecible.
El compilador de C# no te lo impedirá: el lenguaje permite ambas cosas. Por eso en prácticas PF es importante asegurarse de que la función "vive por sí misma", no cambia nada fuera y no lee nada excepto sus argumentos.
GO TO FULL VERSION