it-swarm.com.de

Was ist (funktionale) reaktive Programmierung?

Ich habe den Wikipedia-Artikel über reaktive Programmierung gelesen. Ich habe auch den kleinen Artikel über Funktionale Reaktive Programmierung gelesen. Die Beschreibungen sind sehr abstrakt.

  1. Was bedeutet funktionale reaktive Programmierung (FRP) in der Praxis?
  2. Woraus besteht reaktive Programmierung (im Gegensatz zu nicht reaktiver Programmierung?)?

Mein Hintergrund ist in imperativen/OO-Sprachen, daher wäre eine Erklärung, die sich auf dieses Paradigma bezieht, wünschenswert.

1149
JtR

Wenn Sie ein Gefühl für FRP bekommen möchten, können Sie mit dem alten Fran-Tutorial aus dem Jahr 1998 beginnen, das animierte Illustrationen enthält. Beginnen Sie für Artikel mit Functional Reactive Animation und folgen Sie dann den Links auf dem Publikationslink auf meiner Homepage und dem - FRP Link im Haskell-Wiki .

Persönlich denke ich gerne darüber nach, was FRP bedeutet , bevor ich darüber spreche, wie es implementiert werden könnte. (Code ohne Spezifikation ist eine Antwort ohne Frage und daher "nicht einmal falsch".) Daher beschreibe ich FRP nicht in Repräsentations-/Implementierungsbegriffen wie Thomas K in einer anderen Antwort (Graphen, Knoten, Kanten, Brennen, Ausführung, usw). Es gibt viele mögliche Implementierungsstile, aber keine Implementierung gibt an, was FRP ist .

Ich stimme mit Laurence Gs einfacher Beschreibung überein, dass es sich bei FRP um "Datentypen, die einen Wert" über die Zeit "darstellen" handelt. Die konventionelle imperative Programmierung erfasst diese dynamischen Werte nur indirekt durch Status und Mutationen. Die gesamte Geschichte (Vergangenheit, Gegenwart, Zukunft) hat keine erstklassige Darstellung. Darüber hinaus können nur sich diskret entwickelnde Werte (indirekt) erfasst werden, da das imperative Paradigma zeitlich diskret ist. Im Gegensatz dazu erfasst FRP diese sich entwickelnden Werte direkt und hat keine Schwierigkeiten mit kontinuierlich Werte entwickeln.

FRP ist auch insofern ungewöhnlich, als es gleichzeitig abläuft, ohne das theoretische und pragmatische Rattennest zu missachten, das eine zwingende Nebenläufigkeit plagt. Semantisch ist die Nebenläufigkeit von FRP feinkörnig , bestimmt und stetig . (Ich spreche von Bedeutung, nicht von Implementierung. Eine Implementierung kann Parallelität oder Parallelität beinhalten oder auch nicht.) Die semantische Determiniertheit ist sowohl für rigorose als auch für informelle Überlegungen sehr wichtig. Parallelität erhöht zwar die Komplexität der imperativen Programmierung (aufgrund nicht deterministischer Verschachtelung), ist jedoch in FRP mühelos.

Was ist FRP? Du hättest es selbst erfinden können. Beginnen Sie mit diesen Ideen:

  • Dynamische/sich entwickelnde Werte (d. H. Werte "über die Zeit") sind an sich erstklassige Werte. Sie können sie definieren und kombinieren, in und aus Funktionen übergeben. Ich nannte diese Dinge "Verhaltensweisen".

  • Verhaltensweisen setzen sich aus wenigen Grundelementen zusammen, z. B. konstanten (statischen) Verhaltensweisen und der Zeit (wie eine Uhr), und werden dann sequentiell und parallel kombiniert. n Verhaltensweisen werden kombiniert, indem eine n-fache Funktion (auf statische Werte) "punktweise" angewendet wird, d. h. kontinuierlich über die Zeit.

  • Um diskrete Phänomene zu berücksichtigen, müssen Sie eine andere Art (Familie) von "Ereignissen" haben, von denen jedes einen Strom (endlich oder unendlich) von Ereignissen aufweist. Jedes Vorkommen hat eine zugeordnete Zeit und einen Wert.

  • Um das kompositorische Vokabular zu finden, aus dem alle Verhaltensweisen und Ereignisse aufgebaut werden können, spielen Sie mit einigen Beispielen. Zerlege es weiter in Teile, die allgemeiner/einfacher sind.

  • Damit Sie wissen, dass Sie auf einer soliden Grundlage stehen, geben Sie dem gesamten Modell eine kompositorische Grundlage, indem Sie die Technik der Denotationssemantik anwenden. b) jedes Grundelement und jeder Operator hat eine einfache und genaue Bedeutung in Abhängigkeit von der Bedeutung der Bestandteile. Mischen Sie niemals Implementierungsaspekte in Ihren Explorationsprozess. Wenn Ihnen diese Beschreibung patzig vorkommt, wenden Sie sich an (a) Bezeichnungsentwurf mit Typklassenmorphismen, (b) Blindprogrammierung mit Push-Pull-Funktion (ohne Berücksichtigung der Implementierungsbits) und (c) der Denotational Semantics Haskell Wikibooks Seite . Beachten Sie, dass die Denotationssemantik aus zwei Teilen besteht, von den beiden Gründern Christopher Strachey und Dana Scott: dem einfacheren und nützlicheren Teil von Strachey und dem schwierigeren und weniger nützlichen (für das Software-Design) Teil von Scott.

Wenn Sie sich an diese Grundsätze halten, werden Sie meiner Meinung nach mehr oder weniger etwas im Sinne von FRP erhalten.

Woher habe ich diese Grundsätze? Beim Softwaredesign stelle ich immer die gleiche Frage: "Was bedeutet das?". Die Denotationssemantik gab mir einen genauen Rahmen für diese Frage und einen, der zu meiner Ästhetik passt (im Gegensatz zur operationellen oder axiomatischen Semantik, die mich beide unzufrieden macht). Also habe ich mich gefragt, was ist Verhalten? Ich erkannte bald, dass die zeitlich diskrete Natur der imperativen Berechnung eher eine Anpassung an einen bestimmten Stil der Maschine als eine natürliche Beschreibung des Verhaltens selbst ist. Die einfachste genaue Beschreibung des Verhaltens, die ich mir vorstellen kann, ist einfach "Funktion der (kontinuierlichen) Zeit", das ist also mein Modell. Mit diesem Modell lässt sich eine kontinuierliche, deterministische Parallelität mit Leichtigkeit und Anmut bewältigen.

Es war eine ziemliche Herausforderung, dieses Modell korrekt und effizient umzusetzen, aber das ist eine andere Geschichte.

931
Conal

Bei der reinen Funktionsprogrammierung treten keine Nebenwirkungen auf. Für viele Arten von Software (zum Beispiel alles, was mit Benutzerinteraktion zu tun hat) sind Nebenwirkungen auf einer bestimmten Ebene erforderlich.

Eine Möglichkeit, nebenwirkungsartiges Verhalten zu erzielen, während der Funktionsstil beibehalten wird, besteht in der Verwendung von funktionaler reaktiver Programmierung. Dies ist die Kombination aus funktionaler und reaktiver Programmierung. (Der Wikipedia-Artikel, auf den Sie verlinkt haben, handelt von letzterem.)

Die Grundidee hinter der reaktiven Programmierung ist, dass es bestimmte Datentypen gibt, die einen Wert "über die Zeit" darstellen. Berechnungen, bei denen diese Werte im Laufe der Zeit geändert werden, haben selbst Werte, die sich im Laufe der Zeit ändern.

Beispielsweise könnten Sie die Mauskoordinaten als ein Paar von Ganzzahl-Über-Zeit-Werten darstellen. Nehmen wir an, wir hatten so etwas wie (das ist Pseudocode):

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

Zu jedem Zeitpunkt hätten x und y die Koordinaten der Maus. Im Gegensatz zur nicht-reaktiven Programmierung müssen wir diese Zuordnung nur einmal vornehmen, und die Variablen x und y bleiben automatisch auf dem neuesten Stand. Das ist der Grund, warum reaktives Programmieren und funktionales Programmieren so gut zusammenarbeiten: Reaktives Programmieren macht das Mutieren von Variablen überflüssig und lässt Sie dennoch viel von dem tun, was Sie mit variablen Mutationen erreichen können.

Wenn wir dann einige Berechnungen auf dieser Basis durchführen, sind die resultierenden Werte auch Werte, die sich im Laufe der Zeit ändern. Zum Beispiel:

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

In diesem Beispiel ist minX immer 16 kleiner als die x-Koordinate des Mauszeigers. Mit reaktionsbewussten Bibliotheken könnte man dann so etwas sagen:

rectangle(minX, minY, maxX, maxY)

Ein 32x32-Feld wird um den Mauszeiger gezeichnet und verfolgt ihn, wo immer er sich bewegt.

Hier ist ein ziemlich gutes Artikel über funktionale reaktive Programmierung .

740

Eine einfache Möglichkeit, eine erste Vorstellung davon zu bekommen, wie es ist, besteht darin, sich vorzustellen, dass Ihr Programm eine Kalkulationstabelle ist und alle Ihre Variablen Zellen sind. Wenn sich eine der Zellen in einer Tabelle ändert, ändern sich auch alle Zellen, die sich auf diese Zelle beziehen. Bei FRP ist es genauso. Stellen Sie sich nun vor, dass sich einige der Zellen von selbst ändern (oder vielmehr aus der Außenwelt stammen): In einer GUI-Situation wäre die Position der Maus ein gutes Beispiel.

Das verpasst zwangsläufig ziemlich viel. Die Metapher bricht ziemlich schnell zusammen, wenn Sie tatsächlich ein FRP-System verwenden. Zum einen gibt es normalerweise Versuche, auch diskrete Ereignisse zu modellieren (z. B. das Klicken der Maus). Ich schreibe das hier nur, um Ihnen eine Vorstellung davon zu geben, wie es ist.

144
Bob

Für mich geht es um 2 verschiedene Bedeutungen des Symbols =:

  1. In Mathematik bedeutet x = sin(t), dass xanderer Name für sin(t) ist. Das Schreiben von x + y ist also dasselbe wie sin(t) + y. Funktionale reaktive Programmierung ist in dieser Hinsicht wie Mathematik: Wenn Sie x + y schreiben, wird sie mit dem Wert von t berechnet, der zu dem Zeitpunkt verwendet wird.
  2. In C-ähnlichen Programmiersprachen (imperativen Sprachen) ist x = sin(t) eine Zuweisung: Dies bedeutet, dass x das Wert vonsin(t) speichert, das zum Zeitpunkt der Zuweisung verwendet wurde.
131
user712092

Gut, nach dem Hintergrundwissen und dem Lesen der Wikipedia-Seite, auf die Sie verwiesen haben, scheint reaktives Programmieren etwas wie Datenfluss-Computing zu sein, aber mit spezifischen externen "Reizen", die eine Reihe von Knoten auslösen, um ihre Berechnungen durchzuführen.

Dies ist sehr gut für das UI-Design geeignet, bei dem durch Berühren einer Benutzeroberfläche (z. B. der Lautstärkeregelung in einer Musikwiedergabeanwendung) möglicherweise verschiedene Anzeigeelemente und die tatsächliche Lautstärke der Audioausgabe aktualisiert werden müssen. Wenn Sie die Lautstärke ändern (z. B. einen Schieberegler), entspricht dies der Änderung des Werts, der einem Knoten in einem gerichteten Diagramm zugeordnet ist.

Verschiedene Knoten mit Flanken von diesem "Volumenwert" -Knoten würden automatisch ausgelöst, und alle erforderlichen Berechnungen und Aktualisierungen würden sich natürlich durch die Anwendung ziehen. Die Anwendung "reagiert" auf den Benutzerreiz. Funktionale reaktive Programmierung wäre nur die Implementierung dieser Idee in einer funktionalen Sprache oder allgemein innerhalb eines funktionalen Programmierparadigmas.

Weitere Informationen zum Thema "Datenflussberechnung" finden Sie in Wikipedia oder in Ihrer bevorzugten Suchmaschine. Die allgemeine Idee ist: Das Programm ist ein gerichteter Graph von Knoten, von denen jeder eine einfache Berechnung durchführt. Diese Knoten sind durch Diagrammverknüpfungen miteinander verbunden, die die Ausgänge einiger Knoten mit den Eingängen anderer Knoten verbinden.

Wenn ein Knoten ausgelöst wird oder seine Berechnung durchführt, werden die entsprechenden Eingänge der mit seinen Ausgängen verbundenen Knoten "ausgelöst" oder "markiert". Jeder Knoten, bei dem alle Eingänge ausgelöst/markiert/verfügbar sind, wird automatisch ausgelöst. Das Diagramm kann implizit oder explizit sein, je nachdem, wie genau die reaktive Programmierung implementiert ist.

Knoten können als parallel ausgelöst betrachtet werden, werden jedoch häufig seriell oder mit eingeschränkter Parallelität ausgeführt (z. B. können einige Threads sie ausführen). Ein berühmtes Beispiel war Manchester Dataflow Machine , das (IIRC) eine Architektur mit Tags verwendete, um die Ausführung von Knoten im Diagramm durch eine oder mehrere Ausführungseinheiten zu planen. Datenfluss-Computing eignet sich ziemlich gut für Situationen, in denen das asynchrone Auslösen von Berechnungen, die zu Berechnungskaskaden führen, besser funktioniert als der Versuch, die Ausführung durch eine Uhr (oder Uhren) steuern zu lassen.

Die reaktive Programmierung importiert diese Idee der "Kaskade der Ausführung" und scheint das Programm datenflussartig zu betrachten, jedoch mit der Maßgabe, dass einige der Knoten an die "Außenwelt" gebunden sind und die Kaskaden der Ausführung ausgelöst werden, wenn diese sensorisch sind -ähnliche Knoten ändern sich. Die Programmausführung würde dann ähnlich aussehen wie bei einem komplexen Reflexbogen. Das Programm kann grundsätzlich zwischen Reizen sitzend sein oder auch nicht, oder es kann sich zwischen Reizen in einen grundsätzlich sitzenden Zustand versetzen.

"Nicht-reaktive" Programmierung wäre eine Programmierung mit einer ganz anderen Sicht auf den Ausführungsfluss und die Beziehung zu externen Eingaben. Es ist wahrscheinlich etwas subjektiv, da die Leute wahrscheinlich versucht sind, etwas zu sagen, das auf externe Eingaben "reagiert". Ein Programm, das eine Ereigniswarteschlange in einem festgelegten Intervall abfragt und alle gefundenen Ereignisse an Funktionen (oder Threads) weiterleitet, ist jedoch weniger reaktiv (da es sich nur in einem festgelegten Intervall um Benutzereingaben kümmert). Auch hier ist es der Sinn der Sache: Man kann sich vorstellen, eine Polling-Implementierung mit einem schnellen Polling-Intervall auf einer sehr niedrigen Ebene in ein System zu integrieren und darauf reaktiv zu programmieren.

71
Thomas Kammeyer

Nachdem ich viele Seiten über FRP gelesen hatte, stieß ich endlich auf dieses aufschlussreiches Schreiben über FRP, und es ließ mich endlich verstehen, worum es bei FRP wirklich geht.

Ich zitiere unten Heinrich Apfelmus (Autor der reaktiven Banane).

Was ist das Wesen der funktionalen reaktiven Programmierung?

Eine verbreitete Antwort wäre, dass es bei FRP darum geht, ein System in Form von zeitvariablen Funktionen anstatt eines veränderlichen Zustands zu beschreiben, und das wäre sicherlich nicht falsch. Dies ist der semantische Standpunkt. Die tiefere und befriedigendere Antwort ergibt sich meiner Meinung nach jedoch aus dem folgenden rein syntaktischen Kriterium:

Das Wesen der funktionalen reaktiven Programmierung besteht darin, das dynamische Verhalten eines Wertes zum Zeitpunkt der Deklaration vollständig anzugeben.

Nehmen wir zum Beispiel einen Zähler: Sie haben zwei Schaltflächen mit der Bezeichnung „Auf“ und „Ab“, mit denen Sie den Zähler erhöhen oder verringern können. Sie müssen unbedingt zuerst einen Anfangswert angeben und ihn dann bei jedem Tastendruck ändern. etwas wie das:

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

Der Punkt ist, dass zum Zeitpunkt der Deklaration nur der Anfangswert für den Zähler angegeben wird; Das dynamische Verhalten des Zählers ist im restlichen Programmtext enthalten. Im Gegensatz dazu gibt die funktionale reaktive Programmierung das gesamte dynamische Verhalten zum Zeitpunkt der Deklaration wie folgt an:

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

Wann immer Sie die Dynamik des Zählers verstehen wollen, müssen Sie sich nur seine Definition ansehen. Alles, was damit passieren kann, wird auf der rechten Seite angezeigt. Dies steht in starkem Gegensatz zum imperativen Ansatz, bei dem nachfolgende Deklarationen das dynamische Verhalten zuvor deklarierter Werte ändern können.

In nach meinem Verständnis besteht ein FRP-Programm aus einer Reihe von Gleichungen: enter image description here

j ist diskret: 1,2,3,4 ...

f hängt von t ab, sodass die Möglichkeit besteht, externe Stimuli zu modellieren

der gesamte Status des Programms ist in Variablen x_i gekapselt.

Die FRP-Bibliothek kümmert sich um den Fortschritt der Zeit, mit anderen Worten, indem sie j bis j+1 führt.

Ich erkläre diese Gleichungen ausführlicher in this Video.

EDIT:

Ungefähr 2 Jahre nach der ursprünglichen Antwort bin ich kürzlich zu dem Schluss gekommen, dass FRP-Implementierungen einen weiteren wichtigen Aspekt haben. Sie müssen (und tun es normalerweise) ein wichtiges praktisches Problem lösen: Cache-Invalidierung.

Die Gleichungen für x_i- beschreiben einen Abhängigkeitsgraphen. Wenn sich einige der x_i zum Zeitpunkt j ändern, müssen nicht alle anderen x_i' Werte bei j+1 aktualisiert werden, sodass nicht alle Abhängigkeiten neu berechnet werden müssen, da einige x_i' ist möglicherweise unabhängig von x_i.

Darüber hinaus können x_i-, die sich ändern, inkrementell aktualisiert werden. Betrachten wir zum Beispiel eine Kartenoperation f=g.map(_+1) in Scala, wobei f und gList von Ints sind. Hier entspricht fx_i(t_j) und g ist x_j(t_j). Wenn ich nun ein Element g voranstelle, wäre es verschwenderisch, die Operation map für alle Elemente in g auszuführen. Einige FRP-Implementierungen (zum Beispiel reflex-frp ) zielen darauf ab, dieses Problem zu lösen. Dieses Problem wird auch als inkrementelles Rechnen bezeichnet

Mit anderen Worten, Verhaltensweisen (der x_i- s) in FRP können als Berechnungen im Cache betrachtet werden. Es ist die Aufgabe der FRP-Engine, diese Cache-s (die x_i- s) effizient ungültig zu machen und neu zu berechnen, wenn sich einige der f_i- s ändern.

65
jhegedus

Haftungsausschluss: Meine Antwort steht im Zusammenhang mit rx.js - einer Bibliothek für reaktive Programmierung für Javascript.

Bei der funktionalen Programmierung wenden Sie, anstatt jedes Element einer Sammlung zu durchlaufen, Funktionen höherer Ordnung (Higher Order Functions, HoFs) auf die Sammlung selbst an. Die Idee hinter FRP ist also, statt jedes einzelne Ereignis zu verarbeiten, einen Strom von Ereignissen zu erstellen (implementiert mit einem beobachtbaren *) und stattdessen HoFs darauf anzuwenden. Auf diese Weise können Sie das System als Daten-Pipelines darstellen, die Publisher mit Abonnenten verbinden.

Die Hauptvorteile der Verwendung eines Observablen sind:
i) Es abstrahiert den Status von Ihrem Code, z. B. wenn der Ereignishandler nur für jedes n-te Ereignis ausgelöst werden soll oder nach den ersten n-Ereignissen nicht mehr ausgelöst werden soll oder nur mit dem Auslösen begonnen werden soll Nach den ersten 'n' Ereignissen können Sie einfach die HoFs (Filter, TakeUntil, Skip) verwenden, anstatt Zähler zu setzen, zu aktualisieren und zu überprüfen.
ii) Verbessert die Codelokalität - Wenn Sie 5 verschiedene Ereignishandler haben, die den Status einer Komponente ändern, können Sie deren Observables zusammenführen und stattdessen einen einzelnen Ereignishandler für das zusammengeführte Observable definieren, wodurch effektiv 5 Ereignishandler zu 1 kombiniert werden Auf diese Weise ist es sehr einfach zu überlegen, welche Ereignisse in Ihrem gesamten System eine Komponente beeinflussen können, da alles in einem einzigen Handler vorhanden ist.

  • Ein Observable ist das Dual eines Iterable.

Eine Iterable ist eine träge konsumierte Sequenz - jedes Element wird vom Iterator abgerufen, wann immer er es verwenden möchte, und daher wird die Aufzählung vom Konsumenten gesteuert.

Ein Observable ist eine träge produzierte Sequenz - jedes Element wird dem Beobachter immer dann übergeben, wenn es der Sequenz hinzugefügt wird, und daher wird die Aufzählung vom Produzenten gesteuert.

29
tldr

Der Artikel Einfach effiziente funktionale Reaktivität von Conal Elliott ( direktes PDF , 233 KB) ist ein ziemlich gute Einführung. Die entsprechende Bibliothek funktioniert auch.

Das Papier wird jetzt von einem anderen Papier abgelöst, Push-Pull-Funktion, reaktive Programmierung ( direktes PDF 286 KB).

29
scvalex

Alter, das ist eine verdammt geniale Idee! Warum habe ich das 1998 nicht erfahren? Wie auch immer, hier ist meine Interpretation des Fran Tutorials. Vorschläge sind sehr willkommen. Ich denke darüber nach, eine Spiel-Engine auf dieser Basis zu starten.

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

Kurz gesagt: Wenn jede Komponente wie eine Zahl behandelt werden kann, kann das gesamte System wie eine mathematische Gleichung behandelt werden, oder?

18
Dan Ross

Paul Hudaks Buch The Haskell School of Expression ist nicht nur eine gute Einführung in Haskell, sondern verbringt auch viel Zeit mit FRP. Wenn Sie ein Anfänger mit FRP sind, empfehle ich es sehr, um Ihnen ein Gefühl dafür zu geben, wie FRP funktioniert.

Es gibt auch etwas, das aussieht wie eine Neufassung dieses Buches (veröffentlicht 2011, aktualisiert 2014), The Haskell School of Music .

14
Curt J. Sampson

Nach den vorherigen Antworten scheint es, dass wir mathematisch einfach in einer höheren Reihenfolge denken. Anstatt einen Wert x mit dem Typ X zu denken, denken wir an eine Funktion x: TX, wobei T die Art der Zeit ist, sei es die natürlichen Zahlen, die ganzen Zahlen oder das Kontinuum. Wenn wir nun y: = x + 1 in die Programmiersprache schreiben, meinen wir tatsächlich die Gleichung y (t =) = x (t) + 1.

10
Yuning

Verhält sich wie eine Tabellenkalkulation. In der Regel basierend auf einem ereignisgesteuerten Framework.

Wie bei allen "Paradigmen" ist die Neuheit umstritten.

Aus meiner Erfahrung mit verteilten Flussnetzwerken von Akteuren kann es leicht zu einem allgemeinen Problem der Zustandskonsistenz über das Netzwerk von Knoten kommen, d.

Dies ist schwer zu vermeiden, da einige Semantiken Referenzschleifen oder Rundfunk implizieren und ziemlich chaotisch sein können, wenn das Netzwerk der Akteure sich einem unvorhersehbaren Zustand annähert (oder nicht).

In ähnlicher Weise können einige Zustände trotz klar definierter Kanten nicht erreicht werden, da der globale Zustand von der Lösung abweicht. 2 + 2 kann oder kann nicht 4 werden, abhängig davon, wann die 2 zu 2 wurde und ob sie so geblieben sind. Tabellenkalkulationen verfügen über synchrone Uhren und Schleifenerkennung. Verteilte Schauspieler tun dies im Allgemeinen nicht.

Alles gute Spaß :).

9
emperorz

Ich habe dieses nette Video auf dem Clojure-Subreddit über FRP gefunden. Es ist ziemlich einfach zu verstehen, auch wenn Sie Clojure nicht kennen.

Hier ist das Video: http://www.youtube.com/watch?v=nket0K1RXU4

Hier ist die Quelle, auf die sich das Video in der zweiten Hälfte bezieht: https://github.com/Cicayda/yolk-examples/blob/master/src/yolk_examples/client/autocomplete.cljs

8
Daniel Kaplan

Dieser Artikel von Andre Staltz ist die beste und klarste Erklärung, die ich bisher gesehen habe.

Einige Zitate aus dem Artikel:

Reaktive Programmierung ist die Programmierung mit asynchronen Datenströmen.

Darüber hinaus steht Ihnen eine erstaunliche Sammlung von Funktionen zur Verfügung, mit denen Sie jeden dieser Streams kombinieren, erstellen und filtern können.

Hier ist ein Beispiel für die fantastischen Diagramme, die Teil des Artikels sind:

Click event stream diagram

7
GreenGiant

Es geht um mathematische Datentransformationen über die Zeit (oder das Ignorieren der Zeit).

Im Code bedeutet dies funktionale Reinheit und deklarative Programmierung.

Zustandsfehler sind ein großes Problem im Standard-Imperativ-Paradigma. Verschiedene Codebits können einen gemeinsam genutzten Zustand zu verschiedenen "Zeiten" in der Programmausführung ändern. Das ist schwer zu bewältigen.

In FRP beschreiben Sie (wie bei der deklarativen Programmierung), wie Daten von einem Zustand in einen anderen übergehen und was sie auslösen. Auf diese Weise können Sie die Zeit ignorieren, da Ihre Funktion einfach auf ihre Eingaben reagiert und ihre aktuellen Werte verwendet, um einen neuen zu erstellen. Dies bedeutet, dass der Status im Diagramm (oder Baum) der Transformationsknoten enthalten und funktionsrein ist.

Dies reduziert die Komplexität und die Debugging-Zeit erheblich.

Denken Sie an den Unterschied zwischen A = B + C in Mathematik und A = B + C in einem Programm. In der Mathematik beschreiben Sie eine Beziehung, die sich nie ändern wird. In einem Programm heißt es, dass "Im Moment" A B + C ist. Der nächste Befehl könnte jedoch B ++ sein. In diesem Fall ist A nicht gleich B + C. In der mathematischen oder deklarativen Programmierung ist A immer gleich B + C, egal zu welchem ​​Zeitpunkt Sie fragen.

Entfernen Sie also die Komplexität des gemeinsamen Zustands und ändern Sie die Werte im Laufe der Zeit. Ihr Programm ist viel einfacher zu überlegen.

Ein EventStream ist ein EventStream + eine Transformationsfunktion.

Ein Verhalten ist ein EventStream + Ein Wert im Speicher.

Wenn das Ereignis ausgelöst wird, wird der Wert durch Ausführen der Transformationsfunktion aktualisiert. Der Wert, den dies erzeugt, wird im Verhaltensspeicher gespeichert.

Verhalten können zusammengesetzt werden, um neue Verhaltensweisen zu erzeugen, die eine Transformation auf N andere Verhaltensweisen darstellen. Dieser zusammengesetzte Wert wird neu berechnet, wenn die Eingabeereignisse (Verhalten) ausgelöst werden.

"Da Beobachter zustandslos sind, benötigen wir häufig mehrere von ihnen, um eine Zustandsmaschine wie im Drag-Beispiel zu simulieren. Wir müssen den Zustand speichern, in dem alle beteiligten Beobachter darauf zugreifen können, z.

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

5
Jay Shepherd

Die kurze und klare Erklärung zu Reactive Programming erscheint auf Cyclejs - Reactive Programming , es werden einfache und visuelle Beispiele verwendet.

Ein [Modul/Komponente/Objekt] ist reaktiv bedeutet, dass es vollständig für die Verwaltung seines eigenen Zustands verantwortlich ist, indem es auf externe Ereignisse reagiert.

Was ist der Vorteil dieses Ansatzes? Dies ist eine Umkehrung der Kontrolle , hauptsächlich weil [Modul/Komponente/Objekt] für sich selbst verantwortlich ist und die Kapselung mit privaten Methoden gegen öffentliche Methoden verbessert.

Es ist ein guter Ausgangspunkt, keine vollständige Wissensquelle. Von dort konnte man zu komplexeren und tieferen Papieren springen.

2
pdorgambide

FRP ist eine Kombination aus funktionaler Programmierung (Programmierparadigma, das auf der Idee von Alles ist eine Funktion aufgebaut ist) und reaktivem Programmierparadigma (das auf der Idee basiert, dass Alles ein Strom ist (Beobachter und beobachtbare Philosophie)). Es soll das Beste der Welt sein.

Schauen Sie sich zunächst den Beitrag von Andre Staltz zur reaktiven Programmierung an.

0
kg11

Testen Sie Rx, Reactive Extensions for .NET. Sie weisen darauf hin, dass Sie mit IEnumerable im Grunde genommen aus einem Stream "ziehen". Linq-Abfragen über IQueryable/IEnumerable sind Mengenoperationen, die die Ergebnisse aus einer Menge "saugen". Mit denselben Operatoren über IObservable können Sie jedoch Linq-Abfragen schreiben, die "reagieren".

Beispielsweise könnten Sie eine Linq-Abfrage wie folgt schreiben (von m in MyObservableSetOfMouseMovements, wobei m.X <100 und m.Y <100 neuen Punkt auswählen (m.X, m.Y)).

und mit den Rx-Erweiterungen ist es soweit: Sie haben UI-Code, der auf den eingehenden Strom von Mausbewegungen reagiert und zeichnet, wann immer Sie sich in der 100.100-Box befinden ...

0
Sentinel