La programmation réactive et son implémentation

Une autre approche des langages qui facilite la description d'un système, et donc du monde réel.

Les définitions de la programmation réactive sont similaires à celles des mathématiques et s'opposent à la programmation procédurale (ou impérative) qui décrit des listes de commandes.

Par exemple, quand dans un langage procédural classique on écrit::

a = b + c

La valeur a ne dépend pas de b et c mais des valeurs qu'elles ont quand le processeur rencontre cette ligne. Elle ne changera qu'avec un nouvel assignement de la variable a.

En programmation réactive, a devient dépendante de b et c pour la durée de la session. Chaque modification de la valeur de ces variables entraînera un changement de la valeur de a.

Et si ensuite on pose:

b = d + e

la valeur de a dépendra aussi indirectement de celle de d et de e.

Cette approche permet de décrire beaucoup plus intuitivement un système ou un objet complexe en mouvement. Par exemple, si l'on veut décrire un moteur à explosion, on assigne les données de vitesse, poids, accélération, déclivité, freinage à des variables, on peut définir la consommation d'essence en fonction de ces variables, et l'on obtient une variable consommation qui évolue selon chaque changement de chacun de ces paramètres. Il est clair que cette approche convient bien aussi pour définir le comportement d'un robot et son interaction avec l'environnement.

L'approche réactive peut poser des difficultés. Ainsi si l'on définit:

d = a + z

Puisque que a dépend indirectement de d et que d dépend de a, la valeur de a devient difficile à définir. C'est le même problème que l'on aura dans un langage procédural si dans le corps d'une fonction f1 on fait un appel à la fonction f2, et dans le corps de f2, on fait un appel à f1.
Ce fonctionnement est parfaitement valide si l'on ajoute une condition de terminaison, en fonction du résultat, à defaut de quoi l'on entrerait dans une boucle sans fin. C'est une forme de récursivité.

Si l'on transpose la récursivité indirecte en terme de PR, on peut poser que a = b et que b = a + z si l'une des deux définitions est assortie d'une condition, la première sur la valeur de b, la seconde sur la valeur de a.
Mais si les objets du modèle sont une transposition du monde réel, ce n'est pas forcément nécessaire. Cela revient à transposer dans le code une interdépendance qui existe dans le monde réel. Ce qui est en fait le but. Mais cela ne peut fonctionner que dans un système concurrent, à défaut de quoi ces deux définitions fonctionnant en boucle sans fin monopoliseraient toutes les ressources du processeur.

Une autre différence avec la programmation procédurale et qu'une variable ne peut avoir qu'un seul assignement qui décrit toutes ses dépendances, alors que les variables des langages classiques sont modifiées tout au cours du déroulement du programme. Cela ne peut que rendre le code plus compact.

Quand faut-il utiliser plutôt la programmation réactive?

Chaque fois que le problème est plus compliqué à exprimer que la solution.

Prenons le cas d'un système de réservation. Cela se décrit comme un ensemble de cases de réservation, avec d'une part une liste de ressources qui allouent des créneaux horaires, d'autre part une file de clients qui consultent les cases pour choisir un horaire non déjà réservé. On définit chaque case comme étant vide, ou allouées, ou réservée, et chaque valeur dépend d'un évènement extérieur, déclenché par la ressource ou le client. La partie "processus" est simple, et se réduit à enregistrer les entrées des ressources et des clients.

A l'opposé, prenons le cas où l'on a une très grande liste de mots à trier le plus vite possible: le problème est simple, les mots doit être en ordre alphabériques. Pour obtenir ce résultat le plus rapidemment, on fait appel à des algorithmes tels que le Quick Sort ou l'Insertion Sort, donc la programmation procédurale convient mieux ici.

Donc le mode impératif convient mieux pour exprimer des processus et des transformations et le mode déclaratif des interactions.

Programmation réactive vs impérative

A coté des différences de base, qui font que la PI fonctionne en séquence d'états, que les variables dépendent des assignements succéssifs, on doit noter le fait qu'un programme impératif contrôle les opérations, c'est lui qui agit, et même si l'a choisit une procédure en fonction d'entrées extérieures, dans une procédure, l'action vient du programme lui-même.
En PR au contraire, le programme ne fait que refléter les actions et interactions des acteurs, et n'a par lui-même aucune action sur eux. L'action d'un acteur ne dépend que de l'action des autres acteurs et pas du programme.

Programmation réactive vs fonctionnelle

Tous comme la PR, la PF exprime un programme sous forme de relations mathématique et non comme une séquence d'états. Dans un langage déclaratif, tout est décrit exclusivement par des expressions, c'est le cas de la PR. Quand tout est décrit en termes de procédures et de fonctions, et où le résultat de chaque fonction dépend exclusivement de ses arguments (et dans le corps de laquelle on n'accède à aucune variable plus globale), c'est la progammation fonctionnelle.
Un programme fonctionnel agit sur les objets de façon procédurale, mais il le fait de façon différente pour éviter les effets de bord.

Et vs la programmation réactive fonctionnelle

Il s'agit d'un autre paradigme. Cela consiste à ajouter un modèle orienté évènements à un langage fonctionnel, ce qui a été implémenté par exemple dans FrTime conçu sur la base de Scheme. Contrairement à la programmation fonctionelle pure, certains objets ou variables sont mutables, mais leur état ne dépend pas d'assignements dans le corps du programme, ce qui nous ramènerait à la programmation impérative, il dépend d'évènements.

Programmation réactive vs évènements

La PR est en fait assez proche du fonctionnement des évènements, qui s'intègrent dans un langage procédural comme JavaScript. Mais elle est différente en ceci qu'elle permet d'écrire un code plus concis et plus clair.
Si l'on peut écrire var A = B + C, c'est plus simple qu'associer un évènement à A qui lui même dépend d'évènements associés à B et C. On peut associer à A un évènement qui dépend du résultat de la somme des valeurs de B et C et des évènements qui changent la valeur de B et C. Mais quand l'un de ces deux évènements est activé, cela n'active pas la somme de leurs valeurs. Pour ce faire, il faut que les évènements appellent une fonction qui calcule cette somme et que la fonction déclenche un autre évènement quand le résultat est obtenu, lequel est intercepté par A.
La PR peut s'exprimer en PE comme elle peut l'être en PI, puisque de toutes façon est elle réduite en code machine qui à une forme impérative, mais son avantage est que la complexité du code est transmise au compilateur et au runtime.

Programmation réactive et concurrence

Ce type de programmation dans les applications pratiques (et non en théorie) est basé sur la concurrence, en ceci qu'on l'utilisera pour représenter un système où de nombreux acteurs peuvent agir simultanément, ce à quoi elle convient parfaitement.

La programmation réactive dans les langages

La PR n'est implémentée directement dans aucun langage populaire, même pas dans les langages récents. Elle ne l'est pas dans Go, dans Rust, ou dans Dart. Seul Prolog utilise quelque chose de proche avec sa programmation logique.
Mais on tente de l'ajouter sous forme d'extension aux langages procéduraux.
En java on assigne des gestionnaires d'évènements à des propriétés et la même chose peut se faire en JavaScript. Les évènements peuvent s'enchaîner comme les définitions de la PR et produire des résultats indirects.
Mais on ne peut comparer ces procédés et l'imbrication des callbacks de JavaScript avec la simplicité du code de la RP.

Pour JavaScript. React.js est surtout destiné à construire une interface graphique. Il utilise les évènements et ressemble assez peu à la PR. L'article Vraie programmation réactive en JavaScript sur ce site explique comment ajouter très simplement la PR à JavaScript.

Sodium est une bibliothèque qui se dit extension PR pour Java et C++.
Le Reactive Extension est un module C# pour réaliser de la programmation basée sur les évènements.

Aucun de ces langages n'offre les définitions propres à la syntaxe de la PR, ils offrent un faux-semblant qui est plutôt de la programmation par évènements car il faut recourir à un code procédural pour propager la réactivité dans tout le programme.

Comment l'implémenter

Ce que l'on gagne en simplicité dans le code pour le programmeur, on le paie en difficulté pour la réalisation du compilateur. Ce n'est pas sans raison si les langages procéduraux se sont imposés par rapport aux alternatives: le code procédural se traduit simplement en plusieurs lignes de code machine. Pour la RP, c'est différent.
A chaque variable, on doit assigner la liste des variables dont la valeur en dépend directement, de façon a répercuter les changements de valeurs. Un tel changement de valeur n'est plus un simple assignement, en quelque sorte déposer une valeur dans une case mémoire, c'est le déclenchement d'un processus qui recherche les dépendances et recalcule les formules qui impliquent la variable modifiée.

On peut optimiser le code en ne considérant les dépendances que lorsqu'elles sont utilisées, donc quand on modifie b ou c, on ne considère a que si a est utilisé.
Cela s'obtient de deux façons. Soit on ajoute un superviseur qui réagit à chaque changement d'un objet et transmet le résultat aux objets qui en dépendent, soit on ajoute au code de l'objet un code qui envoie un signal quand l'objet est modifié - des instructions supplémentaires à une méthode si on change les propriétés des objets par des méthodes. Ce signal est un message aux objets qui en dépendent.
Dans les deux cas le système dispose d'un filtre pour ne lancer la propagation que quand les objets dépendants sont en cours d'utilisation.

Si l'on compile le langage réactif en code JavaScript et que l'on ne vise pas à la performance, on peut simplifier en utilisant les évènements en backend, avec un code qui génère les définitions de la PR en fonctions et qui associe ces fonctions à chaque évènement, comme expliqué plus haut (PR vs PE).

En conclusion, la programmation réactive est un moyen de faciliter énormément le travail des programmeurs. Elle devrait être implémentée dans chaque nouveau langage, à code de la syntaxe impérative indispensable pour les opérations requérant de la performance.

Par Denis Sureau le 23 janvier 2014.