[Functional-programming] Cos'è la programmazione reattiva (funzionale)?


Answers

Nella pura programmazione funzionale, non ci sono effetti collaterali. Per molti tipi di software (ad esempio, qualsiasi interazione con l'utente) sono necessari effetti collaterali a un certo livello.

Un modo per ottenere un comportamento simile all'effetto collaterale pur mantenendo uno stile funzionale è utilizzare la programmazione reattiva funzionale. Questa è la combinazione di programmazione funzionale e programmazione reattiva. (L'articolo di Wikipedia a cui sei collegato riguarda quest'ultimo.)

L'idea alla base della programmazione reattiva è che ci sono alcuni tipi di dati che rappresentano un valore "nel tempo". I calcoli che coinvolgono questi valori mutevoli nel tempo avranno valori che cambiano nel tempo.

Ad esempio, potresti rappresentare le coordinate del mouse come una coppia di valori interi-nel tempo. Diciamo che abbiamo qualcosa di simile (questo è pseudo-codice):

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

In qualsiasi momento, x e y avrebbero le coordinate del mouse. A differenza della programmazione non reattiva, è necessario eseguire questa assegnazione solo una volta e le variabili x e y rimarranno "aggiornate" automaticamente. Questo è il motivo per cui la programmazione reattiva e la programmazione funzionale funzionano così bene insieme: la programmazione reattiva elimina la necessità di mutare le variabili, consentendo comunque di fare molto di ciò che si potrebbe ottenere con mutazioni variabili.

Se eseguiamo alcuni calcoli basati su questo, i valori risultanti saranno anche valori che cambiano nel tempo. Per esempio:

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

In questo esempio, minX sarà sempre 16 in meno rispetto alla coordinata x del puntatore del mouse. Con le librerie sensibili al reattivo potresti dire qualcosa del tipo:

rectangle(minX, minY, maxX, maxY)

E una casella 32x32 sarà disegnata attorno al puntatore del mouse e la seguirà ovunque si muova.

Ecco un bel documento sulla programmazione reattiva funzionale .

Question

Ho letto l'articolo di Wikipedia sulla programmazione reattiva . Ho letto anche il piccolo articolo sulla programmazione reattiva funzionale . Le descrizioni sono piuttosto astratte.

  1. Cosa significa in pratica la programmazione reattiva funzionale (FRP)?
  2. In cosa consiste la programmazione reattiva (al contrario della programmazione non reattiva?)?

Il mio background è in lingue imperative / OO, quindi una spiegazione che si riferisce a questo paradigma sarebbe apprezzata.




Questo articolo di Andre Staltz è la spiegazione migliore e più chiara che ho visto finora.

Alcune citazioni dall'articolo:

La programmazione reattiva è la programmazione con flussi di dati asincroni.

Inoltre, ti viene fornita una straordinaria serie di funzioni per combinare, creare e filtrare qualsiasi di questi flussi.

Ecco un esempio dei fantastici diagrammi che fanno parte dell'articolo:




Dopo aver letto molte pagine su FRP mi sono finalmente imbattuto in this scritto illuminante su FRP, finalmente mi ha fatto capire di cosa tratta veramente FRP.

Cito qui di seguito Heinrich Apfelmus (autore di banana reattiva).

Qual è l'essenza della programmazione reattiva funzionale?

Una risposta comune potrebbe essere che "il FRP è tutto basato sulla descrizione di un sistema in termini di funzioni che variano nel tempo anziché in uno stato mutabile", e che certamente non sarebbe sbagliato. Questo è il punto di vista semantico. Ma a mio parere, la risposta più profonda e soddisfacente è data dal seguente criterio puramente sintattico:

L'essenza della programmazione reattiva funzionale è quella di specificare il comportamento dinamico di un valore completamente al momento della dichiarazione.

Ad esempio, prendi l'esempio di un contatore: hai due pulsanti con l'etichetta "Su" e "Giù" che possono essere usati per incrementare o decrementare il contatore. Imperativamente, devi 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 la dinamica 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 le dichiarazioni successive possono modificare il comportamento dinamico dei valori precedentemente dichiarati.

Quindi, nella mia comprensione, un programma FRP è un insieme di equazioni:

j è discreto: 1,2,3,4 ...

f dipende da ciò che incorpora la possibilità di modellare stimoli esterni

tutto lo stato del programma è incapsulato in variabili x_i

La libreria FRP si occupa del tempo di avanzamento, in altre parole, tenendo da j a j+1 .

Spiego queste equazioni in modo molto più dettagliato in this video.

MODIFICARE:

Circa 2 anni dopo la risposta originale, di recente sono giunto alla conclusione che le implementazioni di FRP hanno un altro aspetto importante. Devono (e di solito fanno) risolvere un importante problema pratico: invalidazione della cache .

Le equazioni per x_i -s descrivono un grafico di dipendenza. Quando alcune delle x_i cambiano al tempo j allora non tutti gli altri valori x_i' di j+1 devono essere aggiornati, quindi non tutte le dipendenze devono essere ricalcolate perché alcuni x_i' potrebbero essere indipendenti da x_i .

Inoltre, x_i -s che cambiano può essere aggiornato in modo incrementale. Per esempio consideriamo una operazione mappa f=g.map(_+1) in Scala, dove f e g sono List of Ints . Qui f corrisponde a x_i(t_j) e g è x_j(t_j) . Ora, se antepongo un elemento a g , sarebbe inutile eseguire l'operazione map per tutti gli elementi di g . Alcune implementazioni FRP (ad esempio reflex-frp ) mirano a risolvere questo problema. Questo problema è anche noto come calcolo incrementale.

In altre parole, i comportamenti (il x_i -s) in FRP possono essere pensati come calcoli memorizzati nella cache. È compito del motore FRP invalidare e ricalcolare in modo efficiente questi cache-s ( x_i -s) se alcuni degli f_i -s cambiano.




La spiegazione breve e chiara sulla programmazione reattiva appare su Cyclejs - Reactive Programming , utilizza campioni semplici e visivi.

Un [modulo / Componente / oggetto] è reattivo significa che è pienamente responsabile della gestione del proprio stato reagendo agli eventi esterni.

Qual è il vantaggio di questo approccio? È Inversion of Control , principalmente perché [module / Component / object] è responsabile di se stesso, migliorando l'incapsulamento usando metodi privati ​​contro quelli pubblici.

È un buon punto di partenza, non una fonte completa di conoscenza. Da lì puoi saltare a carte più complesse e profonde.




Il libro di Paul Hudak, The Haskell School of Expression , non è solo una bella introduzione a Haskell, ma impiega anche una buona quantità di tempo su FRP. Se sei un principiante con FRP, lo consiglio vivamente per darti un'idea di come funziona FRP.

C'è anche quello che sembra una nuova riscrittura di questo libro (pubblicato nel 2011, aggiornato nel 2014), The Haskell School of Music .




Per me si tratta di 2 diversi significati di symbol = :

  1. In matematica x = sin(t) significa che x è diverso nome per sin(t) . Quindi scrivere x + y è la stessa cosa di sin(t) + y . La programmazione reattiva funzionale è come la matematica a questo riguardo: se si scrive x + y , viene calcolato con qualunque sia il valore di t al momento in cui viene utilizzato.
  2. Nei linguaggi di programmazione C-like (lingue imperative), x = sin(t) è un compito: significa che x memorizza il valore di sin(t) assunto al momento dell'assegnazione.



FRP è una combinazione di programmazione funzionale (paradigma di programmazione basato sull'idea di tutto è una funzione) e paradigma di programmazione reattiva (costruita sull'idea che tutto sia un flusso (osservatore e filosofia osservabile)). Dovrebbe essere il migliore dei mondi.

Dai un'occhiata al post di Andre Staltz sulla programmazione reattiva per iniziare.




Funziona come un foglio di calcolo come indicato. Solitamente basato su un framework basato su eventi.

Come tutti i "paradigmi", la novità è discutibile.

Dalla mia esperienza di reti di attori a flusso distribuito, può facilmente cadere preda di un problema generale di coerenza dello stato attraverso la rete di nodi, ovvero si finisce con un sacco di oscillazioni e intrappolamenti in loop strani.

Questo è difficile da evitare in quanto alcune semantiche implicano cicli referenziali o trasmissioni e possono essere piuttosto caotiche in quanto la rete di attori converge (o meno) in uno stato imprevedibile.

Allo stesso modo, alcuni stati potrebbero non essere raggiunti, nonostante abbiano margini ben definiti, perché lo stato globale si allontana dalla soluzione. 2 + 2 può o non può arrivare a essere 4 a seconda che i 2 diventino 2, e se siano rimasti così. I fogli di calcolo hanno clock sincroni e rilevamento loop. Generalmente gli attori distribuiti no.

Tutto molto divertente :).