web-dev-qa-db-fra.com

Comparaison de core.async et de la programmation réactive fonctionnelle (+ Rx)

Je semble être un peu confus en comparant core.async de Clojure à ce qu'on appelle Extensions réactives (Rx) et FRP en général. Ils semblent s'attaquer à un problème similaire d'async-hronicité, alors je me demande quelles sont les principales différences et dans quels cas est-on préféré à l'autre. Quelqu'un peut-il expliquer?

EDIT: Pour encourager des réponses plus approfondies, je veux rendre la question plus précise:

  1. Core.async me permet d'écrire du code d'aspect synchrone. Cependant, si je comprends bien, FRP n'a besoin que d'un seul niveau de rappels imbriqués (toutes les fonctions qui gèrent la logique sont passées comme arguments à l'API FRP). Il semble que les deux approches rendent les pyramides de rappel inutiles. Il est vrai que dans JS je dois écrire function() {...} plusieurs fois, mais le problème principal, les rappels imbriqués , a disparu FRP aussi. Est-ce que je comprends bien?

  2. " FRP complique la communication des messages avec un flux de contrôle" Pouvez-vous (quelqu'un) donner une explication plus spécifique?

  3. Je ne peux pas contourner les points de terminaison observables de FRP de la même manière que je passe les canaux?

En général, je comprends d'où viennent les deux approches et j'ai essayé peu de tutoriels dans les deux. Cependant, je semble être "paralysé" par la non-évidence des différences. Y a-t-il exemple d'un code qui serait difficile à écrire dans l'un d'eux et facile à utiliser l'autre? Et quelle est la raison architecturale de cela?

59
tillda

Je pense que le problème principal est que votre hypothèse sur le problème abordé ne l'est pas tout à fait, car aucun d'entre eux ne s'attaque au asynchronicity "problème".

Les abstractions

FRP l'idée principale est la propagation du changement, pensez à faire la même chose qu'Excel, où vous définissez les cellules en fonction les unes des autres dans une cascade, et quand une cellule change, toutes les cellules dépendantes de la cascade sont recalculées.

core.async l'idée principale est la décomposition des systèmes, pensez à séparer les préoccupations en utilisant un queue au milieu de différents processus, dans core.async cas au lieu de files d'attente, vous avez des canaux mais vous avez l'idée.

Ainsi, la suppression du code pyramidal n'est pas l'objectif de l'une ou l'autre technologie, et elles opèrent sur différentes couches d'abstraction.

A propos de la complétion du flux de contrôle

L'idée de compliquer la communication et le contrôle de flux est tirée de le post asynchrone principal d'origine .

Bien qu'il existe différents mécanismes pour rendre les événements/rappels plus propres (FRP, Rx/Observables), ils ne changent pas leur nature fondamentale, c'est-à-dire que lors d'un événement, une quantité arbitraire d'autres codes est exécutée, éventuellement sur le même thread, conduisant à des avertissements tels que "ne faites pas trop de travail dans votre gestionnaire" et des expressions comme "l'enfer de rappel".

En reformulant, si vous avez code de domaine d'entreprise à l'intérieur d'un gestionnaire d'événements, vous avez compliqué le traitement des événements X avec le que faire lorsque X se produit .

Lequel est quoi core.async aborde, car l'introduction d'un file d'attente/canal au milieu, permet une meilleure séparation des préoccupations.

La mise en oeuvre

Toutes vos questions concernant les rappels et le passage de points de terminaison observables en tant que paramètres ne sont que des questions d'implémentation, cela dépend vraiment de l'implémentation Rx et de l'API.

Si vous regardez React composants réutilisables vous n'avez vraiment pas beaucoup d'enfer de rappel à voir, et vous avez cette idée de passer des observables autour.

Dernières pensées

Même si Rx peut être utilisé pour modéliser n'importe quel flux de données, est plus couramment utilisé pour UI Rendering a-la Excel, pour simplifier la façon dont vous mettez à jour votre vue lorsque votre modèle change.

D'autre part, Core.Async peut être utilisé pour modéliser la séparation des problèmes lorsque deux sous-systèmes communiquent entre eux (même scénario d'utilisation que les files d'attente), en l'utilisant sur l'idée principale de la chaîne de rendu de l'interface utilisateur, il s'agit de séparer:

  • Génération et traitement d'événements côté serveur
  • Comment cet événement affecte votre modèle

Vous pouvez donc avoir core.async et FRP ensemble, puisque core.async séparera les préoccupations et FRP définira votre flux de données en cascade une fois votre modèle mis à jour.

31
Guillermo Winkler

Au moins une des principales différences principales, et, je pense, ce que Rich ment de "[FRP] complique la communication des messages avec un flux de contrôle", est la suivante.

Les rappels sont du code qui est exécuté. Et cette exécution doit se produire dans un fil à un moment donné. Souvent, l'heure à laquelle l'événement se produit, et le thread est le thread qui remarque/produit l'événement. Si le producteur place plutôt un message sur une chaîne, vous êtes libre de consommer le message quand vous le souhaitez, dans le fil de votre choix. Ainsi, les rappels, qui sont essentiellement une forme de communication, complètent la communication avec le flux de contrôle en dictant quand et où le code de rappel est exécuté. Si vous devez utiliser des rappels pour une raison quelconque, utilisez-les simplement pour placer un message dans une file d'attente/un canal et vous êtes de retour sur la bonne voie.

Parce que core.async est moins complexe, il doit être préféré tant qu'il n'y a pas de bonne raison d'utiliser les alternatives.

23
oskarkv

Clojure core.async est un portage de blocs go du langage Go . Le concept fondamental est celui des canaux représentant la communication asynchrone entre les threads.

Le but est de pouvoir écrire des blocs de code séquentiel d'apparence normale qui accèdent aux canaux pour obtenir une entrée, et cela est traduit de manière transparente dans une machine d'état qui élabore le CSP traduction pour vous.

Contrairement à FRP, qui est toujours fondamentalement sur les rappels, et il respecte la communication des messages avec un flux de contrôle. core.async élimine complètement les rappels de votre code et sépare le flux de contrôle de la transmission des messages. De plus, dans FRP, un canal n'est pas un objet de première classe (c'est-à-dire que vous ne pouvez pas envoyer un canal FRP en tant que valeur sur un canal FRP).

11
noisesmith

ÉDITER:

Pour répondre à tes questions:

  1. Oui, plusieurs fois, les rappels peuvent être éliminés, par exemple avec la logique de nouvelle tentative, distinctUntilChanged et beaucoup d'autres choses telles que:

    var obs = getJSON('story.json').retry(3);

    var obs = Rx.Observable.fromEvent(document, 'keyup').distinctUntilChanged();

  2. Je ne sais pas à quoi cela se réfère en le rendant plus complexe avec le contrôle de flux.

  3. Oui, vous pouvez contourner un objet comme vous le feriez pour un canal, comme avec l'objet ci-dessous.

Par exemple, vous pouvez avoir ici un comportement de type canal en utilisant RxJS avec un ReplaySubject :

var channel = new Rx.ReplaySubject();

// Send three observables down the chain to subscribe to
channel.onNext(Rx.Observable.timer(0, 250).map(function () { return 1; }));
channel.onNext(Rx.Observable.timer(0, 1000).map(function () { return 2; }));
channel.onNext(Rx.Observable.timer(0, 1500).map(function () { return 3; }));

// Now pass the channel around anywhere!
processChannel(channel);

Pour le rendre un peu plus concret, comparez le code du post de David Nolen ici avec un exemple RxJS FRP ici

5
Matthew Podwysocki

Il y a un article ici qui compare FRP avec CSP sur un ensemble limité d'exemples (ce qui voulait cependant démontrer les avantages du CSP), avec une conclusion à la fin: http://potetm.github.io/2014/ 01/07/frp.html

4
Robert Monfera