Dopo aver letto molte pagine su FRP, alla fine mi sono imbattuto in questo scritto illuminante su FRP, che mi ha fatto finalmente capire di cosa si tratta veramente.
Cito di seguito Heinrich Apfelmus (autore di banane reattive).
Qual è l'essenza della programmazione reattiva funzionale?
Una risposta comune sarebbe che "FRP si basa sulla descrizione di un sistema in termini di funzioni che variano nel tempo anziché in uno stato mutabile", e ciò non sarebbe certamente sbagliato. Questo è il punto di vista semantico. Ma a mio avviso, la risposta più profonda e soddisfacente è data dal seguente criterio puramente sintattico:
L'essenza della programmazione reattiva funzionale è di specificare completamente il comportamento dinamico di un valore al momento della dichiarazione.
Ad esempio, prendi l'esempio di un contatore: hai due pulsanti etichettati “Su” e “Giù” che possono essere usati per aumentare o diminuire il contatore. Imperativamente, dovresti prima specificare un valore iniziale e poi cambiarlo ogni volta che viene premuto un pulsante; qualcosa come questo:
counter := 0 -- initial value
on buttonUp = (counter := counter + 1) -- change it later
on buttonDown = (counter := counter - 1)
Il punto è che al momento della dichiarazione viene specificato solo il valore iniziale per il contatore; il comportamento dinamico del contatore è implicito nel resto del testo del programma. Al contrario, la programmazione reattiva funzionale specifica l'intero comportamento dinamico al momento della dichiarazione, in questo modo:
counter :: Behavior Int
counter = accumulate ($) 0
(fmap (+1) eventUp
`union` fmap (subtract 1) eventDown)
Ogni volta che vuoi capire le dinamiche del contatore, devi solo guardare la sua definizione. Tutto ciò che può accadere apparirà sul lato destro. Ciò è in netto contrasto con l'approccio imperativo in cui dichiarazioni successive possono modificare il comportamento dinamico di valori precedentemente dichiarati.
Quindi, nella mia comprensione, un programma FRP è un insieme di equazioni:
j
è discreto: 1,2,3,4 ...
f
dipende da t
ciò, questo incorpora la possibilità di modellare stimoli esterni
tutto lo stato del programma è incapsulato in variabili x_i
La libreria FRP si occupa di far progredire il tempo, in altre parole, di prendere j
aj+1
.
Spiego queste equazioni in modo molto più dettagliato in questo video.
MODIFICARE:
Circa 2 anni dopo la risposta originale, recentemente sono giunto alla conclusione che le implementazioni FRP hanno un altro aspetto importante. Devono (e di solito lo fanno) risolvere un importante problema pratico: invalidare la cache .
Le equazioni per x_i
-s descrivono un grafico delle dipendenze. Quando alcune delle x_i
modifiche alla volta j
non è necessario aggiornare tutti gli altri x_i'
valori j+1
, non è necessario ricalcolare tutte le dipendenze perché alcune x_i'
potrebbero essere indipendenti x_i
.
Inoltre, x_i
-s che cambiano può essere aggiornato in modo incrementale. Per esempio prendiamo in considerazione un'operazione di carta f=g.map(_+1)
a Scala, dove f
e g
sono List
di Ints
. Qui f
corrisponde x_i(t_j)
ed g
è x_j(t_j)
. Ora se antepongo un elemento a g
allora sarebbe inutile eseguire l' map
operazione per tutti gli elementi in g
. Alcune implementazioni di FRP (ad esempio reflex-frp ) mirano a risolvere questo problema. Questo problema è noto anche come calcolo incrementale.
In altre parole, i comportamenti (i x_i
-s) in FRP possono essere pensati come calcoli memorizzati nella cache. È compito del motore FRP invalidare e ricalcolare in modo efficiente queste cache (le x_i
-s) se alcune delle f_i
-s cambiano.