web-dev-qa-db-fra.com

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

J'ai lu l'article de Wikipedia sur programmation réactive . J'ai aussi lu le petit article sur programmation réactive fonctionnelle . Les descriptions sont assez abstraites.

  1. Qu'est-ce que la programmation réactive fonctionnelle (PRF) signifie en pratique?
  2. En quoi consiste la programmation réactive (par opposition à la programmation non réactive?)?

Mon expérience est dans les langues impératives/OO, donc une explication liée à ce paradigme serait appréciée.

1149
JtR

Si vous voulez avoir une idée de FRP, vous pouvez commencer par l'ancien tutoriel en français de 1998, qui contient des illustrations animées. Pour les articles, commencez par Animation fonctionnelle réactive , puis suivez les liens sur le lien des publications sur ma page d'accueil et le - FRP lien sur le wiki Haskell .

Personnellement, j'aime bien réfléchir à ce que signifie PRF avant de préciser comment il pourrait être mis en œuvre. (Code sans spécification est une réponse sans question et donc "pas même faux".) Donc, je ne décris pas FRP en termes de représentation/implémentation comme Thomas K le fait dans une autre réponse (graphes, nœuds, arêtes, déclenchement, exécution, etc). Il y a beaucoup de styles d'implémentation possibles, mais aucune implémentation ne dit ce que FRP est .

Je résonne avec la description simple de Laurence G selon laquelle FRP parle de "types de données qui représentent une valeur" dans le temps "". La programmation impérative conventionnelle ne capture ces valeurs dynamiques qu'indirectement, à travers des mutations et des états. 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 (indirectement) capturées, car le paradigme impératif est temporellement discret. En revanche, FRP capture ces valeurs évolutives directement et ne présente aucune difficulté avec en continu évolution des valeurs.

FRP est également inhabituel en ce sens qu’il est concourant sans aller à l’encontre du nid théorique et pragmatique des rats qui nuit à la concurrence impérative. Sémantiquement, la concurrence de FRP est affinée , déterminée et continu . (Je parle de sens, pas de mise en œuvre. Une mise en œuvre peut impliquer ou non une concurrence ou un parallélisme.) La détermination sémantique est très importante pour un raisonnement à la fois rigoureux et informel. Alors que la concurrence ajoute une complexité énorme à la programmation impérative (en raison d'un entrelacement non déterministe), elle est sans effort en PRF.

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 transférer dans et hors des fonctions. J'ai appelé ces choses "comportements".

  • Les comportements sont construits à partir de quelques primitives, comme les comportements constants (statiques) et l'heure (comme une horloge), puis avec une combinaison séquentielle et parallèle. Les comportements n sont combinés en appliquant une fonction n-aire (sur des valeurs statiques), "point par point", c'est-à-dire de manière continue dans le temps.

  • Pour rendre compte de phénomènes discrets, utilisez un autre type (famille) d’événements, chacun ayant un flot d’occurrences (fini ou infini). 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 des bases solides, donnez au modèle entier un fondement compositionnel, en utilisant la technique de la sémantique dénotationnelle, ce qui signifie simplement que (a) chaque type correspond à un type mathématique simple et précis correspondant à des "significations", et ( b) chaque primitif et chaque opérateur ont une signification simple et précise en fonction de la signification des constituants. Jamais, jamais ne mélangez pas les considérations d'implémentation dans votre processus d'exploration. Si cette description est un charabia, consultez (a) schéma de dénotationnel avec morphismes de classe de types, (b) Programmation réactive fonctionnelle push-pull (en ignorant les bits d'implémentation), et (c) le Sémantique dénotationnelle Page des wikibooks Haskell . Attention, la sémantique dénotationnelle comporte deux parties, de ses deux fondateurs Christopher Strachey et Dana Scott: la partie Strachey la plus facile et la plus utile et la partie Scott la plus difficile et la plus utile (pour la conception de logiciels).

Si vous vous en tenez à ces principes, je suppose que vous obtiendrez quelque chose de plus ou de moins dans l’esprit de FRP.

Où ai-je trouvé ces principes? Dans la conception de logiciels, je pose toujours la même question: "qu'est-ce que cela signifie?". La sémantique dénotationnelle m'a donné un cadre précis pour cette question, qui correspond à mon esthétique (contrairement à la sémantique opérationnelle ou axiomatique, qui me laisse insatisfait). Alors je me suis demandé quel est le comportement? J'ai vite compris que la nature temporellement discrète du calcul impératif est une adaptation à un style particulier de la machine , plutôt qu'une description naturelle du comportement lui-même. La description 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 concurrence simultanée, déterministe avec facilité et grâce.

Il a été très difficile de mettre en œuvre ce modèle correctement et efficacement, mais c'est une autre histoire.

931
Conal

En programmation fonctionnelle pure, il n'y a pas d'effets secondaires. Pour de nombreux types de logiciels (par exemple, tout ce qui implique une interaction utilisateur), des effets secondaires sont nécessaires à un certain niveau.

Un moyen d'obtenir un comportement semblable à un effet secondaire tout en conservant un style fonctionnel consiste à utiliser une programmation réactive fonctionnelle. C'est la combinaison de la programmation fonctionnelle et de la programmation réactive. (L'article Wikipedia que vous avez lié à ce dernier point.)

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 de changement dans le temps auront eux-mêmes des valeurs qui changent avec le temps.

Par exemple, vous pouvez représenter les coordonnées de la souris sous forme d'une paire de valeurs entières dans le temps. Disons que nous avions quelque chose comme (c'est du 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 que de faire cette affectation une 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 élimine le besoin de transformer des variables tout en vous permettant de faire tout ce que vous pouvez accomplir avec des mutations variables.

Si nous faisons ensuite des calculs basés sur cela, les valeurs résultantes seront également des valeurs qui changent avec le temps. Par exemple:

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

Dans cet exemple, minX sera toujours inférieur de 16 à la coordonnée x du pointeur de la souris. Avec les bibliothèques réactives, vous pouvez alors dire quelque chose comme:

rectangle(minX, minY, maxX, maxY)

Et un cadre 32x32 sera dessiné autour du pointeur de la souris et le suivra partout où il se déplacera.

Voici un très bon article sur la programmation réactive fonctionnelle .

740
Laurence Gonsalves

Un moyen simple d’obtenir une première idée de ce qu’il en est est 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, toute cellule faisant référence à cette cellule change également. C'est la même chose avec FRP. Imaginons maintenant que certaines 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 un peu beaucoup. La métaphore s'effondre 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, la souris sur laquelle on clique). Je ne fais que mettre ceci ici pour vous donner une idée de la situation.

144
Bob

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

  1. En mathématiques, x = sin(t) signifie que x est nom différent pour sin(t). Donc, écrire x + y est la même chose que sin(t) + y. La programmation réactive fonctionnelle ressemble en cela à la mathématique: si vous écrivez x + y, elle est calculée avec quelle que soit la valeur de t au 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 x enregistre valeur desin(t) pris au moment de la affectation.
131
user712092

D’après vos connaissances générales et la lecture de la page Wikipedia que vous avez indiquée, il semble que la programmation réactive ressemble à l’informatique à flux de données, mais que des "stimuli" externes spécifiques déclenchent un ensemble de nœuds pour déclencher et effectuer leurs calculs.

Cela convient assez bien à la conception d’interface utilisateur, par exemple, dans laquelle le fait de toucher un contrôle d’interface utilisateur (par exemple, le contrôle du volume d’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, par exemple) qui correspond à la modification de la valeur associée à un nœud dans un graphe dirigé.

Divers nœuds ayant des arêtes à partir de ce nœud "valeur de volume" seraient automatiquement déclenchés et tous les calculs et mises à jour nécessaires se répercuteraient naturellement sur 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 plus généralement dans un paradigme de programmation fonctionnelle.

Pour plus d'informations sur "l'informatique par flux de données", 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 dirigé de noeuds, chacun effectuant des calculs simples. Ces nœuds sont connectés les uns aux autres par des liens de graphes qui fournissent les sorties de certains nœuds aux entrées des autres.

Lorsqu'un nœud 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, en fonction de la manière dont la programmation réactive est mise en œuvre.

Les nœuds peuvent être considérés comme activés en parallèle, mais ils sont souvent exécutés en série ou avec un parallélisme limité (par exemple, il est possible que quelques threads les exécutent). Un exemple célèbre est le Manchester Dataflow Machine , qui (IIRC) utilisait une architecture de données étiquetée pour planifier l'exécution des nœuds du graphe via une ou plusieurs unités d'exécution. L'informatique par flux de données est assez bien adaptée aux situations dans lesquelles le déclenchement de calculs de manière asynchrone donnant lieu à des cascades de calculs fonctionne mieux que d'essayer de faire en sorte que l'exécution soit régie par une horloge (ou des horloges).

La programmation réactive importe cette idée de "cascade d’exécution" et semble penser que le programme est semblable à un flux de données, mais à la condition que certains nœuds soient reliés au "monde extérieur" et que les cascades d’exécution soient déclenchées -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 ne pas être 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 de la relation avec les entrées externes. Cela risque d'être quelque peu subjectif, car les gens seront probablement tentés de dire que tout ce qui réagit à des intrants externes "réagit" à eux. Mais, en regardant l’esprit de la chose, un programme qui interroge une file 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 à la saisie de l’utilisateur qu’à un intervalle fixe). Là encore, c’est l’esprit de la chose: on peut imaginer intégrer une implémentation d’interrogation avec un intervalle d'interrogation rapide dans un système à un niveau très bas et de la programmer de manière réactive.

71
Thomas Kammeyer

Après avoir lu de nombreuses pages sur FRP, j’ai finalement trouvé this une écriture éclairante sur FRP, c’est enfin ce qui m’a fait comprendre ce qu’il en était vraiment.

Je cite ci-dessous Heinrich Apfelmus (auteur de banane réactive).

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

Une réponse courante serait que "le PRF consiste essentiellement à décrire un système en termes de fonctions variant dans le temps au lieu d'un état mutable", ce qui 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 consiste à spécifier complètement le comportement dynamique d'une valeur au moment de la déclaration.

Par exemple, prenons l'exemple d'un compteur: vous avez deux boutons intitulés "Up" et "Down" qui peuvent être utilisés pour incrémenter ou décrémenter le compteur. Vous devez impérativement spécifier une valeur initiale, puis la modifier chaque fois que vous appuyez sur un bouton. 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 du compteur 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 suit:

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. Cela contraste nettement avec l'approche impérative dans laquelle les déclarations ultérieures peuvent modifier le comportement dynamique des valeurs précédemment déclarées.

Donc, dans ma compréhension un programme FRP est un ensemble d'équations: enter image description here

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

f dépend de t donc cela intègre la possibilité de modéliser des stimuli externes

tout l'état du programme est encapsulé dans des variables x_i

La bibliothèque FRP prend en charge la progression du temps, autrement dit, en prenant j à j+1.

J'explique ces équations de manière beaucoup plus détaillée dans la vidéo this .

EDIT:

Environ deux ans après la réponse initiale, j'ai récemment conclu que les implémentations de PRF ont un autre aspect important. Ils doivent (et le font généralement) résoudre un problème pratique important: l'invalidation du cache .

Les équations pour x_i- s décrivent un graphe de dépendance. Quand une partie du x_i change à l’heure j, alors toutes les autres valeurs x_i' dans j+1 ne doivent pas être mises à jour. Par conséquent, toutes les dépendances ne doivent pas être recalculées. x_i' peut être indépendant de x_i.

De plus, les x_i- qui changent peuvent être mis à jour progressivement. Par exemple, considérons une opération de carte f=g.map(_+1) dans Scala, où f et g sont List sur Ints. Ici, f correspond à x_i(t_j) et g est x_j(t_j). Maintenant, si je préfixe un élément à g, il serait inutile de procéder à l'opération map pour tous les éléments de g. Certaines implémentations de FRP (par exemple reflex-frp ) visent à résoudre ce problème. Ce problème est également appelé calcul incrémental.

En d'autres termes, les comportements (les x_i- s) dans FRP peuvent être considérés comme des calculs en cache. Le moteur FRP a pour tâche d'invalider et de recalculer efficacement ces antémémoires (le x_i- s) si certains des f_i- s changent.

65
jhegedus

Disclaimer: ma réponse se situe dans le contexte de rx.js - une bibliothèque de 'programmation réactive' pour Javascript.

En programmation fonctionnelle, au lieu de parcourir chaque élément d'une collection, vous appliquez des fonctions plus élevées (HoF) à la collection elle-même. Ainsi, l’idée de base de FRP est qu’au lieu de traiter chaque événement individuel, créez un flux d’événements (implémenté avec un observable *) et appliquez-y les HoFs. De cette manière, vous pouvez visualiser le système sous forme de pipelines de données reliant les éditeurs aux abonnés.

Les principaux avantages de l'utilisation d'un observable sont les suivants:
i) il extrait l’état de votre code, par exemple, si vous souhaitez que le gestionnaire d’événements ne soit déclenché que pour chaque événement, ou qu’il cesse de déclencher après les premiers événements, ou commence à déclencher uniquement. après les premiers événements, vous pouvez simplement utiliser les HoFs (filter, takeUntil, skip, respectivement) au lieu de définir, mettre à jour et vérifier les compteurs.
ii) améliore la localisation du code - si 5 gestionnaires d’événements différents modifient l’état d’un composant, vous pouvez fusionner leurs observables et définir un seul gestionnaire d’événements sur l’observable fusionné, en combinant efficacement 5 gestionnaires d’événements en un. Ainsi, il est très facile de déterminer les événements pouvant affecter un composant dans l’ensemble du système, 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, d'où l'énumération est pilotée par le consommateur.

Une observable est une séquence produite paresseusement - chaque élément est poussé vers l'observateur chaque fois qu'il est ajouté à la séquence, d'où l'énumération est pilotée par le producteur.

29
tldr

Le document Réactivité fonctionnelle simplement efficace de Conal Elliott ( PDF direct , 233 KB) est un assez bonne introduction. La bibliothèque correspondante fonctionne également.

Le papier est maintenant remplacé par un autre papier, Programmation réactive fonctionnelle push-pull ( PDF direct , 286 KB).

29
scvalex

Mec, c'est une idée géniale géniale! Pourquoi n'ai-je pas appris 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, le système tout entier peut être traité comme une équation mathématique, n'est-ce pas?

18
Dan Ross

Le livre de Paul Hudak, The Haskell School of Expression , est non seulement une excellente introduction à Haskell, mais il passe également beaucoup de temps à FRP. Si vous êtes débutant avec le PRF, je le recommande vivement pour vous donner une idée de son fonctionnement.

Il y a aussi ce qui ressemble à une nouvelle réécriture de ce livre (sorti en 2011, mis à jour en 2014), The Haskell School of Music .

14
Curt J. Sampson

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 ayant le 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, lorsque nous écrivons y: = x + 1 dans le langage de programmation, nous entendons en fait l'équation y (t =) = x (t) + 1.

10
Yuning

Agit comme un tableur, comme indiqué. Généralement basé sur un framework événementiel.

Comme avec tous les "paradigmes", sa nouveauté est discutable.

D'après mon expérience des réseaux d'acteurs à flux distribués, il peut facilement être victime d'un problème général de cohérence d'état sur le réseau de nœuds, à savoir que vous vous retrouvez avec beaucoup d'oscillation et de piégeage dans des boucles étranges.

Cela est difficile à éviter car certaines sémantiques impliquent des boucles référentielles ou une diffusion, et peuvent être assez chaotiques lorsque 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 en fonction du moment où les 2 sont devenus 2, et s'ils sont restés ainsi. Les tableurs ont des horloges synchrones et une détection de boucle. Les acteurs distribués ne le font généralement pas.

Tout bon amusement :).

9
emperorz

J'ai trouvé cette jolie vidéo sur le sous-titre Clojure à propos de 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

8
Daniel Kaplan

Cet article par Andre Staltz est la meilleure et la plus claire des explications que j'ai vue jusqu'à présent.

Quelques citations de l'article:

La programmation réactive consiste à programmer avec des flux de données asynchrones.

En plus de cela, vous disposez d'une étonnante 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:

Click event stream diagram

7
GreenGiant

Il s’agit de transformations de données mathématiques au fil du temps (ou d’ignorer le temps).

En code, cela signifie pureté fonctionnelle et programmation déclarative.

Les bugs d’état sont un énorme problème dans le paradigme de l’impératif standard. Divers bits de code peuvent modifier un état partagé à différents "moments" lors de l'exécution du programme. 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 l'arborescence) des noeuds de transformation et qu'il 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 "maintenant" A est B + C. Mais la commande suivante peut être B ++, auquel cas A n'est pas égal à B + C. En programmation mathématique ou déclarative, A sera toujours égal à B + C, quel que soit le moment que vous demandez.

Donc, en supprimant la complexité 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 transforment N autres comportements. Cette valeur composée recalculera lorsque les événements d'entrée (comportements) se déclenchent.

"Comme les observateurs sont sans état, nous avons souvent besoin de plusieurs d'entre eux pour simuler une machine à états, comme dans l'exemple de glisser. Nous devons sauvegarder l'état dans lequel il est accessible à tous les observateurs impliqués, comme dans la variable chemin ci-dessus."

Citation de - Deprecating The Observer Pattern http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf

5
Jay Shepherd

L'explication courte et claire de la programmation réactive apparaît sur Cyclejs - Programmation réactive , elle utilise des exemples 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 Inversion of Control , principalement parce que [module/Component/object] est responsable de lui-même, améliorant ainsi l'encapsulation à l'aide de méthodes privées par rapport à 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 papiers plus complexes et plus profonds.

2
pdorgambide

FRP est une combinaison de programmation fonctionnelle (paradigme de programmation basé sur l'idée que tout est une fonction) et paradigme de programmation réactive (construite sur l'idée que tout est un flux (observateur et philosophie observable)). C'est censé être le meilleur des mondes.

Allez voir Andre Staltz sur la programmation réactive pour commencer.

0
kg11

Découvrez Rx, Reactive Extensions for .NET. Ils soulignent qu'avec IEnumerable, vous "tirez" 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 que sur IObservable, vous pouvez écrire des requêtes Linq qui "réagissent".

Par exemple, vous pouvez écrire une requête Linq telle que (de m dans MyObservableSetOfMouseMovements où m.X <100 et m.Y <100 sélectionnent un nouveau point (m.X, m.Y)).

et avec les extensions Rx, c'est tout: vous avez un code d'interface utilisateur qui réagit au flux entrant de mouvements de souris et dessine chaque fois que vous vous trouvez dans la zone 100 100 ...

0
Sentinel