Programmation fonctionnelle en C# : de la syntaxe à l'architecture
\n\nLe C# moderne a depuis longtemps dépassé le cadre d'un langage purement orienté objet. Depuis la version 3.0 (LINQ) jusqu'aux dernières versions de .NET, le langage absorbe activement les paradigmes de la programmation fonctionnelle (PF). Si vous maîtrisez les lambdas et LINQ, mais souhaitez approfondir, cet article est pour vous. Nous aborderons des concepts avancés de PF qui rendront votre code C# plus prévisible, testable et concis.
\n\n1. Fonctions pures et gestion des effets de bord
\n\nFonction pure — c'est une fonction qui :
\n- \n
- Retourne toujours le même résultat pour les mêmes arguments (transparence référentielle). \n
- N'a pas d'effets de bord (ne modifie pas l'état global, n'écrit pas dans un fichier, n'envoie pas de requêtes HTTP). \n
En C#, l'exemple idéal d'une fonction pure est les opérations mathématiques. Le problème est qu'une application réelle ne peut pas être composée uniquement de fonctions pures : tôt ou tard, il faut sauvegarder des données ou envoyer un email. La solution — séparer la logique des effets.
\n\nTechnique avancée : utilisez des foncteurs et monades pour la conteneurisation des effets. Examinons un exemple simple avec Maybe<T> (équivalent de nullable, mais fonctionnel) :
public class Maybe<T>\n{\n private readonly T _value;\n public bool HasValue { get; }\n \n private Maybe(T value, bool hasValue) => (_value, HasValue) = (value, hasValue);\n \n public static Maybe<T> Some(T value) => new Maybe<T>(value, true);\n public static Maybe<T> None() => new Maybe<T>(default, false);\n \n // Foncteur : Select (Map)\n public Maybe<TResult> Select<TResult>(Func<T, TResult> selector) =>\n HasValue ? Maybe<TResult>.Some(selector(_value)) : Maybe<TResult>.None();\n \n // Monade : SelectMany (FlatMap/Bind)\n public Maybe<TResult> SelectMany<TResult>(Func<T, Maybe<TResult>> binder) =>\n HasValue ? binder(_value) : Maybe<TResult>.None();\n}\n\n// Utilisation\nMaybe<int> result = Maybe<int>.Some(10)\n .Select(x => x * 2)\n .SelectMany(x => x > 15 ? Maybe<int>.Some(x) : Maybe<int>.None());\n\nCe code ne génère pas d'exceptions et ne vérifie pas null manuellement — toute la logique de gestion de l'absence de valeur est encapsulée dans la monade.
2. Curryfication et application partielle
\n\nEn PF classique, chaque fonction prend un seul argument. La curryfication est la transformation d'une fonction avec plusieurs paramètres en une chaîne de fonctions imbriquées, chacune prenant un seul argument. L'application partielle est la fixation d'une partie des arguments.
\n\nEn C#, il n'y a pas de curryfication intégrée, mais elle est facile à implémenter via des fermetures :
\n\n// Fonction normale\nFunc<int, int, int, int> add = (a, b, c) => a + b + c;\n\n// Version curryfiée\nFunc<int, Func<int, Func<int, int>>> curriedAdd = a => b => c => a + b + c;\n\n// Application partielle : fixer le premier argument\nFunc<int, Func<int, int>> add5 = curriedAdd(5);\n\n// Utilisation\nint sum = add5(10)(20); // 35\n\nScénario avancé : injection de dépendances via l'application partielle. Au lieu de passer la configuration ou un service via le constructeur de la classe, on peut les fixer au moment de la composition :
\n\npublic class ReportService\n{\n public Func<string, string, string> FormatReport => \n (template, data) => $"[{DateTime.Now}] {template}: {data}";\n}\n\n// Utilisation avec application partielle\nvar service = new ReportService();\nvar formatWithTemplate = service.FormatReport("Error"); // fixer le modèle\nstring errorMessage = formatWithTemplate("Connection failed");