Avantages de la programmation fonctionnelle
En programmation fonctionnelle, comme les données sont immuables, il n'y a pas de problèmes de programmation concurrente et le code est sûr pour les threads multiples. Cela réduit considérablement les effets secondaires dans les prorgammes. Pour les projets nécessitant des itérations rapides, la programmation fonctionnelle permet de changer de fonction à chaud sans se soucier des problèmes de données, car elle considère la fonction comme l'unité de base. Tant que les relations entre les fonctions sont correctes, la justesse des résultats est garantie.
L'expression en programmation fonctionnelle est plus conforme à la syntaxe quotidienne des humains, rendant le code plus lisible. Pour implémenter la même fonctionnalité, la programmation fonctionnelle nécessite beaucoup moins de code que la programmation orientée objet, rendant le code plus concis et clair. La programmation fonctionnelle est largement utilisée dans la recherche scientifique car les exigences d'ingénierie du code sont faibles, ce qui la rend plus simple à écrire et plus rapide à développer. Dans les scénarios où la vitesse de développement est prioritaire mais où les ressources d'exécution et la vitesse de fonctionnement ne sont pas critiques, l'utilisation de la programmation fonctionnelle est plus efficace.
Inconvénients de la programmation fonctionnelle
Comme toutes les données sont immuables, toutes les variables existent tout au long de l'exécution du programme, ce qui consomme beaucoup de ressources. En raison de sa conception inhérente, la performance de la programmation fonctionnelle a toujours été insuffisante. Bien que les langages de programmation fonctionnelle modernes utilisent de nombreuses techniques comme l'évaluation paresseuse pour optimiser la vitesse, ils ne peuvent toujours pas rivaliser avec les programmes orientés objet, bien que ces derniers ne soient pas non plus particulièrement rapides.
Bien que la programmation fonctionnelle existe depuis de nombreuses années, il reste encore beaucoup de problèmes à résoudre pour une utilisation à grande échelle dans des projets d'ingénierie, en particulier pour les projets de grande envergure. Sans une compréhension approfondie de la programmation fonctionnelle, le code peut devenir aussi difficile à comprendre que la programmation orientée objet.
Imaginons que nous devions développer une bibliothèque pour tracer des graphiques de fonctions mathématiques (univariées). Nous pourrions vouloir fournir des fonctionnalités pour tracer divers types de graphiques de fonctions, comme des droites f(x)=ax+b, des paraboles f(x)=ax²+bx+c ou des fonctions trigonométriques f(x)=asinx+b, etc. Comment concevoir les interfaces publiques ? Étant donné que chaque type de fonction a des coefficients (a, b, c, etc.) différents et des structures de construction différentes, il est difficile de fournir une interface统一. Cela pourrait conduire à des méthodes publiques similaires à celles ci-dessous:
// Tracer le graphique d'une fonction linéaire
public void TracerDroite(double pente, double ordonneeOrigine)
{
List<PointF> points = new List<PointF>();
for(double x = -10; x <= 10; x = x + 0.1)
{
PointF p = new PointF(x, pente * x + ordonneeOrigine);
points.Add(p);
}
// Connecter les points
}
// Tracer le graphique d'une fonction parabolique
public void TracerParabole(double coeffA, double coeffB, double coeffC)
{
List<PointF> points = new List<PointF>();
for(double x = -10; x <= 10; x = x + 0.1)
{
PointF p = new PointF(x, coeffA * Math.Pow(x, 2) + coeffB * x + coeffC);
points.Add(p);
}
// Connecter les points
}
...
TracerDroite(3, 4); // Tracer une droite
TracerParabole(1, 2, 3); // Tracer une parabole
Si nous procédons ainsi, pour tracer N types de fonctions différents, nous devrions définir N interfaces. Il est clair que ce n'est pas une approche viable.
Si nous changeons notre manière de penser, puisque nous traçons des graphiques de fonctions, pourquoi passer les coefficients comme paramètres plutôt que de passer directement la fonctoin comme paramètre à l'interface ? Exactement, pour tracer le graphique d'une fonction, nous pouvons simplement passer cette fonction comme paramètre à l'interface. Comme les délégués en C# sont une encapsulation de méthodes (fonctions), nous pouvons implémenter cela en C# comme suit:
public delegate double FonctionADessiner(double x);
// Tracer le graphique d'une fonction
public void DessinerFonction(FonctionADessiner fonction)
{
List<PointF> points = new List<PointF>();
for(double x = -10; x <= 10; x = x + 0.1)
{
PointF p = new PointF(x, fonction(x));
points.Add(p);
}
// Connecter les points
}
...
FonctionADessiner fctDroite =
(FonctionADessiner)((x) => { return 3 * x + 4;}); // Créer une fonction linéaire
DessinerFonction(fctDroite); // Tracer la droite avec coefficients 3 et 4
FonctionADessiner fctParabole =
(FonctionADessiner)((x) => {return 1 * Math.Pow(x, 2) + 2 * x + 3;}); // Créer une fonction parabolique
DessinerFonction(fctParabole); // Tracer la parabole avec coefficients 1, 2 et 3
FonctionADessiner fctSinus =
(FonctionADessiner)((x) => {return 3 * Math.Sin(x) + 4;}); // Créer une fonction sinus
DessinerFonction(fctSinus); // Tracer le graphique de la fonction sinus avec coefficients 3 et 4
Comme indiqué ci-dessus, en passant la fonction (encapsulée dans un délégué) directement comme paramètre à l'interface, nous pouvons uniformiser l'interface. Le type de fonction à tracer est entièrement déterminé par nous-mêmes à l'extérieur de l'interface.
Considérer les fonctions comme des types ordinaires, auxquels on peut assigner des valeurs, les stocker, les passer en paramètres ou même les retourner comme valeurs de retour, est l'un des principes les plus importants de la programmation fonctionnelle.