Programmation réactive

En informatique, la programmation réactive est un paradigme de programmation visant à conserver une cohérence d'ensemble en propageant les modifications d'une source réactive (modification d'une variable, entrée utilisateur, etc.) aux éléments dépendants de cette source.

Historique modifier

La programmation réactive est née à partir du patron de conception observateur afin de corriger ses défauts[1], parmi lesquels :

Encapsulation
Le patron de conception Observateur est souvent utilisé pour faire des machines à états. Malheureusement, les états sont souvent sauvegardés dans des variables qui doivent être visibles par tous les observateurs[2].
Manque de composabilité
Le code qui en résulte n'est pas assez composable. Par exemple, il n'est pas toujours facile d'ajouter ou de supprimer un glisser-déposer (drag and drop) à un élément lorsqu'il y a beaucoup d'observateurs qui ont plus ou moins la même fonctionnalité[2].
Lisibilité réduite
Il est difficile de faire correspondre les entrées aux sorties[3] (syndrome du plat de spaghettis).
Gestion des ressources
La vie d'un observateur doit être spécifiée explicitement, ce qui peut provoquer une mauvaise gestion des ressources. Dans certains cas, pour des soucis de performances, il est utile de n'activer un observateur qu'au moment où nous en avons besoin. Exemple : si l'utilisateur n'utilise pas le glisser-déposer (drag and drop), il est alors inutile de récupérer les coordonnées de la souris sans cesse[4].
Séparation des préoccupations
Il faut diviser le code de sorte que chaque méthode ait sa propre fonctionnalité, et que celle-ci soit unique, autrement dit, il faut respecter le SRP (Single Responsible Principle). Exemple : Dans le cas d'une application qui sert à dessiner avec la souris, les observateurs de l'application ne vont pas que construire le chemin de la souris mais vont aussi appeler des méthodes pour l'afficher, ce qui n'est pas une bonne pratique[5].
Cohérence des données
Avec le patron de conception Observateur, il n'y a aucune garantie que les données restent cohérentes[5].
Uniformité
Le fait que la mise en place des différents observateurs est effectuée par plusieurs méthodes, diminue l'uniformité du code[4].

Importance de la programmation réactive modifier

La plupart des applications contemporaines sont réactives, c'est-à-dire qu'elles répondent, en calculant, à des événements qui leur parviennent[1].

Cela permet aux utilisateurs d'avoir une meilleure interaction avec le système, une réponse beaucoup plus rapidement et donc une satisfaction[3].

La réactivité est désormais présente partout, et principalement dans les interfaces graphiques[1].

Les applications réactives sont importantes aujourd'hui car elles doivent être :

Disponibles
le système doit répondre rapidement quoi qu'il arrive.
Résilientes
le système doit toujours rester disponible, même en cas d'erreur.
Souples
le système doit continuer à vivre même s'il est surchargé.
Orientées messages
le système utilise des messages asynchrones.

Ce qui correspond aux exigences des utilisateurs d'aujourd'hui[6].

Fonctionnement modifier

Description générale modifier

Chaque variable est avant tout initialisée lors de l'analyse des expressions et les dépendances entre les variables sont enregistrées, de sorte que, dès qu'une variable subit une mise à jour (événement), alors toutes les variables qui en dépendent, subissent elles aussi une mise à jour de leur valeur. Par la suite, elles peuvent, elles aussi, déclencher la mise à jour d'autres variables et ainsi de suite[1].

La programmation réactive fait alors en sorte que chaque expression soit correcte à tout instant[1].

Par exemple :

a := 50
b := a + 22
c := a + 6
d := b + c

Après avoir analysé les expressions, voici les résultats :

a b c d
50 72 56 128

Et voici les dépendances :

a b c d
a a b , c

Supposons désormais que la valeur de a change et passe à 10.

En programmation réactive, puisque b et c dépendent de a, alors b et c vont être automatiquement mises à jour.

a b c d
10 32 16 128

De la même manière, b et c viennent d'être mis à jour. Par conséquent, puisque d dépend de b et c, alors d subira lui aussi une mise à jour.

a b c d
10 32 16 48

Il suffit d'une seule modification de valeur pour que la variable se mette à jour.

Concepts modifier

La programmation réactive est basée sur des concepts comme des variations des valeurs dans le temps, des flots d’événements pour modéliser les mises à jour discrètes, des suivis de dépendances, et des propagations automatiques du changement[1].

Valeurs qui varient dans le temps modifier

À tout instant t de l'exécution du programme, toutes les expressions du programme doivent rester correctes. Par conséquent, les valeurs des variables évoluent dans le temps au fur et à mesure de l'exécution du programme de manière automatique et discrète[1].

Flots d'événements modifier

Lorsqu'une variable change de valeur, toutes les variables qui dépendent d'elle sont mises à jour, ce qui nécessite un événement / message.

Il est important en programmation réactive d'écouter tous les événements qui peuvent survenir.

Suivis de dépendances modifier

Les dépendances entre les variables, directes ou indirectes, doivent être suivies. Si une nouvelle dépendance fait son apparition, alors elle devra être sauvegardée afin de pouvoir propager dans un futur proche, d'éventuelles mises à jour de valeurs[1].

Propagations automatiques du changement modifier

Lorsqu'une variable subit une mise à jour, toutes les autres variables qui en dépendent doivent être informées afin qu'elles puissent se mettre à jour.

La propagation doit se poursuivre automatiquement jusqu'à ce que toutes les variables qui en dépendent directement ou indirectement soient à jour[1].

Langages de programmation réactifs modifier

Les langages de programmation réactive entrent dans la catégorie des langages orientés flots de contrôle[7].

Apports et limitations modifier

Apports modifier

  • Il n'est plus possible d'avoir des bugs à cause d'un oubli de mise à jour d'une valeur[1].
  • Gain de temps à l'exécution[3].
  • Certaines applications sont plus simples à écrire en programmation réactive[9].
  • Le programmeur n'a plus à se soucier de l'ordre de déclenchement des événements[10].

Limitations modifier

  • La programmation réactive est difficile à implémenter[3].
  • La programmation réactive est asynchrone, donc, il est difficile de comprendre et de suivre les flux[1].
  • Difficile de coder un compilateur.

Approches modifier

Observateur (patron de conception) modifier

L’approche traditionnelle pour implémenter des applications réactives est le patron de conception observateur qui sépare les producteurs d’événements (observables) des consommateurs d’événements (observateurs)[1].

Programmation orientée objet modifier

Il s'agit ici, à la fois d'utiliser de la programmation réactive et de la programmation orientée objet.

Méthodes et attributs modifier

L'approche qui semble la plus naturelle est de remplacer pour chaque objet les méthodes et les attributs par des réactions. Ainsi, les attributs se mettront tout seuls à jour.

Par exemple, il est possible de faire un mutateur qui ne prend aucune valeur en paramètre et qui met à jour un attribut.

À la demande modifier

Le principe de cette approche est de faire en sorte de ne mettre à jour les valeurs qu'au moment où on les demande. Seules les entrées sont sauvegardées en mémoire et les sorties sont recalculées à chaque appel[11].

Cache avec invalidation modifier

Recalculer les sorties à chaque fois qu'on les demande est trop inefficace, surtout lorsqu'on travaille avec de grosses données ou si les calculs sont coûteux. Le but de cette approche est d'utiliser une sorte de cache qui permet d'éviter de recalculer inutilement certaines valeurs. Par conséquent, dès qu'on modifie une donnée alors on invalide de manière que dès qu'on demandera la valeur, on sera obligé de recalculer. S'il n'y a eu aucune invalidation, alors on retourne la donnée du cache[12].

Suivre les dépendances modifier

Il y a ici, deux approches différentes :

  • On peut suivre les dépendances et utiliser l'approche à la demande en même temps : Dès qu'on demande une valeur, on va remonter dans les dépendances pour calculer et retourner la bonne valeur[13].
  • On peut suivre les dépendances et utiliser l'approche du cache : Dès qu'on demande une valeur, on la retourne. Si on modifie une valeur, on fait en sorte que tout soit à jour en utilisant les dépendances pour minimiser les mises à jour[13].

Mises à jour incrémentales modifier

Pour des grosses structures de données, ou des données assez importantes, le fait d'écraser entièrement les données pour les mettre à jour est trop coûteux.

Au lieu de tout recalculer, cette approche consiste à bien cibler les données à mettre à jour pour ne se contenter de ne calculer à nouveau que ce qui a changé réellement[14].

Accumulation des changements modifier

Cette approche consiste à sauvegarder toutes les mises à jour (tous les changements de valeur) et d'attendre la demande d'une valeur pour appliquer toutes les mises à jour d'un coup associée à cette valeur[15].

Programmation réactive fonctionnelle modifier

La programmation réactive fonctionnelle est une approche de la programmation réactive[16].

Exemples d'utilisation modifier

  • Les tableurs utilisent notamment ce style de programmation pour garder la cohérence des cellules. Ainsi une cellule contenant "= A1 + 10" sera automatiquement recalculée si A1 est modifiée.
  • Ce paradigme est aussi utile dans le cadre d'une architecture MVC pour mettre la vue à jour lorsque le modèle est modifié (ou inversement)[1].
  • Il est également utilisé dans des applications temps-réel, par exemple avec le framework Meteor[17].
  • La programmation réactive est aussi utilisée dans les interfaces graphiques, pour mettre à jour les coordonnées de la souris en temps réel[1].
  • Peut être utilisé pour des applications web, c'est-à-dire, pour répondre à des signaux provenant du réseau[18].

Bibliographie modifier

  • (en) Guido Salvaneschi, Alessandro Margara et Giordano Tamburrelli, Reactive Programming: a Walkthrough, , 2 p.
  • (en) Guido Salvaneschi et Mira Mezini, Towards Reactive Programming for Object-oriented, , 36 p.
  • (en) Ingo Maier et Martin Odersky, Deprecating the Observer Pattern with Scala.React, 20 p.
  • Frederic Dabrowski, Programmation Réactive Synchrone, Langage et Contrôle des Ressources, 119 p.
  • (en) ENGINEER BAINOMUGISHA, ANDONI LOMBIDE CARRETON, TOM VAN CUTSEM, STIJN MOSTINCKX et WOLFGANG DE MEUTER, A Survey on Reactive Programming, 35 p.
  • (en) Ingo Maier, Tiark Rompf et Martin Odersky, Deprecating the Observer Pattern, 18 p.
  • (en) Alan Jeffrey, Functional Reactive Programming with Liveness Guarantees, , 11 p.

Liens externes modifier

Jonas Bonér, Dave Farley, Roland Kuhn et Martin Thompson, « Le Manifeste Réactif », (consulté le ).

Références modifier