J'ai lu l'article Wikipedia sur la programmation réactive . J'ai également lu le petit article sur la programmation réactive fonctionnelle . Les descriptions sont assez abstraites.
- Que signifie la programmation réactive fonctionnelle (FRP) dans la pratique?
- En quoi consiste la programmation réactive (par opposition à la programmation non réactive?)?
Mon expérience est dans les langages impératifs / OO, donc une explication qui se rapporte à ce paradigme serait appréciée.
Réponses:
Si vous voulez vous familiariser avec FRP, vous pouvez commencer avec l'ancien tutoriel Fran de 1998, qui contient des illustrations animées. Pour les articles, commencez par Functional Reactive Animation , puis suivez les liens sur le lien publications sur ma page d'accueil et le lien FRP sur le wiki Haskell .
Personnellement, j'aime réfléchir à ce que signifie le PRF avant d'aborder comment il pourrait être mis en œuvre. (Le code sans spécification est une réponse sans question et donc "même pas faux".) Je ne décris donc pas FRP en termes de représentation / implémentation comme Thomas K le fait dans une autre réponse (graphiques, nœuds, arêtes, tir, exécution, etc). Il y a beaucoup de styles de mise en œuvre possibles, mais aucune mise en œuvre ce que dit FRP est .
Je résonne avec la simple description de Laurence G selon laquelle FRP concerne "des types de données qui représentent une valeur" dans le temps "". La programmation impérative conventionnelle ne capture ces valeurs dynamiques qu'indirectement, par l'état et les mutations. L'histoire complète (passée, présente, future) n'a pas de représentation de première classe. De plus, seules les valeurs évoluant discrètement peuvent être capturées (indirectement), car le paradigme impératif est temporellement discret. En revanche, FRP capture directement ces valeurs évolutives et n'a aucune difficulté à évoluer en continu .
FRP est également inhabituel en ce qu'il est simultané sans aller à l'encontre du nid de rats théorique et pragmatique qui afflige l'impératif de concurrence. Sémantiquement, la simultanéité de FRP est fine , déterminée et continue . (Je parle de sens, pas d'implémentation. Une implémentation peut ou non impliquer la concurrence ou le parallélisme.) La détermination sémantique est très importante pour le raisonnement, à la fois rigoureux et informel. Bien que la concurrence ajoute une énorme complexité à la programmation impérative (en raison de l'entrelacement non déterministe), elle est sans effort dans FRP.
Alors, quel est FRP? Vous auriez pu l'inventer vous-même. Commencez avec ces idées:
Les valeurs dynamiques / évolutives (c'est-à-dire les valeurs "dans le temps") sont des valeurs de première classe en elles-mêmes. Vous pouvez les définir et les combiner, les passer dans et hors des fonctions. J'ai appelé ces choses des "comportements".
Les comportements sont construits à partir de quelques primitives, comme les comportements constants (statiques) et le temps (comme une horloge), puis avec une combinaison séquentielle et parallèle. n comportements sont combinés en appliquant une fonction n-aire (sur des valeurs statiques), "ponctuelle", c'est-à-dire en continu dans le temps.
Pour tenir compte des phénomènes discrets, ayez un autre type (famille) d '"événements", dont chacun a un flux (fini ou infini) d'occurrences. Chaque occurrence a une heure et une valeur associées.
Pour trouver le vocabulaire de composition à partir duquel tous les comportements et événements peuvent être construits, jouez avec quelques exemples. Continuez à déconstruire en morceaux plus généraux / simples.
Pour que vous sachiez que vous êtes sur un terrain solide, donnez à l'ensemble du modèle une base compositionnelle, en utilisant la technique de la sémantique dénotationnelle, ce qui signifie simplement que (a) chaque type a un type mathématique simple et précis correspondant de «significations», et ( b) chaque primitive et opérateur a une signification simple et précise en fonction de la signification des constituants. Ne mélangez jamais les considérations d'implémentation à votre processus d'exploration. Si cette description vous paraît chaotique, consultez (a) Conception dénotative avec les morphismes de classe de type , (b) Programmation réactive fonctionnelle push-pull (en ignorant les bits d'implémentation), et (c) la page des wikibooks Denotational Semantics Haskell. Sachez que la sémantique dénotationnelle se compose de deux parties, de ses deux fondateurs Christopher Strachey et Dana Scott: la partie Strachey plus facile et plus utile et la partie Scott plus difficile et moins utile (pour la conception de logiciels).
Si vous respectez ces principes, je pense que vous obtiendrez quelque chose de plus ou moins dans l'esprit de FRP.
Où ai-je obtenu ces principes? En conception de logiciels, je pose toujours la même question: "qu'est-ce que ça veut dire?". La sémantique dénotationnelle m'a donné un cadre précis pour cette question, et qui correspond à mon esthétique (contrairement à la sémantique opérationnelle ou axiomatique, qui me laissent insatisfait). Alors je me suis demandé quel est le comportement? Je me suis vite rendu compte que la nature temporellement discrète du calcul impératif est une adaptation à un style particulier de machine , plutôt qu'une description naturelle du comportement lui-même. La description précise la plus simple du comportement à laquelle je puisse penser est simplement "fonction du temps (continu)", c'est donc mon modèle. Délicieusement, ce modèle gère la simultanéité déterministe continue avec facilité et grâce.
Mettre en place ce modèle correctement et efficacement a été un véritable défi, mais c'est une autre histoire.
la source
En programmation fonctionnelle pure, il n'y a pas d'effets secondaires. Pour de nombreux types de logiciels (par exemple, tout ce qui a une interaction avec l'utilisateur), les effets secondaires sont nécessaires à un certain niveau.
Une façon d'obtenir un comportement semblable à un effet secondaire tout en conservant un style fonctionnel consiste à utiliser une programmation réactive fonctionnelle. Il s'agit de la combinaison d'une programmation fonctionnelle et d'une programmation réactive. (L'article Wikipedia auquel vous avez lié concerne ce dernier.)
L'idée de base de la programmation réactive est qu'il existe certains types de données qui représentent une valeur "dans le temps". Les calculs qui impliquent ces valeurs évolutives dans le temps auront eux-mêmes des valeurs qui évoluent dans le temps.
Par exemple, vous pouvez représenter les coordonnées de la souris sous la forme d'une paire de valeurs entières dans le temps. Disons que nous avions quelque chose comme (c'est un pseudo-code):
À tout moment, x et y auraient les coordonnées de la souris. Contrairement à la programmation non réactive, nous n'avons besoin d'effectuer cette affectation qu'une seule fois, et les variables x et y resteront "à jour" automatiquement. C'est pourquoi la programmation réactive et la programmation fonctionnelle fonctionnent si bien ensemble: la programmation réactive supprime le besoin de muter les variables tout en vous permettant de faire beaucoup de ce que vous pourriez accomplir avec des mutations variables.
Si nous effectuons ensuite des calculs basés sur cela, les valeurs résultantes seront également des valeurs qui changent au fil du temps. Par exemple:
Dans cet exemple,
minX
sera toujours 16 de moins que la coordonnée x du pointeur de la souris. Avec des bibliothèques réactives, vous pourriez alors dire quelque chose comme:Et une boîte 32x32 sera dessinée autour du pointeur de la souris et la suivra partout où elle se déplace.
Voici un très bon article sur la programmation réactive fonctionnelle .
la source
sqrt(x)
en C avec votre macro, cela ne fait que calculersqrt(mouse_x())
et me redonner un double. Dans un véritable système réactif fonctionnel,sqrt(x)
reviendrait un nouveau "double dans le temps". Si vous deviez essayer de simuler un système FR avec#define
vous auriez à peu près à jurer les variables en faveur des macros. Les systèmes FR ne recalculent généralement les éléments que lorsqu'ils doivent être recalculés, tandis que l'utilisation de macros signifie que vous réévaluerez constamment tout, jusqu'aux sous-expressions.Un moyen facile d'atteindre une première intuition sur ce que c'est que d'imaginer que votre programme est un tableur et que toutes vos variables sont des cellules. Si l'une des cellules d'une feuille de calcul change, toutes les cellules qui font référence à cette cellule changent également. C'est la même chose avec FRP. Imaginez maintenant que certaines des cellules changent d'elles-mêmes (ou plutôt qu'elles proviennent du monde extérieur): dans une situation d'interface graphique, la position de la souris serait un bon exemple.
Cela manque nécessairement beaucoup. La métaphore se décompose assez rapidement lorsque vous utilisez réellement un système FRP. D'une part, il y a généralement des tentatives pour modéliser également des événements discrets (par exemple en cliquant sur la souris). Je ne fais que mettre ceci ici pour vous donner une idée de ce que c'est.
la source
Pour moi, il s'agit de 2 significations différentes du symbole
=
:x = sin(t)
moyens, quix
est un autre nom poursin(t)
. L'écriturex + y
est donc la même chose quesin(t) + y
. La programmation réactive fonctionnelle est comme les mathématiques à cet égard: si vous écrivezx + y
, elle est calculée avec la valeur det
au moment où elle est utilisée.x = sin(t)
est une affectation: cela signifie quex
stocke la valeur desin(t)
pris au moment de l'affectation.la source
x = sin(t)
,x
c'est la valeur desin(t)
pour le donnét
. Ce n'est pas un nom différent poursin(t)
as function. Sinon, ça le seraitx(t) = sin(t)
.2 + 3 = 5
oua**2 + b**2 = c**2
.OK, d'après les connaissances de base et la lecture de la page Wikipédia sur laquelle vous avez pointé, il apparaît que la programmation réactive est quelque chose comme l'informatique de flux de données mais avec des "stimuli" externes spécifiques déclenchant un ensemble de nœuds pour tirer et effectuer leurs calculs.
C'est assez bien adapté à la conception d'interface utilisateur, par exemple, dans laquelle le toucher d'un contrôle d'interface utilisateur (par exemple, le contrôle du volume sur une application de lecture de musique) peut nécessiter de mettre à jour divers éléments d'affichage et le volume réel de la sortie audio. Lorsque vous modifiez le volume (un curseur, disons) qui correspondrait à la modification de la valeur associée à un nœud dans un graphique dirigé.
Divers nœuds ayant des bords à partir de ce nœud de "valeur de volume" seraient automatiquement déclenchés et tous les calculs et mises à jour nécessaires se répercuteraient naturellement dans l'application. L'application "réagit" au stimulus de l'utilisateur. La programmation réactive fonctionnelle ne serait que la mise en œuvre de cette idée dans un langage fonctionnel, ou généralement dans un paradigme de programmation fonctionnelle.
Pour en savoir plus sur le «dataflow computing», recherchez ces deux mots sur Wikipedia ou en utilisant votre moteur de recherche préféré. L'idée générale est la suivante: le programme est un graphe orienté de nœuds, chacun effectuant un calcul simple. Ces nœuds sont connectés les uns aux autres par des liens graphiques qui fournissent les sorties de certains nœuds aux entrées d'autres.
Lorsqu'un nœud se déclenche ou effectue son calcul, les nœuds connectés à ses sorties ont leurs entrées correspondantes "déclenchées" ou "marquées". Tout nœud dont toutes les entrées sont déclenchées / marquées / disponibles se déclenche automatiquement. Le graphique peut être implicite ou explicite selon la manière exacte dont la programmation réactive est implémentée.
Les nœuds peuvent être considérés comme s'exécutant en parallèle, mais ils sont souvent exécutés en série ou avec un parallélisme limité (par exemple, il peut y avoir quelques threads les exécutant). Un exemple célèbre est la Manchester Dataflow Machine , qui (IIRC) a utilisé une architecture de données balisée pour planifier l'exécution des nœuds dans le graphique via une ou plusieurs unités d'exécution. L'informatique de flux de données est assez bien adaptée aux situations dans lesquelles le déclenchement asynchrone de calculs donnant lieu à des cascades de calculs fonctionne mieux que d'essayer de faire exécuter l'exécution par une horloge (ou des horloges).
La programmation réactive importe cette idée de "cascade d'exécution" et semble penser le programme comme un flux de données mais à condition que certains des nœuds soient accrochés au "monde extérieur" et les cascades d'exécution se déclenchent lorsque ces sensitifs -comme les nœuds changent. L'exécution du programme ressemblerait alors à quelque chose d'analogue à un arc réflexe complexe. Le programme peut être ou non fondamentalement sessile entre les stimuli ou s'installer dans un état fondamentalement sessile entre les stimuli.
Une programmation "non réactive" serait une programmation avec une vision très différente du flux d'exécution et des relations avec les entrées externes. Il est probable que cela soit quelque peu subjectif, car les gens seront probablement tentés de dire tout ce qui répond aux entrées externes "réagit" à eux. Mais en regardant l'esprit de la chose, un programme qui interroge une file d'attente d'événements à un intervalle fixe et distribue tous les événements trouvés aux fonctions (ou threads) est moins réactif (car il n'assiste à l'entrée de l'utilisateur qu'à un intervalle fixe). Encore une fois, c'est l'esprit de la chose ici: on peut imaginer mettre une implémentation d'interrogation avec un intervalle d'interrogation rapide dans un système à un niveau très bas et programmer de manière réactive en plus.
la source
Après avoir lu de nombreuses pages sur FRP, je suis finalement tombé sur cette écriture éclairante sur FRP, cela m'a finalement fait comprendre ce qu'est vraiment FRP.
Je cite ci-dessous Heinrich Apfelmus (auteur de Banane réactive).
Donc, à ma connaissance, un programme FRP est un ensemble d'équations:
j
est discret: 1,2,3,4 ...f
dépendt
donc cela incorpore la possibilité de modéliser des stimuli externestous les états du programme sont encapsulés dans des variables
x_i
La bibliothèque FRP prend soin de progresser le temps, autrement dit, en prenant
j
àj+1
.J'explique ces équations beaucoup plus en détail dans cette vidéo.
ÉDITER:
Environ 2 ans après la réponse originale, je suis récemment arrivé à la conclusion que les implémentations FRP ont un autre aspect important. Ils doivent résoudre (et le font généralement) un problème pratique important: l' invalidation du cache .
Les équations pour
x_i
-s décrivent un graphe de dépendance. Lorsque certaines desx_i
modifications à un moment donné,j
toutes les autresx_i'
valeurs nej+1
doivent pas être mises à jour, donc toutes les dépendances n'ont pas besoin d'être recalculées car certainesx_i'
peuvent être indépendantes dex_i
.De plus, les
x_i
-s qui changent peuvent être mis à jour progressivement. Par exemple, considérons une opération de cartef=g.map(_+1)
dans Scala, oùf
etg
sontList
deInts
. Icif
correspond àx_i(t_j)
etg
estx_j(t_j)
. Maintenant, si je lui ajoute un élément,g
il serait inutile d'effectuer l'map
opération pour tous les éléments deg
. Certaines implémentations FRP (par exemple reflex-frp ) visent à résoudre ce problème. Ce problème est également appelé calcul incrémentiel.En d'autres termes, les comportements (les
x_i
-s) dans FRP peuvent être considérés comme des calculs mis en cache. C'est la tâche du moteur FRP d'invalider et de recalculer efficacement ces cache-s (lesx_i
-s) si certains desf_i
-s changent.la source
j+1
". Pensez plutôt aux fonctions du temps continu. Comme Newton, Leibniz et d'autres nous l'ont montré, il est souvent très pratique (et "naturel" au sens littéral) de décrire ces fonctions de manière différentielle, mais continue, en utilisant des intégrales et des systèmes d'ODE. Sinon, vous décrivez un algorithme d'approximation (et un mauvais) au lieu de la chose elle-même.L'article Réactivité fonctionnelle simplement efficace de Conal Elliott ( PDF direct , 233 Ko) est une assez bonne introduction. La bibliothèque correspondante fonctionne également.
Le document est maintenant remplacé par un autre document, la programmation réactive fonctionnelle push-pull ( PDF direct , 286 Ko).
la source
Avertissement: ma réponse est dans le contexte de rx.js - une bibliothèque de «programmation réactive» pour Javascript.
Dans la programmation fonctionnelle, au lieu de parcourir chaque élément d'une collection, vous appliquez des fonctions d'ordre supérieur (HoF) à la collection elle-même. L'idée derrière FRP est donc qu'au lieu de traiter chaque événement individuel, créer un flux d'événements (implémenté avec un observable *) et appliquer des HoF à cela à la place. De cette façon, vous pouvez visualiser le système comme des pipelines de données reliant les éditeurs aux abonnés.
Les principaux avantages de l'utilisation d'un observable sont les suivants:
i) il fait abstraction de l'état de votre code, par exemple, si vous voulez que le gestionnaire d'événement ne soit déclenché que pour chaque 'n événement, ou arrête de se déclencher après les premiers' n 'événements, ou ne commencez à tirer qu'après les premiers événements 'n', vous pouvez simplement utiliser les HoFs (filtrer, takeUntil, sauter respectivement) au lieu de définir, mettre à jour et vérifier les compteurs.
ii) il améliore la localité du code - si vous avez 5 gestionnaires d'événements différents qui changent l'état d'un composant, vous pouvez fusionner leurs observables et définir un seul gestionnaire d'événements sur l'observable fusionné à la place, combinant efficacement 5 gestionnaires d'événements en 1. Cela le rend très Il est facile de raisonner sur les événements de l'ensemble de votre système qui peuvent affecter un composant, car ils sont tous présents dans un seul gestionnaire.
Un Iterable est une séquence consommée paresseusement - chaque élément est tiré par l'itérateur chaque fois qu'il veut l'utiliser, et donc l'énumération est dirigée par le consommateur.
Un observable est une séquence produite paresseusement - chaque élément est poussé vers l'observateur chaque fois qu'il est ajouté à la séquence, et donc l'énumération est dirigée par le producteur.
la source
Mec, c'est une idée géniale! Pourquoi n'ai-je pas découvert cela en 1998? Quoi qu'il en soit, voici mon interprétation du tutoriel Fran . Les suggestions sont les bienvenues, je pense à démarrer un moteur de jeu basé sur cela.
En bref: si chaque composant peut être traité comme un nombre, l'ensemble du système peut être traité comme une équation mathématique, non?
la source
Le livre de Paul Hudak, The Haskell School of Expression , est non seulement une belle introduction à Haskell, mais il passe également pas mal de temps sur FRP. Si vous êtes un débutant avec FRP, je le recommande fortement pour vous donner une idée du fonctionnement de FRP.
Il y a aussi ce qui ressemble à une nouvelle réécriture de ce livre (publié en 2011, mis à jour en 2014), The Haskell School of Music .
la source
D'après les réponses précédentes, il semble que mathématiquement, nous pensons simplement dans un ordre supérieur. Au lieu de penser une valeur x de type X , nous pensons à une fonction x : T → X , où T est le type de temps, que ce soit les nombres naturels, les entiers ou le continuum. Maintenant, quand nous écrivons y : = x + 1 dans le langage de programmation, nous voulons dire l'équation y ( t ) = x ( t ) + 1.
la source
Agit comme une feuille de calcul comme indiqué. Généralement basé sur un cadre piloté par les événements.
Comme pour tous les "paradigmes", sa nouveauté est discutable.
D'après mon expérience des réseaux d'acteurs à flux distribué, il peut facilement être la proie d'un problème général de cohérence des états à travers le réseau de nœuds, c'est-à-dire que vous vous retrouvez avec beaucoup d'oscillation et de piégeage dans des boucles étranges.
Ceci est difficile à éviter car certaines sémantiques impliquent des boucles référentielles ou de la diffusion, et peuvent être assez chaotiques car le réseau d'acteurs converge (ou non) sur un état imprévisible.
De même, certains états peuvent ne pas être atteints, malgré des bords bien définis, car l'état global s'éloigne de la solution. 2 + 2 peut ou non devenir 4 selon le moment où les 2 sont devenus 2 et s'ils sont restés ainsi. Les feuilles de calcul ont des horloges synchrones et une détection de boucle. Les acteurs distribués ne le font généralement pas.
Tout bon amusement :).
la source
J'ai trouvé cette jolie vidéo sur le subreddit Clojure sur FRP. C'est assez facile à comprendre même si vous ne connaissez pas Clojure.
Voici la vidéo: http://www.youtube.com/watch?v=nket0K1RXU4
Voici la source à laquelle la vidéo fait référence dans la seconde moitié: https://github.com/Cicayda/yolk-examples/blob/master/src/yolk_examples/client/autocomplete.cljs
la source
Cet article d'Andre Staltz est l'explication la meilleure et la plus claire que j'ai vue jusqu'à présent.
Quelques citations de l'article:
Voici un exemple des diagrammes fantastiques qui font partie de l'article:
la source
Il s'agit de transformations mathématiques de données dans le temps (ou d'ignorer le temps).
En code, cela signifie pureté fonctionnelle et programmation déclarative.
Les bogues d'état sont un énorme problème dans le paradigme impératif standard. Divers bits de code peuvent changer un état partagé à différents "moments" dans l'exécution des programmes. C'est difficile à gérer.
Dans FRP, vous décrivez (comme dans la programmation déclarative) comment les données se transforment d'un état à un autre et ce qui les déclenche. Cela vous permet d'ignorer le temps car votre fonction réagit simplement à ses entrées et utilise leurs valeurs actuelles pour en créer une nouvelle. Cela signifie que l'état est contenu dans le graphe (ou arbre) des nœuds de transformation et est fonctionnellement pur.
Cela réduit considérablement la complexité et le temps de débogage.
Pensez à la différence entre A = B + C en mathématiques et A = B + C dans un programme. En mathématiques, vous décrivez une relation qui ne changera jamais. Dans un programme, il est dit que "En ce moment" A est B + C. Mais la commande suivante pourrait être B ++, auquel cas A n'est pas égal à B + C. En mathématiques ou en programmation déclarative, A sera toujours égal à B + C, peu importe l'heure à laquelle vous le demandez.
Donc, en supprimant les complexités de l'état partagé et en modifiant les valeurs au fil du temps. Votre programme est beaucoup plus facile à raisonner.
Un EventStream est un EventStream + une fonction de transformation.
Un comportement est un EventStream + une valeur en mémoire.
Lorsque l'événement se déclenche, la valeur est mise à jour en exécutant la fonction de transformation. La valeur que cela produit est stockée dans la mémoire des comportements.
Les comportements peuvent être composés pour produire de nouveaux comportements qui sont une transformation de N autres comportements. Cette valeur composée sera recalculée lorsque les événements (comportements) d'entrée se déclencheront.
"Étant donné que les observateurs sont apatrides, nous avons souvent besoin de plusieurs d'entre eux pour simuler une machine d'état comme dans l'exemple de glisser. Nous devons enregistrer l'état où il est accessible à tous les observateurs impliqués, comme dans le chemin variable ci-dessus."
Citation de - Deprecating The Observer Pattern http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf
la source
L'explication courte et claire sur la programmation réactive apparaît sur Cyclejs - Programmation réactive , elle utilise des échantillons simples et visuels.
C'est un bon point de départ, pas une source complète de connaissances. De là, vous pouvez passer à des documents plus complexes et plus profonds.
la source
Découvrez Rx, Reactive Extensions for .NET. Ils soulignent qu'avec IEnumerable, vous "tirez" essentiellement d'un flux. Les requêtes Linq sur IQueryable / IEnumerable sont des opérations d'ensemble qui «aspirent» les résultats d'un ensemble. Mais avec les mêmes opérateurs sur IObservable, vous pouvez écrire des requêtes Linq qui «réagissent».
Par exemple, vous pouvez écrire une requête Linq comme (à partir de m dans MyObservableSetOfMouseMovements où mX <100 et mY <100 sélectionnent un nouveau point (mX, mY)).
et avec les extensions Rx, c'est tout: vous avez un code d'interface qui réagit au flux entrant de mouvements de souris et dessine chaque fois que vous êtes dans la zone 100,100 ...
la source
FRP est une combinaison de programmation fonctionnelle (le paradigme de programmation construit sur l'idée que tout est une fonction) et le paradigme de programmation réactive (construit sur l'idée que tout est un flux (observateur et philosophie observable)). Il est censé être le meilleur des mondes.
Consultez le post d'Andre Staltz sur la programmation réactive pour commencer.
la source