Indice
1 Definizione . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2 Caratteristiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2.1 Propagazione del cambiamento . . . . . . . . . . . . . . . . 3
2.2 Strategie di propagazione . . . . . . . . . . . . . . . . . . . . 7
2.3 Modelli di valutazione . . . . . . . . . . . . . . . . . . . . . 9
2.4 Glitch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Metodi di rimozione . . . . . . . . . . . . . . . . . 12
2.5 Flussi asincroni di dati ed eventi . . . . . . . . . . . . . . . . 15
2.6 Entità osservabili e osservatrici . . . . . . . . . . . . . . . . 17
Entità osservabile . . . . . . . . . . . . . . . . . . . 17
Entità osservatrice . . . . . . . . . . . . . . . . . . 18
3 Contesti d’utilizzo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4 Vantaggi rispetto ad altri approcci . . . . . . . . . . . . . . . . . . 19
5 Programmazione reattiva distribuita . . . . . . . . . . . . . . . . . 21
6 Programmazione reattiva funzionale . . . . . . . . . . . . . . . . . . 22
Functional Programming . . . . . . . . . . . . . . . 22
Functional Reactive Programming . . . . . . . . . 23
6.1 Astrazioni di base . . . . . . . . . . . . . . . . . . . . . . . . 24
7 Tassonomia dei linguaggi reattivi . . . . . . . . . . . . . . . . . . . 25
8 Uso industriale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
9 Il manifesto reattivo . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Bibliografia 32
1
Programmazione reattiva
Questi appunti si propongono di descrivere il panorama della programmazione
reattiva partendo dal principio, cioè dalle sue caratteristiche fondanti, per poi
procedere a piccoli passi verso argomenti più di dettaglio cercando sempre, prima
dell’introduzione di ognuno di essi (per quanto possibile), di fornire al lettore le
nozioni di base per poter affrontare la lettura senza difficoltà.
1 Definizione
Premettendo che non è tuttora esistente una definizione ufficialmente riconosciu-
ta di programmazione reattiva (RP, Reactive Programming), una di quelle che
condensa al meglio la filosofia di questo paradigma è probabilmente la seguente:
la programmazione reattiva è un paradigma di programmazione che si
avvale dei concetti del pattern Observer per gestire ed elaborare dati
ed eventi tramite flussi asincroni e osservabili dall’esterno, in accordo
con la nozione di propagazione del cambiamento.
2 Caratteristiche
Dalla definizione si può evincere come la programmazione reattiva sia un para-
digma fortemente correlato all’utilizzo di stream asincroni di dati/eventi e perciò
particolarmente utile nella programmazione event-driven, efficace per lo sviluppo
di applicazioni che necessitano di risposte reattive a stimoli provenienti in maniera
asincrona da molteplici fonti, situazione riscontrabile tipicamente su piattaforme
web e mobile. Questo approccio prevalentemente asincrono indebolisce i legami
2
INDICE 3
(loose coupling) tra i componenti dei programmi, producendo codice altamente
modulare e dunque più facilmente modificabile, manutenibile ed estensibile.
È importante precisare che si parla di flussi di informazioni osservabili, i cui
dati ed eventi emessi sono perciò catturabili dall’esterno e gestibili all’occorrenza
da tutti coloro che necessitino di continui aggiornamenti riguardo uno specifico
evento d’interesse. All’interno di questo contesto trova dunque facilmente posto il
1
Design Pattern Observer [11] della GoF , il quale definisce una o più dipendenze
fra oggetti, cosı̀ che quando uno di essi cambia stato, tutti i suoi sottoscrittori
vengono notificati automaticamente e possono reagire di conseguenza.
Per quanto riguarda invece il concetto di propagazione del cambiamento (Pro-
pagation of change), si tratta di una nozione presente in letteratura e su cui si
fonda il pensiero della programmazione reattiva; verrà esposto in maniera più
dettagliata nella prossima sottosezione.
2.1 Propagazione del cambiamento
Nel paradigma di programmazione reattiva tutto può essere trasmesso tramite
stream asincroni: dati, eventi, messaggi, chiamate, situazioni di successo e di er-
rore. Si possono osservare questi flussi e reagire quando da essi vengono emessi
valori. Questo ha un effetto interessante sulle applicazioni poiché tende a farle
diventare intrinsecamente asincrone ed event-driven, reattive nel gestire i cam-
biamenti in atto e le loro propagazioni spesso prive di sincronia. All’interno del
contesto delineatosi si afferma l’idea di propagazione del cambiamento, concetto
che sta alla base del paradigma in esame.
Si prenda come esempio esplicativo la seguente istruzione in pseudo-codice:
a := b + c
Se si utilizza un linguaggio imperativo, l’assegnamento di cui sopra applica alla
variabile il risultato dovuto alla somma tra le variabili e e questo avvie-
a b c,
ne nel preciso istante nel quale l’espressione viene valutata. Ciò significa che un
cambiamento successivo dei valori di e/o di non produce alcun effetto sulla
b c
variabile la quale mantiene sempre il valore originariamente calcolato (a meno,
a,
1 Gang of Four, cosı̀ vengono spesso chiamati Erich Gamma, Richard Helm, Ralph Johnson e
John Vlissides, autori del libro Design Patterns: Elements of Reusable Object-Oriented Software.
3
4 INDICE
ovviamente, di una nuova valutazione dell’espressione stessa). In programmazione
reattiva, invece, il concetto di propagazione del cambiamento rende il procedi-
mento differente: il valore di viene infatti sempre aggiornato automaticamente
a
ogniqualvolta i valori delle variabili e/o cambiano, senza prevedere la necessità
b c
da parte del programma di rieseguire l’istruzione di assegnamento di a.
La logica della propagazione del cambiamento può essere per esempio imple-
mentata per mezzo di un grafo i cui nodi rappresentano i valori da tenere aggiornati
e i cui archi rappresentano le relazioni di dipendenza che coinvolgono i nodi. Tra-
mite questo tipo di architettura si viene quindi a creare una rete di dipendenze
computazionali ed il cambiamento di un nodo (cioè di un valore) scatena una
reazione che implica l’attraversamento degli eventuali archi ad esso collegati e il
conseguente aggiornamento di tutti i nodi raggiunti.
Per rendere più chiari questi concetti, viene esposto di seguito un semplice
esempio in pseudo-codice. number1 := 4
number2 := 6
sum := number1 + number2
average := sum / 2
Il codice mostra l’assegnamento dei valori 4 e 6 a due variabili (number1 e number2),
con successivo calcolo e salvataggio della somma e della media aritmetica (ri-
spettivamente all’interno delle variabili ed In questo caso risulta
sum average).
estremamente semplice costruire il corrispondente grafo delle dipendenze per la
propagazione dei cambiamenti, in figura 1:
4
INDICE 5
Figura 1: Grafo delle dipendenze, somma e media di due numeri
Si può facilmente notare come il primo nodo genitore (average) abbia solo un
nodo figlio (sum), perciò dipenda solo da esso, mentre quest’ultimo dipenda invece
da due nodi figlio (rappresentati dalle variabili e questi ultimi
number1 number2);
sono attualmente anche nodi periferici del grafo orientato, perciò si possono consi-
derare ‘autonomi‘ perché non dipendono da nessun altro nodo figlio e nell’attuale
configurazione non possono quindi mai subire modifiche propagate.
Seguendo l’approccio imperativo, i valori delle variabili ed vengo-
sum average
no assegnati durante l’esecuzione del frammento di codice e non risultano aggior-
nati dopo una modifica dei valori delle variabili che rappresentano i due numeri.
Al contrario, seguendo l’approccio dettato dalla programmazione reattiva e in par-
ticolare dal concetto di propagazione del cambiamento su cui il paradigma si basa,
ogni modifica dei valori delle variabili e propaga il cambiamento
number1 number2
al nodo genitore il quale aggiorna la somma e propaga a sua volta l’informa-
sum,
zione di cambiamento al nodo genitore il quale infine aggiorna il valore
average,
della media aritmetica.
La situazione cambia leggermente nel prossimo esempio, sempre in pseudo-
codice. 5
6 INDICE
numbers := [1, 2, 3, 4, 5, 6]
sum := 0
for each number in numbers
sum := sum + number
end
average := sum / count(numbers)
In questo caso non sono presenti solo due numeri di cui far la somma e la media,
bensı̀ un array di numeri grande a piacere, perciò è necessario iterare l’intero
vettore per calcolare la somma totale dei suoi elementi e ricavarne la lunghezza
(count(numbers)) per quantificare il valore al denominatore nella formula per
la media aritmetica. In figura 2 viene mostrato il grafo delle dipendenze per la
propagazione dei cambiamenti relativo a questo esempio:
Figura 2: Grafo delle dipendenze, somma e media di un array di numeri
Si può osservare come il nodo genitore contrariamente all’esempio prece-
average,
dente, si ritrovi ora ad avere due nodi figlio da cui dipende, e e come
sum numbers,
quest’ultimo abbia due nodi genitore (average e Questo accade per il sem-
sum).
plice motivo che la variabile contenente la media viene assegnata tramite un’espres-
sione che coinvolge entrambe le variabili (average := sum / count(numbers));
si trova a dipendere quindi non più solo dal valore della somma ma anche
average
dalla lunghezza dell’array di numeri. Se subisce una mutazione, la notifi-
numbers
ca del cambiamento viene propagata attraverso il grafo delle dipendenze a ed
sum
6
INDICE 7
2
ma solo il primo può essere aggiornato poiché il secondo non possiede
average,
ancora tutte la nuove informazioni per poter calcolare la media; una volta che an-
che il nodo notifica il cambiamento del valore della somma al nodo genitore,
sum
allora questo viene aggiornato.
2.2 Strategie di propagazione
Le principali strategie adottabili per la propagazione del cambiamento nella pro-
grammazione reattiva sono le seguenti:
• Complete propagation
Ogni nodo, durante la propagazione, invia il suo attuale stato completo ai
nodi dipendenti; in questo caso, tutto lo stato precedente del nodo viene
perso durante l’aggiornamento al nuovo stato. Questa strategia presenta
un alto carico computazionale, in quanto ogni nodo mutato propaga il suo
completo stato, perciò non è adatto nei casi in cui il grafo delle dipenden-
ze computazionali sia particolarmente complesso o nel caso in cui gli stati
necessitino di grandi quantità di dati per essere descritti.
• Delta propagation
Nel momento della propagazione i nodi interessati inviano solo una porzione
(un delta) del loro stato ai nodi dipendenti, cioè solo le frazioni di infor-
mazione che sono state effettivamente coinvolte da modifiche. Le porzioni
di stato precedenti non soggette ad alterazioni permangono all’interno dei
nodi, non vengono perse. Questo tipo di approccio tende a minimizzare la
quantità di dati da trasportare lungo la catena di dipendenze durante la pro-
pagazione dei cambiamenti, dunque risulta efficiente anche nei casi in cui si
presenti un grafo delle dipendenze complesso o una gran quantità di dati per
la rappresentazione degli stati, situazioni nelle quali la complete propagation
mostra invece limitazioni.
2 Nel caso in cui calcolasse comunque la media senza possedere tutte le informazioni
average
aggiornate si potrebbe verificare un malfunzionamento dovuto ad inconsistenza dei dati chiamato
glitch, argomento della sottosezione 1.2.4. 7
8 INDICE
• Batch propagation
La batch propagation è un approccio che prevede la non immediata pro-
pagazione dei cambiamenti, cioè la propagazione ritardata nel tempo. È
un metodo di ottimizzazione: se due o più cambiamenti vicini nel tempo
si annullano a vicenda (fanno cioè sı̀ che si ritorni allo stato di partenza,
precedente ai cambiamenti stessi), oppure un aggiornamento non modifica
alcuno stato, la propagazione non avviene, in quanto non è in atto nessun
cambiamento complessivo nel concreto. Al contrario, nel caso due o più
cambiamenti vicini nel tempo generino un’effettiva mutazione di stato nel
grafo, viene propagato solo l’ultimo cambiamento, infatti i precedenti risul-
tano ormai obsoleti. Questa strategia minimizza il numero di propagazioni e
quindi di percorrenza delle dipendenze nel grafo e di carico computazionale
complessivo, ma tende a rendere il sistema meno reattivo ai cambiamenti,
poiché essi vengono ritardati nel tempo. É dunque cruciale adottare un tem-
po di ritardo corretto, al fine di ritardare il cambiamento riducendo il carico
di elaborazione ma al tempo stesso non diminuire eccessivamente la reatti-
vità del sistema; si delinea quindi una strategia efficace nel caso in cui le
computazioni e il trasferimento di dati tra i nodi si rivelino particolarmente
onerosi e sia tollerabile un leggero ritardo di reattività ai cambiamenti.
• Invalidity notification propagation
Non si delinea propriamente come una strategia implementativa, quanto più
come una ”sotto-strategia”, cioè un approccio utilizzabile potenzialmente ac-
canto ad ognuna delle tre strategie precedentemente descritte. É utilizzabile
inoltre soltanto nei casi in cui il sistema di interesse possieda un grafo del-
le dipendenze con direzionalità di propagazione di tipo pull oppure hybrid
(verranno descritti nella prossima sottosezione). La invalidity notification
propagation consiste nella richiesta di aggiornamento (pull update) da parte
di un nodo che riscontra di possedere uno stato non valido, oppure che riceve
un messaggio di propagazione non valido. In questo caso il nodo interessato
scarta l’anomalia e richiede ai nodi da cui dipende un nuovo aggiornamento
in merito. Dopo la ricezione del nuovo aggiornamento, e solo nel caso questo
risulti valido, il nodo si aggiorna e lo propaga come di consueto attraverso
le dipendenze ad esso collegate.
Si ricorda che è possibile anche progettare architetture reattive che contemplino
la contemporanea presenza di più strategie, fra loro non in contrasto, al fine di
8
INDICE 9
ricavare vantaggi da tutte in base alle differenti situazioni. Un’ottima idea può
essere per esempio quella di impiegare la delta propagation insieme alla batch
propagation.
2.3 Modelli di valutazione
Per modello di valutazione (evaluation model ) nella Reactive Programming si in-
tende la dinamica direzionale adottata per la propagazione dei cambiamenti nel
grafo delle dipendenze computazionali del sistema reattivo. Il modello viene appli-
cato a livello di linguaggio e rimane trasparente al programmatore, che non deve
quindi preoccuparsi di propagare i dati manualmente.
Come già specificato precedentemente, un nodo trasmette il cambiamento ad
altri nodi suoi dipendenti seguendo la rete di relazioni rappresentata dal grafo; il
nodo che invia può essere quindi visto come un produttore di dati (informazioni
di cambiamento) e i nodi che ricevono come dei consumatori di dati, in accordo
con la logica produttore-consumatore evidenziata in figura 3.
A questo punto, però, bisogna prendere un’importante decisione di progetta-
zione: è necessario infatti scegliere quale modello di valutazione adottare, ognuno
di essi offre vantaggi in alcune situazioni e svantaggi in altre. La letteratura pre-
senta due principali modelli [1][9] a cui si aggiunge un terzo ibrido fra i primi
due:
• Push-based
Nel modello push-based la reazione ha inizio quando un nodo produttore
(origine) cambia stato aggiornandosi, in questo caso ”spinge” (push) l’infor-
mazione di cambiamento attraverso il grafo verso i nodi consumatori suoi
dipendenti (destin
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
-
Programmazione web
-
Programmazione a Oggetti - Teoria
-
Appunti Linguaggi di programmazione
-
Programmazione - programmazione Java 1