Po przeczytaniu wielu stron o FRP w końcu natknąłem się na to oświecające pismo o FRP, w końcu zrozumiałem, na czym tak naprawdę polega FRP.
Cytuję poniżej Heinricha Apfelmusa (autora reaktywnego banana).
Jaka jest istota funkcjonalnego programowania reaktywnego?
Częstą odpowiedzią byłoby, że „FRP polega na opisaniu systemu w kategoriach funkcji zmieniających się w czasie zamiast stanu zmiennego”, a to z pewnością nie byłoby błędne. To jest semantyczny punkt widzenia. Ale moim zdaniem głębszej, bardziej satysfakcjonującej odpowiedzi udziela następujące czysto składniowe kryterium:
Istotą funkcjonalnego programowania reaktywnego jest całkowite określenie dynamicznego zachowania wartości w momencie deklaracji.
Weźmy na przykład licznik: masz dwa przyciski oznaczone „W górę” i „W dół”, których można użyć do zwiększenia lub zmniejszenia licznika. Koniecznie należy najpierw określić wartość początkową, a następnie ją zmienić po każdym naciśnięciu przycisku; coś takiego:
counter := 0 -- initial value
on buttonUp = (counter := counter + 1) -- change it later
on buttonDown = (counter := counter - 1)
Chodzi o to, że w momencie deklaracji podana jest tylko wartość początkowa licznika; dynamiczne zachowanie licznika jest niejawne w pozostałej części tekstu programu. Natomiast funkcjonalne programowanie reaktywne określa całe zachowanie dynamiczne w momencie deklaracji, takie jak:
counter :: Behavior Int
counter = accumulate ($) 0
(fmap (+1) eventUp
`union` fmap (subtract 1) eventDown)
Ilekroć chcesz zrozumieć dynamikę licznika, musisz tylko spojrzeć na jego definicję. Wszystko, co może się przydarzyć, pojawi się po prawej stronie. Jest to bardzo sprzeczne z imperatywnym podejściem, w którym kolejne deklaracje mogą zmienić dynamiczne zachowanie wcześniej zadeklarowanych wartości.
Tak więc, moim zdaniem, program FRP jest zbiorem równań:
j
jest dyskretny: 1,2,3,4 ...
f
zależy od t
tego, co obejmuje możliwość modelowania bodźców zewnętrznych
cały stan programu jest zamknięty w zmiennych x_i
Biblioteka FRP dba o postęp czasu, innymi słowy, j
do j+1
.
Wyjaśniam te równania bardziej szczegółowo w tym filmie.
EDYTOWAĆ:
Około 2 lata po oryginalnej odpowiedzi doszedłem niedawno do wniosku, że implementacje FRP mają jeszcze jeden ważny aspekt. Muszą (i zwykle robią) rozwiązać ważny problem praktyczny: unieważnienie pamięci podręcznej .
Równania dla x_i
-s opisują wykres zależności. Kiedy niektóre x_i
zmiany w danym momencie, j
nie wszystkie pozostałe x_i'
wartości j+1
muszą zostać zaktualizowane, więc nie wszystkie zależności muszą zostać ponownie obliczone, ponieważ niektóre x_i'
mogą być niezależne x_i
.
Ponadto x_i
-s, które się zmieniają, można stopniowo aktualizować. Dla przykładu rozważmy operację mapę f=g.map(_+1)
w Scala, gdzie f
i g
są List
od Ints
. Tutaj f
odpowiada x_i(t_j)
i g
jest x_j(t_j)
. Teraz, jeśli przygotuję do tego element g
, byłoby niepotrzebnie przeprowadzić map
operację dla wszystkich elementów w g
. Niektóre implementacje FRP (na przykład reflex-frp ) mają na celu rozwiązanie tego problemu. Ten problem jest również znany jako obliczanie przyrostowe.
Innymi słowy, zachowania ( x_i
-s) we FRP można traktować jako obliczenia buforowane. Mechanizm FRP ma za zadanie skutecznie unieważnić i ponownie obliczyć te pamięci podręczne ( x_i
-y), jeśli niektóre z f_i
nich się zmienią.