Nachdem ich viele Seiten über FRP gelesen hatte, stieß ich endlich auf dieses aufschlussreiche Schreiben über FRP, das mir endlich verständlich machte, worum es bei FRP wirklich geht.
Ich zitiere unten Heinrich Apfelmus (Autor der reaktiven Banane).
Was ist die Essenz der funktionalen reaktiven Programmierung?
Eine häufige Antwort wäre, dass es bei „FRP darum geht, ein System anhand zeitlich variierender Funktionen anstelle eines veränderlichen Zustands zu beschreiben“, und das wäre sicherlich nicht falsch. Dies ist der semantische Standpunkt. Aber meiner Meinung nach ergibt sich die tiefere und zufriedenstellendere Antwort aus dem folgenden rein syntaktischen Kriterium:
Die Essenz der funktionalen reaktiven Programmierung besteht darin, das dynamische Verhalten eines Werts zum Zeitpunkt der Deklaration vollständig anzugeben.
Nehmen Sie zum Beispiel einen Zähler: Sie haben zwei Schaltflächen mit den Bezeichnungen „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 ändern, wenn eine Taste gedrückt wird. 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 Rest des Programmtextes enthalten. Im Gegensatz dazu spezifiziert die funktionale reaktive Programmierung das gesamte dynamische Verhalten zum Zeitpunkt der Deklaration wie folgt:
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 nur seine Definition betrachten. Alles, was damit passieren kann, wird auf der rechten Seite angezeigt. Dies steht in starkem Gegensatz zu dem imperativen Ansatz, bei dem nachfolgende Deklarationen das dynamische Verhalten zuvor deklarierter Werte ändern können.
Nach meinem Verständnis besteht ein FRP-Programm aus einer Reihe von Gleichungen:
j
ist diskret: 1,2,3,4 ...
f
hängt davon ab, t
dass dies die Möglichkeit beinhaltet, externe Reize zu modellieren
Der gesamte Status des Programms ist in Variablen eingekapselt x_i
Die FRP-Bibliothek kümmert sich um die fortschreitende Zeit, mit anderen Worten, j
um j+1
.
Ich erkläre diese Gleichungen in diesem Video viel detaillierter .
BEARBEITEN:
Ungefähr zwei 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 dies normalerweise) ein wichtiges praktisches Problem lösen: die Ungültigmachung des Caches .
Die Gleichungen für x_i
-s beschreiben einen Abhängigkeitsgraphen. Wenn einige der x_i
Änderungen zu einem bestimmten Zeitpunkt vorgenommen werden, müssen j
nicht alle anderen x_i'
Werte j+1
aktualisiert werden, sodass nicht alle Abhängigkeiten neu berechnet werden müssen, da einige x_i'
möglicherweise unabhängig von sind x_i
.
Darüber hinaus können x_i
-s, die sich ändern, schrittweise aktualisiert werden. Zum Beispiel lassen betrachtet die Karte Operation f=g.map(_+1)
in Scala, wo f
und g
sind List
von Ints
. Hier f
entspricht x_i(t_j)
und g
ist x_j(t_j)
. Wenn ich nun ein Element voranstelle g
, wäre es verschwenderisch, die map
Operation für alle Elemente in auszuführen g
. 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 (die x_i
-s) in FRP können als zwischengespeicherte Berechnungen 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.