Qu'est-ce que la programmation réactive (fonctionnelle)?

1148

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.

  1. Que signifie la programmation réactive fonctionnelle (FRP) dans la pratique?
  2. 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.

JtR
la source
159
voici un gars avec une imagination active et de bonnes compétences en narration. paulstovell.com/reactive-programming
melaos
39
Quelqu'un a vraiment besoin d'écrire une "Programmation réactive fonctionnelle pour les nuls" pour tous les autodidactes ici. Chaque ressource que j'ai trouvée, même Elm, semble supposer que vous avez obtenu un Master en CS au cours des cinq dernières années. Ceux qui connaissent bien la FRP semblent avoir complètement perdu la capacité de voir la question du point de vue naïf, ce qui est essentiel pour l'enseignement, la formation et l'évangélisation.
TechZen
26
Une autre excellente introduction FRP: L'introduction à la programmation réactive que vous avez manquée par mon collègue André
Jonik
5
L'un des meilleurs que j'ai vus, basé sur l'exemple: gist.github.com/staltz/868e7e9bc2a7b8c1f754
Razmig
2
Je trouve l'analogie de la feuille de calcul très utile comme première impression grossière (voir la réponse de Bob: stackoverflow.com/a/1033066/1593924 ). Une cellule de feuille de calcul réagit aux changements dans d'autres cellules (tire) mais n'atteint pas et ne change pas les autres (ne pousse pas). Le résultat final est que vous pouvez changer une cellule et plusieurs millions de mises à jour «indépendamment» de leurs propres écrans.
Jon Coombs

Réponses:

931

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.

Conal
la source
78
Je connaissais la programmation réactive fonctionnelle. Cela semble lié à mes propres recherches (dans les graphiques statistiques interactifs) et je suis sûr que beaucoup d'idées seraient utiles pour mon travail. Cependant, je trouve qu'il est très difficile de dépasser le langage - dois-je vraiment apprendre la «sémantique dénotationnelle» et les «morphismes de classe de type» pour comprendre ce qui se passe? Une introduction générale du sujet au public serait très utile.
hadley
212
@Conal: vous savez clairement de quoi vous parlez, mais votre langue suppose que j'ai un doctorat en mathématiques computationnelles, ce que je n'ai pas. J'ai une formation en ingénierie des systèmes et plus de 20 ans d'expérience avec les ordinateurs et les langages de programmation, mais je pense que votre réponse me laisse perplexe. Je vous mets au défi de republier votre réponse en anglais ;-)
mindplay.dk
50
@ minplay.dk: Vos remarques ne me donnent pas grand-chose sur ce que vous ne comprenez pas en particulier, et je suis peu enclin à faire des suppositions folles sur le sous-ensemble particulier d'anglais que vous recherchez. Cependant, je vous invite à dire précisément sur quels aspects de mon explication ci-dessus vous faites trébucher, afin que moi et d'autres puissent vous aider. Par exemple, y a-t-il des mots particuliers que vous aimeriez définir ou des concepts pour lesquels vous souhaitez ajouter des références? J'aime vraiment améliorer la clarté et l'accessibilité de mon écriture - sans la simplifier.
Conal
27
"Détermination" / "Déterminer" signifie qu'il y a une seule valeur correcte bien définie. En revanche, presque toutes les formes de simultanéité impérative peuvent donner des réponses différentes, selon un planificateur ou que vous cherchiez ou non, et elles peuvent même se bloquer. «Sémantique» (et plus spécifiquement «dénotationnel») fait référence à la valeur («dénotation») d'une expression ou d'une représentation, contrairement à «opérationnelle» (comment la réponse est calculée ou combien d'espace et / ou de temps est consommé par quoi type de machine).
Conal
18
Je suis d'accord avec @ mindplay.dk bien que je ne puisse pas me vanter d'avoir été sur le terrain pendant très longtemps. Même si vous sembliez savoir de quoi vous parliez, cela ne m'a pas donné une compréhension rapide, brève et simple de ce que c'est, car je suis assez gâté pour m'attendre à SO. Cette réponse m'a principalement conduit à une tonne de nouvelles questions sans vraiment répondre à ma première. J'espère que partager l'expérience d'être encore relativement ignorant dans le domaine peut vous donner un aperçu de la simplicité et de la brièveté dont vous avez vraiment besoin. Je viens d'un milieu similaire à l'OP, btw.
Aske B.
739

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):

x = <mouse-x>;
y = <mouse-y>;

À 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:

minX = x - 16;
minY = y - 16;
maxX = x + 16;
maxY = y + 16;

Dans cet exemple, minXsera 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:

rectangle(minX, minY, maxX, maxY)

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 .

Laurence Gonsalves
la source
25
La programmation réactive est donc une forme de programmation déclarative?
troelskn
31
> La programmation réactive est-elle donc une forme de programmation déclarative? La programmation réactive fonctionnelle est une forme de programmation fonctionnelle, qui est une forme de programmation déclarative.
Conal
7
@ user712092 Pas vraiment, non. Par exemple, si j'appelle sqrt(x)en C avec votre macro, cela ne fait que calculer sqrt(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 #definevous 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.
Laurence Gonsalves
4
"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." Et peut-être seulement au niveau de la mise en œuvre. Il y a beaucoup d'effets secondaires dans l'implémentation d'une programmation fonctionnelle pure et paresseuse, et l'un des succès du paradigme est de garder bon nombre de ces effets hors du modèle de programmation. Mes propres incursions dans les interfaces utilisateur fonctionnelles suggèrent qu'elles peuvent également être entièrement programmées sans effets secondaires.
Conal
4
@tieTYT x n'est jamais réaffecté / muté. La valeur de x est la séquence de valeurs dans le temps. Une autre façon de voir les choses est qu'au lieu que x ait une valeur "normale", comme un nombre, la valeur de x est (conceptuellement) une fonction qui prend du temps comme paramètre. (C'est un peu une simplification excessive. Vous ne pouvez pas créer de valeurs temporelles qui vous permettraient de prédire l'avenir de choses comme la position de la souris.)
Laurence Gonsalves
144

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
3
Un exemple extrêmement pertinent. C'est génial d'avoir les trucs théoriques, et peut-être que certaines personnes en comprennent les implications sans avoir recours à un exemple de mise à la terre, mais je dois commencer par ce que cela fait pour moi, pas ce que c'est abstraitement. Ce que je n'ai que récemment (à partir des discussions Rx de Netflix!), C'est que RP (ou Rx, de toute façon), fait de ces "valeurs changeantes" la première classe et vous permet de raisonner à leur sujet, ou d'écrire des fonctions qui font des choses avec elles. Écrivez des fonctions pour créer des feuilles de calcul ou des cellules, si vous le souhaitez. Et il gère quand une valeur se termine (disparaît) et vous permet de nettoyer automatiquement.
Benjohn
Cet exemple souligne la différence entre la programmation événementielle et l'approche réactive, où vous déclarez simplement les dépendances pour utiliser le routage intelligent.
kinjelom
131

Pour moi, il s'agit de 2 significations différentes du symbole =:

  1. En mathématiques x = sin(t)moyens, qui xest un autre nom pour sin(t). L'écriture x + yest donc la même chose que sin(t) + y. La programmation réactive fonctionnelle est comme les mathématiques à cet égard: si vous écrivez x + y, elle est calculée avec la valeur de tau moment où elle est utilisée.
  2. Dans les langages de programmation de type C (langages impératifs), x = sin(t)est une affectation: cela signifie que xstocke la valeur de sin(t) pris au moment de l'affectation.
user712092
la source
5
Bonne explication. Je pense que vous pourriez également ajouter que le "temps" au sens de FRP est normalement "tout changement par rapport à l'entrée externe". Chaque fois qu'une force externe modifie une entrée de FRP, vous avez avancé le «temps» et recalculez tout ce qui est affecté par le changement.
Didier A.
4
En mathématiques x = sin(t), xc'est la valeur de sin(t)pour le donné t. Ce n'est pas un nom différent pour sin(t)as function. Sinon, ça le serait x(t) = sin(t).
Dmitri Zaitsev
+ Le signe Dmitri Zaitsev Equals a plusieurs significations en mathématiques. L'un d'eux est que chaque fois que vous voyez le côté gauche, vous pouvez l' échanger avec le côté droit. Par exemple 2 + 3 = 5ou a**2 + b**2 = c**2.
user712092
71

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.

Thomas Kammeyer
la source
1
OK, il y a de bonnes réponses ci-dessus maintenant. Dois-je supprimer mon message? Si je vois deux ou trois personnes dire qu'il n'ajoute rien, je le supprimerai à moins que son nombre utile augmente. Il ne sert à rien de le laisser ici, sauf s'il ajoute quelque chose de valeur.
Thomas Kammeyer
3
vous avez mentionné le flux de données, ce qui ajoute une certaine valeur à mon humble avis.
Rainer Joswig
C'est ce que QML est censé être, semble-t-il;)
mlvljr
3
Pour moi, cette réponse était la plus facile à comprendre, en particulier parce que l'utilisation d'analogues naturels comme «onduler dans l'application» et «nœuds sensoriels». Génial!
Akseli Palén
1
malheureusement, le lien Manchester Dataflow Machine est mort.
Pac0
65

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).

Quelle est l'essence de la programmation réactive fonctionnelle?

Une réponse commune serait que «FRP consiste à décrire un système en termes de fonctions variant dans le temps au lieu d'un état mutable», et ce ne serait certainement pas faux. C'est le point de vue sémantique. Mais à mon avis, la réponse la plus profonde et la plus satisfaisante est donnée par le critère purement syntaxique suivant:

L'essence de la programmation réactive fonctionnelle est de spécifier complètement le comportement dynamique d'une valeur au moment de la déclaration.

Par exemple, prenez l'exemple d'un compteur: vous avez deux boutons étiquetés «Up» et «Down» qui peuvent être utilisés pour augmenter ou diminuer le compteur. Impérativement, vous devez d'abord spécifier une valeur initiale, puis la modifier chaque fois qu'un bouton est enfoncé; quelque chose comme ça:

counter := 0                               -- initial value
on buttonUp   = (counter := counter + 1)   -- change it later
on buttonDown = (counter := counter - 1)

Le fait est qu'au moment de la déclaration, seule la valeur initiale du compteur est spécifiée; le comportement dynamique de counter est implicite dans le reste du texte du programme. En revanche, la programmation réactive fonctionnelle spécifie l'ensemble du comportement dynamique au moment de la déclaration, comme ceci:

counter :: Behavior Int
counter = accumulate ($) 0
            (fmap (+1) eventUp
             `union` fmap (subtract 1) eventDown)

Chaque fois que vous voulez comprendre la dynamique du compteur, il vous suffit de regarder sa définition. Tout ce qui peut lui arriver apparaîtra à droite. Ceci est très différent de l'approche impérative où les déclarations ultérieures peuvent changer le comportement dynamique des valeurs précédemment déclarées.

Donc, à ma connaissance, un programme FRP est un ensemble d'équations: entrez la description de l'image ici

j est discret: 1,2,3,4 ...

fdépend tdonc cela incorpore la possibilité de modéliser des stimuli externes

tous 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 des x_imodifications à un moment donné, jtoutes les autres x_i'valeurs ne j+1doivent pas être mises à jour, donc toutes les dépendances n'ont pas besoin d'être recalculées car certaines x_i'peuvent être indépendantes de x_i.

De plus, les x_i-s qui changent peuvent être mis à jour progressivement. Par exemple, considérons une opération de carte f=g.map(_+1)dans Scala, où fet gsont Listde Ints. Ici fcorrespond à x_i(t_j)et gest x_j(t_j). Maintenant, si je lui ajoute un élément, gil serait inutile d'effectuer l' mapopération pour tous les éléments de g. 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 (les x_i-s) si certains des f_i-s changent.

jhegedus
la source
4
J'étais là avec toi jusqu'à ce que tu partes avec des équations discrètes . L'idée fondatrice du FRP était le temps continu , où il n'y a pas de " 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.
Conal
Le langage HTML de gabarit et de contraintes de mise en page layx semble exprimer des éléments de FRP.
@Conal, cela me fait me demander en quoi le FRP est différent des ODE. En quoi diffèrent-ils?
jhegedus
@jhegedus Dans cette intégration (éventuellement récursive, c'est-à-dire, les ODE) fournit l'un des éléments constitutifs de FRP, pas l'intégralité. Chaque élément du vocabulaire FRP (y compris, mais sans s'y limiter, l'intégration) est expliqué avec précision en termes de temps continu. Cette explication est-elle utile?
Conal
29

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 observable est le double d'un itérable.

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.

tldr
la source
1
Merci beaucoup pour cette définition simple d'un observable et sa différenciation des itérables. Je pense qu'il est souvent très utile de comparer un concept complexe avec son concept dual bien connu pour acquérir une vraie compréhension.
2
"Donc, l'idée derrière FRP est qu'au lieu de traiter chaque événement individuel, créer un flux d'événements (mis en œuvre avec un observable *) et y appliquer des HoF à la place." Je peux me tromper, mais je crois que ce n'est pas réellement FRP mais plutôt une belle abstraction sur le modèle de conception Observer qui permet des opérations fonctionnelles via HoF (ce qui est génial!) Tout en étant destiné à être utilisé avec du code impératif. Discussion sur le sujet - lambda-the-ultimate.org/node/4982
nqe
18

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.

import pygame
from pygame.surface import Surface
from pygame.sprite import Sprite, Group
from pygame.locals import *
from time import time as epoch_delta
from math import sin, pi
from copy import copy

pygame.init()
screen = pygame.display.set_mode((600,400))
pygame.display.set_caption('Functional Reactive System Demo')

class Time:
    def __float__(self):
        return epoch_delta()
time = Time()

class Function:
    def __init__(self, var, func, phase = 0., scale = 1., offset = 0.):
        self.var = var
        self.func = func
        self.phase = phase
        self.scale = scale
        self.offset = offset
    def copy(self):
        return copy(self)
    def __float__(self):
        return self.func(float(self.var) + float(self.phase)) * float(self.scale) + float(self.offset)
    def __int__(self):
        return int(float(self))
    def __add__(self, n):
        result = self.copy()
        result.offset += n
        return result
    def __mul__(self, n):
        result = self.copy()
        result.scale += n
        return result
    def __inv__(self):
        result = self.copy()
        result.scale *= -1.
        return result
    def __abs__(self):
        return Function(self, abs)

def FuncTime(func, phase = 0., scale = 1., offset = 0.):
    global time
    return Function(time, func, phase, scale, offset)

def SinTime(phase = 0., scale = 1., offset = 0.):
    return FuncTime(sin, phase, scale, offset)
sin_time = SinTime()

def CosTime(phase = 0., scale = 1., offset = 0.):
    phase += pi / 2.
    return SinTime(phase, scale, offset)
cos_time = CosTime()

class Circle:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius
    @property
    def size(self):
        return [self.radius * 2] * 2
circle = Circle(
        x = cos_time * 200 + 250,
        y = abs(sin_time) * 200 + 50,
        radius = 50)

class CircleView(Sprite):
    def __init__(self, model, color = (255, 0, 0)):
        Sprite.__init__(self)
        self.color = color
        self.model = model
        self.image = Surface([model.radius * 2] * 2).convert_alpha()
        self.rect = self.image.get_rect()
        pygame.draw.ellipse(self.image, self.color, self.rect)
    def update(self):
        self.rect[:] = int(self.model.x), int(self.model.y), self.model.radius * 2, self.model.radius * 2
circle_view = CircleView(circle)

sprites = Group(circle_view)
running = True
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            running = False
    screen.fill((0, 0, 0))
    sprites.update()
    sprites.draw(screen)
    pygame.display.flip()
pygame.quit()

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?

Dan Ross
la source
1
C'est un peu tard, mais de toute façon ... Frag est un jeu utilisant FRP .
arx
14

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 .

cjs
la source
10

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 : TX , 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.

Yuning
la source
9

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 :).

emperorz
la source
7

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:

La programmation réactive est la programmation avec des flux de données asynchrones.

En plus de cela, vous disposez d'une incroyable boîte à outils de fonctions pour combiner, créer et filtrer n'importe lequel de ces flux.

Voici un exemple des diagrammes fantastiques qui font partie de l'article:

Cliquez sur le diagramme de flux d'événements

Géant vert
la source
5

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

Jay Shepherd
la source
C'est exactement ce que je pense de la programmation déclarative, et vous décrivez simplement l'idée mieux que moi.
neevek
2

L'explication courte et claire sur la programmation réactive apparaît sur Cyclejs - Programmation réactive , elle utilise des échantillons simples et visuels.

Un [module / composant / objet] est réactif signifie qu'il est entièrement responsable de la gestion de son propre état en réagissant aux événements externes.

Quel est l'avantage de cette approche? C'est une inversion de contrôle , principalement parce que [module / composant / objet] est responsable de lui-même, améliorant l'encapsulation en utilisant des méthodes privées contre des méthodes publiques.

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.

pdorgambide
la source
0

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 ...

Sentinelle
la source
0

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.

Krishna Ganeriwal
la source