Programación funcional en C++: nivel avanzado
\n\nLa programación funcional (PF) es un paradigma que considera los cálculos como la evaluación de funciones matemáticas y evita el estado mutable y los efectos secundarios. Aunque C++ se asocia tradicionalmente con la programación orientada a objetos y procedural, los estándares modernos (C++11 y posteriores) han introducido potentes herramientas para escribir código funcional. En este artículo, iremos más allá de las lambdas básicas y exploraremos técnicas avanzadas: desde la composición de funciones hasta la emulación de mónadas.
\n\n1. Composición de funciones y tuberías
\n\nLa composición de funciones es el proceso de combinar dos o más funciones para crear una nueva función. En C++, es conveniente usar expresiones lambda y plantillas para esto.
\n\n#include <iostream>\n#include <functional>\n#include <vector>\n#include <algorithm>\n\n// Plantilla para la composición de dos funciones\ntemplate<typename F, typename G>\nauto compose(F f, G g) {\n return [f, g](auto x) {\n return f(g(x));\n };\n}\n\n// Ejemplo de uso\nint main() {\n auto addTwo = [](int x) { return x + 2; };\n auto multiplyByThree = [](int x) { return x * 3; };\n\n // Composición: primero multiplyByThree, luego addTwo\n auto composed = compose(addTwo, multiplyByThree);\n\n std::cout << composed(5) << std::endl; // (5*3) + 2 = 17\n\n // Procesamiento en tubería (pipe) con std::ranges (C++20)\n std::vector<int> numbers = {1, 2, 3, 4, 5};\n auto result = numbers\n | std::views::filter([](int n) { return n % 2 == 0; })\n | std::views::transform([](int n) { return n * n; });\n\n for (int n : result) {\n std::cout << n << " "; // 4 16\n }\n return 0;\n}\n\n\nLas tuberías con std::views (C++20) permiten construir cadenas de transformaciones sin crear contenedores intermedios, lo cual es un enfoque funcional clásico.
2. Currying y aplicación parcial
\n\nEl currying es la transformación de una función que toma varios argumentos en una secuencia de funciones, cada una de las cuales toma un solo argumento. La aplicación parcial fija una parte de los argumentos.
\n\n#include <iostream>\n#include <functional>\n\n// Función de suma con currying\nauto curriedAdd = [](int a) {\n return [a](int b) {\n return [a, b](int c) {\n return a + b + c;\n };\n };\n};\n\n// Aplicación parcial con std::bind\nint multiply(int a, int b, int c) {\n return a * b * c;\n}\n\nint main() {\n // Uso de currying\n auto add5 = curriedAdd(5);\n auto add5And3 = add5(3);\n std::cout << add5And3(2) << std::endl; // 10\n\n // Aplicación parcial mediante std::bind\n using namespace std::placeholders;\n auto multiplyBy2 = std::bind(multiply, _1, _2, 2);\n std::cout << multiplyBy2(3, 4) << std::endl; // 3*4*2 = 24\n\n // Aplicación parcial mediante lambda (más idiomático en C++14+)\n auto multiplyBy3 = [](int a, int b) { return multiply(a, b, 3); };\n std::cout << multiplyBy3(5, 6) << std::endl; // 5*6*3 = 90\n\n return 0;\n}\n\n\nEl currying es especialmente útil al trabajar con funciones de orden superior, como std::transform o std::accumulate, permitiendo crear versiones especializadas de funciones "sobre la marcha".
3. Functores, aplicativos y emulación de mónadas
\n\nEn Haskell, las mónadas son un patrón poderoso para trabajar con efectos secundarios. En C++, podemos emularlas usando plantillas de clases. Consideremos una mónada Maybe simple (análoga a std::optional), pero con operaciones monádicas.
#include <iostream>\n#include <functional>\n#include <optional>\n\n// Mónada Maybe (envoltorio sobre std::optional)\ntemplate<typename T>\nclass Maybe {\n s