1. Introduction
Fonction pure — c'est une fonction qui, pour des mêmes données d'entrée, retourne toujours le même résultat et n'a aucun effet de bord. Plus simplement : une fonction pure ne "détruit" rien autour d'elle et n'est pas contaminée de l'extérieur.
Une fonction est considérée pure si :
- Elle seulement calcule une valeur basée sur les arguments d'entrée.
- Ne modifie pas de variables externes ou l'état du programme.
- Ne dépend pas de variables externes ou d'états qui peuvent changer.
Deux règles d'or de la pureté
- Déterminisme : même entrée — même sortie.
- Pas d'effets de bord : la fonction ne change rien en dehors d'elle : ni fichiers, ni variables globales, ni interface utilisateur (salut, Console.WriteLine).
Ce n'est pas une religion, c'est du bon sens
Ça peut sembler une contrainte académique, mais la pratique montre le contraire :
- La fonction pure est prévisible. Elle est facile à tester — tu l'appelles avec des arguments donnés et tu obtiens un résultat déterminé.
- Une fonction pure peut être librement "réordonnée" dans le code sans craindre de casser quelque chose ailleurs.
- Dans un monde multi-core, la fonction pure est une garantie de sécurité : on peut l'exécuter en parallèle sans peur des data races.
2. Exemples de fonctions pures et impures
Fonction pure : tout est prévisible
// Fonction pure : ne touche rien en dehors d'elle
int Add(int a, int b)
{
return a + b;
}
int Square(int x)
{
return x * x;
}
Appelle Add(2, 3) cent fois de suite — ce sera toujours 5. Ennuyeux, mais fiable.
Fonction impure : viole les règles
// Violation de pureté : dépend d'un état externe (variable statique)
int counter = 0;
int Increase()
{
counter++;
return counter;
}
Ici l'appel de Increase() retournera une nouvelle valeur à chaque fois — donc plus de déterminisme.
// Violation de pureté : provoque un effet externe (affichage)
int AddAndPrint(int a, int b)
{
int sum = a + b;
Console.WriteLine(sum); // Effet de bord !
return sum;
}
Et le hasard ou le temps ?
Toute fonction qui utilise DateTime.Now ou Random n'est plus pure :
// Pas pure !
int GetRandomNumber()
{
return new Random().Next();
}
Tableau des différences
| Caractéristique | Fonction pure | Fonction impure |
|---|---|---|
| Toujours même résultat pour mêmes arguments | Oui | Non |
| Effets de bord | Non | Oui |
| Dépendance à l'état externe | Non | Oui |
3. Immutabilité des données : théorie et pratique
Immutabilité (immutability) — c'est une approche où un objet ne peut pas être modifié après sa création. Si on a besoin d'une nouvelle valeur — on crée un nouvel objet.
Pourquoi c'est important ?
- L'application devient résistante aux erreurs accidentelles de modification des données.
- Pas de "fuites" secrètes de modifications : si tu possèdes un objet, personne ne changera son champ en douce.
- L'immutabilité est la base pour beaucoup d'optimisations automatiques et pour le calcul parallèle.
Exemples simples en C#
Types immuables dans .NET
Les chaînes (string) en C# sont immuables ! Chaque fois que tu fais string.Concat(s, "world"), une nouvelle chaîne est créée.
string s = "Hello";
string t = s;
s = s + " World";
Console.WriteLine(t); // t == "Hello"
Tableaux et collections : mutable par défaut
int[] numbers = { 1, 2, 3 };
numbers[0] = 42; // Le tableau a changé !
Immutabilité "en clair" : compilation du code
Au lieu de modifier un objet/une valeur existant(e), on retourne un nouveau :
// Au lieu de ceci :
void AddToList(List<int> list, int value)
{
list.Add(value); // Mutate !
}
// Mieux comme ça :
List<int> AddToList(List<int> list, int value)
{
var newList = new List<int>(list) { value }; // Nouvelle liste
return newList;
}
Illustration : muter ou ne pas muter
flowchart LR
A[Objet initial] --"mutation"--> B[Même objet, mais différent à l'intérieur]
A --"immutabilité"--> C[Nouvel objet]
4. À quoi ça sert dans du vrai code C# ?
- Dans le C# moderne, des bibliothèques comme LINQ, Entity Framework et ASP.NET Core misent sur les fonctions pures et l'immutabilité.
- L'immutabilité réduit le nombre de bugs "magiques" où quelqu'un a écrasé une valeur importante quelque part.
- Les fonctions pures permettent d'utiliser facilement les tests automatiques (unit tests), parce que pour tester il suffit de vérifier entrée et sortie sans se soucier du monde extérieur.
Exemple : travail avec des chaînes
string s = "Hello";
string newS = s.Replace("H", "J"); // s reste "Hello"; newS — "Jello"
Exemple : LINQ et collections
Where, Select et d'autres méthodes retournent de nouvelles collections, sans toucher aux anciennes.
var numbers = new List<int> { 1, 2, 3, 4 };
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();
// numbers est resté identique !
Exemple concret : configuration via objets immuables
Beaucoup d'API .NET modernes utilisent des objets immuables pour la configuration, par exemple JsonSerializerOptions :
var options = new JsonSerializerOptions
{
WriteIndented = true
};
// Cet objet ne change pas "en cours de route", ce qui augmente la fiabilité.
5. Erreurs typiques et pièges
La pente glissante commence quand tu "par accident" mutes des données dans du code supposé "pur".
Souvent ça arrive avec des collections : tu voulais filtrer une liste, et pendant ce processus tu as modifié l'original.
Ou tu as oublié que la méthode comme List<T>.Add modifie l'objet sur place.
Exemple sournois :
List<int> DoubleTheNumbers(List<int> xs)
{
// Erreur ! On mute la liste d'origine, on retourne le même objet.
foreach (var i in xs)
xs.Add(i * 2);
return xs;
}
Ce code provoquera même une exception d'exécution (InvalidOperationException), parce qu'on change la collection pendant qu'on l'itère — problème classique de mutation.
Correct :
List<int> DoubleTheNumbers(List<int> xs)
{
var newList = new List<int>(xs.Select(x => x * 2));
return newList;
}
GO TO FULL VERSION