Fermer

octobre 16, 2022

Programmation fonctionnelle en C# – Une brève considération


Un problème très courant rencontré dans la plupart des codes de programme est la grande complexité. Pour faire face à cela, une excellente alternative est la programmation fonctionnelle, un excellent paradigme qui aide les programmeurs à écrire du code moins susceptible aux erreurs. Consultez cet article pour une introduction au sujet avec des exemples pratiques.

Le but de cet article de blog n’est pas d’approfondir le sujet de la programmation fonctionnelle, mais plutôt d’informer le lecteur sur les différents avantages de la programmation fonctionnelle à travers un bref examen du sujet, d’aborder les principaux sujets et de montrer comment la programmation fonctionnelle peut être appliqué via C#.

Quelques sujets abordés :

  • Que signifie « programmation fonctionnelle » ?
  • C# est-il un langage fonctionnel ?
  • Fonctions pures (avantages et inconvénients)
  • Exemples pratiques de programmation fonctionnelle en C#
  • Utiliser des types immuables
  • Éviter l’utilisation de déclarations
  • Programmation fonctionnelle avec LINQ
  • Fonctions d’ordre supérieur (HOF)

Améliorer quelque chose qui est déjà bon

Si, comme moi, vous avez une certaine expérience du développement de logiciels, il est fort probable que vous ayez déjà rencontré du code qui devait être refactorisé en raison de sa complexité élevée, et la plupart du temps inutile.

La programmation orientée objet (POO) a résolu de nombreux problèmes rencontrés auparavant et apporté d’innombrables possibilités et facilités lors de l’écriture de code, mais ce « don » peut se transformer en une véritable malédiction s’il est mal utilisé.

Quelque chose que j’ai remarqué, c’est que de nombreux développeurs, sur la base de leurs expériences et profitant des possibilités de la POO, ont créé un code totalement empirique, plein d’instructions « if » et « else » sans fin et des classes les plus fantaisistes pour stocker des données en mémoire.

Le résultat est que les systèmes sont de plus en plus modernes mais semblent vieux, car ils utilisent des ressources créées dans les premières versions du langage – non pas que ces ressources ne doivent pas être utilisées, mais il faut toujours considérer leur utilisation.

Une alternative pour éviter ces problèmes et d’autres est l’utilisation de la programmation fonctionnelle. Depuis sa sortie dans les années 2000, de nombreuses fonctionnalités ont été ajoutées, et aujourd’hui on peut enfin dire que C# dispose d’une grande variété d’options pour la programmation fonctionnelle.

Tout au long de cet article, nous aborderons des aspects qui visent à aider le lecteur à écrire du code cohérent, élégant et fonctionnel avec le langage C#.

Que signifie la programmation fonctionnelle ?

La programmation fonctionnelle est un paradigme de programmation déclarative. Dans ce paradigme, les programmes sont construits en composant des fonctions.

Les définitions de fonctions sont des arborescences d’expressions qui mappent des valeurs à d’autres valeurs, au lieu d’une séquence d’instructions impératives qui mettent à jour l’état d’exécution du programme.

En termes simples, la programmation dans un style fonctionnel permet aux développeurs de créer des logiciels avec du code déclaratif grâce à de petites fonctions combinées de manière modulaire, ce qui rend le code cohérent, élégant et facile à comprendre.

Les fonctions pures sont un sous-ensemble de la programmation fonctionnelle qui traite toutes les fonctions comme des fonctions mathématiques déterministes. Ainsi, lorsqu’une fonction pure est appelée avec certains arguments donnés, elle renverra toujours le même résultat et ne peut être affectée par aucun état mutable ni aucun autre effet secondaire. Lorsque vous limitez un programme aux effets secondaires, il a tendance à être moins bogué, plus facile à tester et plus lisible.

En savoir plus sur programmation fonctionnelle en TypeScript et en Javascript.

C# est-il un langage fonctionnel ?

Non, C# est un langage orienté objet qui met l’accent sur les changements d’état grâce à la programmation impérative. Mais cela ne signifie pas que C # ne prend pas en charge la programmation fonctionnelle. Au contraire, les dernières versions du langage montrent à quel point Microsoft est soucieux d’orienter le C# vers la programmation fonctionnelle.

Les expressions LINQ et lambda sont les exemples les plus pertinents en C# dans lesquels nous pouvons utiliser l’approche fonctionnelle car elles ont déjà été développées dans cet esprit, mais il existe de nombreuses autres fonctionnalités en C# qui remplissent cet objectif.

Ci-dessous, nous verrons quelques exemples où nous pouvons remplacer la programmation impérative par la programmation fonctionnelle et comment cela peut bénéficier à notre code au quotidien.

Fonctions pures

Que sont les fonctions pures ?

Une fonction pure est une fonction dont la sortie ne dépend que des arguments qui lui sont passés.
Si une fonction pure est invoquée deux fois en utilisant les mêmes valeurs d’entrée, le résultat obtenu sera la même sortie dans les deux cas.

Avantages de l’utilisation de fonctions pures

Voici quelques-uns des avantages de l’utilisation de fonctions pures :

  • Meilleure compréhension du code
    D’après Robert Martin Nettoyer le code livre, la proportion de temps passé à lire du code est beaucoup plus élevée qu’à l’écrire, donc si nous avons un code facile à comprendre, le temps passé à le maintenir a tendance à être très faible.

  • Débogage facile
    Dans les fonctions pures, nous n’avons pas de complexités créées dans le code impératif. Donc, pour déboguer des fonctions pures, la plupart du temps, cela dépendra uniquement de l’observation des valeurs transmises et de la raison pour laquelle le résultat attendu ne se produit pas.

  • Code facile à tester
    La grande majorité des projets nécessitent aujourd’hui la réalisation de tests unitaires pour s’assurer qu’ils fonctionnent. Lorsque nous écrivons des fonctions pures, nous n’avons aucune dépendance externe, c’est-à-dire que nous n’avons besoin d’aucune sorte de simulation ou de tout autre hack.

Inconvénients des fonctions pures

Bien qu’apportant de nombreux avantages, l’utilisation de fonctions pures apporte également certains inconvénients tels que :

  • Plus grande courbe d’apprentissage
    Par rapport à la programmation orientée objet, la programmation fonctionnelle a une courbe d’apprentissage plus élevée, car il n’y a pas autant de ressources disponibles qu’en POO, en plus du soin qu’il faut prendre lors de l’écriture de fonctions pures.

  • Meilleur traitement
    L’utilisation de valeurs immuables et la récursivité peuvent générer plus de consommation de mémoire, mais cela dépendra beaucoup de chaque scénario.

  • Code supplémentaire
    Comme il n’y a pas de changement d’état, il est nécessaire de répéter le code lors de la copie de valeurs et de leur retour vers de nouveaux objets, mais par rapport à la surcharge de la programmation impérative, les fonctions pures nécessitent beaucoup moins de surcharge.

Exemples pratiques de programmation fonctionnelle en C#

Voici quelques exemples de programmation fonctionnelle en C#. Dans chaque sujet, la forme impérative de mise en œuvre est abordée en premier, puis l’approche fonctionnelle.

Vous pouvez accéder au référentiel avec le code source utilisé dans les exemples ici : Code source.

Utilisation de types immuables

Éviter le changement d’état est essentiel pour la programmation fonctionnelle. Pour cela, une alternative est de créer des types immuables, on garantit ainsi que leurs valeurs ne sont pas modifiées lors de l’exécution. Donc, s’il est nécessaire de les modifier, une nouvelle instance de celui-ci doit être créée.

L’exemple ci-dessous montre d’abord l’utilisation de l’approche impérative où les valeurs des propriétés d’un type mutable sont modifiées pendant l’exécution.

Dans le deuxième exemple, nous avons créé un type immuable avec une méthode qui renvoie une nouvelle instance au lieu de la modifier.

1. Exemple mutable

namespace FPExampleApp;
public class Order
{
    public int UnitPrice { get; set; }
    public int Discount { get; set; }

    public void SpecialCustomer(int unitPrice, int discount)
    {
        UnitPrice = unitPrice;
        Discount += discount;
    }
}
using FPExampleApp;



var order = new Order();
order.UnitPrice = 99;
order.Discount = 10;
order.SpecialCustomer(99, 20); 


2. Exemple immuable

namespace FPExampleApp;
public class ImmutableOrder
{
    public int UnitPrice { get; }
    public int Discount { get; }

    public ImmutableOrder(int unitPrice, int discount)
    {
        UnitPrice = unitPrice;
        Discount = discount;
    }

    public ImmutableOrder SpecialCustomer(int unitPrice, int discount) 
        => new ImmutableOrder(unitPrice, Discount + discount);
}


var immutableOrder = new ImmutableOrder(99, 10);
immutableOrder = immutableOrder.SpecialCustomer(99, 20);


Éviter l’utilisation de déclarations

Dans la mesure du possible, utilisez des expressions au lieu de déclarations. En plus de faciliter l’interprétation du code, les expressions sont beaucoup plus rapides que les déclarations et évitent également la complexité du code.

static string WeatherVerify(double tempInCelsius) 
{
	

	string climate = string.Empty;

	if (tempInCelsius < 20.0)
		climate = "Cold";
	else
		climate = "Perfect";

	return climate;
}

static string ImmutableWeatherVerify(double tempInCelsius) => 
	tempInCelsius < 20.0 ? "Cold" : "Perfect"; 

LINQ—Le maître de la programmation fonctionnelle

LINQ (Language Integrated Query) est le nom d’un ensemble de technologies basées sur l’intégration de capacités de requête directement dans le langage C#.

LINQ a été développé dans un style fonctionnel, et ces dernières années, il a gagné plusieurs améliorations et nouvelles fonctions. Il reprend dans son essence les piliers du langage fonctionnel, comme éviter les changements d’état, en plus d’être implémenté via une syntaxe de requête déclarative. Son utilisation est fortement recommandée lorsqu’il s’agit de programmation fonctionnelle. Vous trouverez ci-dessous quelques exemples d’utilisation de LINQ.

Notez que la première approche utilise le style impératif avec une logique inutile et plusieurs lignes de code, tandis que les deux autres exemples utilisent des expressions LINQ (query et lambda), où le style fonctionnel est utilisé.

int[] scores = { 97, 92, 81, 60 };


var scoreImperative = new List<int>();

foreach (var item in scores)
{
    if (item > 80)
    {
		scoreImperative.Add(item);
    }
}




IEnumerable<int> scoreQuery = from score in scores
							  where score > 80
							  select score;


IEnumerable<int> scoreLambda = scores.Where(score => score > 80);

Fonctions d’ordre supérieur (HOF)

Les fonctions d’ordre supérieur sont des fonctions qui prennent une fonction comme argument ou renvoient une fonction (ou font les deux).

L’opposé de celles-ci sont les fonctions du premier ordre, c’est-à-dire qu’elles ne prennent pas de fonction comme argument et ne renvoient pas de fonction.

Il est courant de trouver des fonctions d’ordre supérieur dans les lambdas, comme dans LINQ Where clause, où un prédicat lui est passé :

var olderUser = users.Where(user => user.Age == 99);

Mais comment s’y prendrait-on pour implémenter une fonction d’ordre supérieur ? L’exemple ci-dessous montre comment créer notre propre Wherede la même manière que LINQ Where:

namespace FPExampleApp
{
    public class User
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public User(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }
}
  • Création de fonction d’ordre supérieur (MyWhere)
var users = new List<User>()
{
	new User("John smith", 99),
    new User("Alice Smith", 99),
};

var olderUser = users.MyWhere(user => user.Age == 99);

foreach (var user in olderUser)
	WriteLine(user?.Name);

static class Helper
{
    public static IEnumerable<T> MyWhere<T>
        (this IEnumerable<T> source, Func<T, bool> predicate) 
	{
		
		foreach (T s in source)
            if (predicate(s))
                yield return s;
    }
}

Notez que la fonction « MyWhere » reçoit une autre fonction en tant qu’argument via le prédicat, en plus d’un IEnumerable (source) qui sera traversé par la fonction. Ainsi, le critère qui détermine quels éléments seront inclus dans la liste est décidé par l’appelant de la fonction, ce qui nous donne une totale liberté pour prédire le résultat.

Conclusion

Comme on le voit tout au long de l’article, la programmation fonctionnelle résout de nombreuses lacunes laissées par la programmation orientée objet, mais ne la remplace en aucun cas. La POO est quelque chose qui a révolutionné la façon dont les logiciels sont construits et permet aux développeurs d’avoir un monde de possibilités.

En utilisant la POO combinée à la programmation fonctionnelle, nous obtenons la capacité d’extraire le maximum qu’un logiciel bien écrit peut offrir, car nous évitons de nombreux problèmes futurs.

Ne vous souciez donc pas de créer des programmes 100% fonctionnels – utilisez simplement des fonctions pures et d’autres ressources disponibles là où vous pouvez les utiliser, et votre code sera à un niveau beaucoup plus élevé.




Source link