Programmation fonctionnelle en C++ : niveau avancé
\n\nLa programmation fonctionnelle (PF) est un paradigme qui considère les calculs comme l'évaluation de fonctions mathématiques et évite les états modifiables et les effets secondaires. Bien que le C++ soit traditionnellement associé à la programmation orientée objet et procédurale, les normes modernes (C++11 et ultérieures) ont introduit des outils puissants pour écrire du code fonctionnel. Dans cet article, nous irons au-delà des lambdas de base et examinerons des techniques avancées : de la composition de fonctions à l'émulation de monades.
\n\n1. Composition de fonctions et pipelines
\n\nLa composition de fonctions est le processus de combinaison de deux ou plusieurs fonctions pour en créer une nouvelle. En C++, il est pratique d'utiliser des expressions lambda et des templates pour cela.
\n\n#include <iostream>\n#include <functional>\n#include <vector>\n#include <algorithm>\n\n// Template pour la composition de deux fonctions\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// Exemple d'utilisation\nint main() {\n auto addTwo = [](int x) { return x + 2; };\n auto multiplyByThree = [](int x) { return x * 3; };\n\n // Composition : d'abord multiplyByThree, puis addTwo\n auto composed = compose(addTwo, multiplyByThree);\n\n std::cout << composed(5) << std::endl; // (5*3) + 2 = 17\n\n // Traitement en pipeline (pipe) avec 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\nLes pipelines avec std::views (C++20) permettent de construire des chaînes de transformations sans créer de conteneurs intermédiaires, ce qui est une approche fonctionnelle classique.
2. Curryfication (Currying) et application partielle
\n\nLa curryfication est la transformation d'une fonction prenant plusieurs arguments en une séquence de fonctions, chacune prenant un seul argument. L'application partielle fixe une partie des arguments.
\n\n#include <iostream>\n#include <functional>\n\n// Fonction d'addition curryfiée\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// Application partielle avec std::bind\nint multiply(int a, int b, int c) {\n return a * b * c;\n}\n\nint main() {\n // Utilisation de la curryfication\n auto add5 = curriedAdd(5);\n auto add5And3 = add5(3);\n std::cout << add5And3(2) << std::endl; // 10\n\n // Application partielle via 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 // Application partielle via lambda (plus idiomatique 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\nLa curryfication est particulièrement utile lors du travail avec des fonctions d'ordre supérieur, telles que std::transform ou std::accumulate, permettant de créer des versions spécialisées de fonctions "à la volée".
3. Foncteurs, applicatifs et émulation de monades
\n\nEn Haskell, les monades sont un puissant modèle pour gérer les effets secondaires. En C++, nous pouvons les émuler à l'aide de templates de classes. Considérons une monade simple Maybe (analogue à std::optional), mais avec des opérations monadiques.
#include <iostream>\n#include <functional>\n#include <optional>\n\n// Monade Maybe (wrapper autour de std::optional)\ntemplate<typename T>\nclass Maybe {\n s