functional programming - programming - Cos'è la programmazione reattiva(funzionale)?




reactive programming wikipedia (12)

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.


Amico, questa è un'idea geniale! Perché non l'ho scoperto su questo nel 1998? Ad ogni modo, ecco la mia interpretazione del tutorial di Fran . I suggerimenti sono i benvenuti, sto pensando di avviare un motore di gioco basato su questo.

import pygame
from pygame.surface import Surface
from pygame.sprite import Sprite, Group
from pygame.locals import *
from time import time as epoch_delta
from math import sin, pi
from copy import copy

pygame.init()
screen = pygame.display.set_mode((600,400))
pygame.display.set_caption('Functional Reactive System Demo')

class Time:
    def __float__(self):
        return epoch_delta()
time = Time()

class Function:
    def __init__(self, var, func, phase = 0., scale = 1., offset = 0.):
        self.var = var
        self.func = func
        self.phase = phase
        self.scale = scale
        self.offset = offset
    def copy(self):
        return copy(self)
    def __float__(self):
        return self.func(float(self.var) + float(self.phase)) * float(self.scale) + float(self.offset)
    def __int__(self):
        return int(float(self))
    def __add__(self, n):
        result = self.copy()
        result.offset += n
        return result
    def __mul__(self, n):
        result = self.copy()
        result.scale += n
        return result
    def __inv__(self):
        result = self.copy()
        result.scale *= -1.
        return result
    def __abs__(self):
        return Function(self, abs)

def FuncTime(func, phase = 0., scale = 1., offset = 0.):
    global time
    return Function(time, func, phase, scale, offset)

def SinTime(phase = 0., scale = 1., offset = 0.):
    return FuncTime(sin, phase, scale, offset)
sin_time = SinTime()

def CosTime(phase = 0., scale = 1., offset = 0.):
    phase += pi / 2.
    return SinTime(phase, scale, offset)
cos_time = CosTime()

class Circle:
    def __init__(self, x, y, radius):
        self.x = x
        self.y = y
        self.radius = radius
    @property
    def size(self):
        return [self.radius * 2] * 2
circle = Circle(
        x = cos_time * 200 + 250,
        y = abs(sin_time) * 200 + 50,
        radius = 50)

class CircleView(Sprite):
    def __init__(self, model, color = (255, 0, 0)):
        Sprite.__init__(self)
        self.color = color
        self.model = model
        self.image = Surface([model.radius * 2] * 2).convert_alpha()
        self.rect = self.image.get_rect()
        pygame.draw.ellipse(self.image, self.color, self.rect)
    def update(self):
        self.rect[:] = int(self.model.x), int(self.model.y), self.model.radius * 2, self.model.radius * 2
circle_view = CircleView(circle)

sprites = Group(circle_view)
running = True
while running:
    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        if event.type == KEYDOWN and event.key == K_ESCAPE:
            running = False
    screen.fill((0, 0, 0))
    sprites.update()
    sprites.draw(screen)
    pygame.display.flip()
pygame.quit()

In breve: se ogni componente può essere trattato come un numero, l'intero sistema può essere trattato come un'equazione matematica, giusto?


Disclaimer: la mia risposta è nel contesto di rx.js - una libreria di "programmazione reattiva" per Javascript.

Nella programmazione funzionale, invece di scorrere attraverso ogni elemento di una raccolta, si applicano le funzioni di ordine superiore (HoF) alla raccolta stessa. Quindi l'idea alla base di FRP è che invece di elaborare ogni singolo evento, creare un flusso di eventi (implementato con un osservabile *) e applicare invece gli HoF. In questo modo è possibile visualizzare il sistema come pipeline di dati che collegano gli editori agli abbonati.

I principali vantaggi dell'utilizzo di un osservabile sono:
i) astrae lo stato dal tuo codice, ad esempio, se vuoi che il gestore di eventi venga licenziato solo per ogni 'n''evento, o smetta di sparare dopo i primi eventi' n ', o inizi a sparare solo dopo il primo' n 'eventi, puoi semplicemente usare gli HoF (filtro, takeUntil, skip rispettivamente) invece di impostare, aggiornare e controllare i contatori.
ii) migliora la località del codice - se hai 5 diversi gestori di eventi che modificano lo stato di un componente, puoi unire i loro osservabili e definire un singolo gestore di eventi sull'osservativo unito, combinando efficacemente 5 gestori di eventi in 1. Questo lo rende molto È facile ragionare su quali eventi nell'intero sistema possono influenzare un componente, poiché è tutto presente in un singolo gestore.

  • Un osservabile è il doppio di un Iterable.

Un Iterable è una sequenza consumata pigramente - ogni elemento viene tirato dall'iteratore ogni volta che lo desidera utilizzarlo, e quindi l'enumerazione è guidata dal consumatore.

Un osservabile è una sequenza prodotta pigramente - ogni oggetto è spinto all'osservatore ogni volta che viene aggiunto alla sequenza, e quindi l'enumerazione è guidata dal produttore.


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 :).




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.


OK, dalla conoscenza di fondo e dalla lettura della pagina di Wikipedia a cui hai indicato, sembra che la programmazione reattiva sia qualcosa come il flusso di dati, ma con specifici "stimoli" esterni che attivano un insieme di nodi da attivare ed eseguire i loro calcoli.

Ciò è particolarmente adatto alla progettazione dell'interfaccia utente, ad esempio, in cui il tocco di un controllo dell'interfaccia utente (ad esempio, il controllo del volume su un'applicazione di riproduzione musicale) potrebbe richiedere l'aggiornamento di vari elementi di visualizzazione e il volume effettivo dell'uscita audio. Quando modifichi il volume (un cursore, diciamo) che corrisponderebbe alla modifica del valore associato a un nodo in un grafico diretto.

Vari nodi con spigoli da quel nodo "value volume" verrebbero automaticamente innescati e tutti i calcoli e gli aggiornamenti necessari si propagherebbero naturalmente attraverso l'applicazione. L'applicazione "reagisce" allo stimolo dell'utente. La programmazione reattiva funzionale sarebbe solo l'implementazione di questa idea in un linguaggio funzionale, o generalmente all'interno di un paradigma di programmazione funzionale.

Per ulteriori informazioni su "dataflow computing", cerca queste due parole su Wikipedia o utilizza il tuo motore di ricerca preferito. L'idea generale è questa: il programma è un grafico diretto di nodi, ognuno dei quali esegue un semplice calcolo. Questi nodi sono collegati tra loro da collegamenti di grafici che forniscono gli output di alcuni nodi agli input di altri.

Quando un nodo attiva o esegue il suo calcolo, i nodi connessi alle sue uscite hanno i loro ingressi corrispondenti "innescati" o "contrassegnati". Ogni nodo con tutti gli input attivati ​​/ contrassegnati / disponibili viene attivato automaticamente. Il grafico potrebbe essere implicito o esplicito a seconda esattamente di come viene implementata la programmazione reattiva.

I nodi possono essere visti come sparati in parallelo, ma spesso vengono eseguiti in serie o con un parallelismo limitato (ad esempio, potrebbero esserci alcuni thread che li eseguono). Un esempio famoso è stato la Manchester Dataflow Machine , che (IIRC) ha utilizzato un'architettura di dati taggata per pianificare l'esecuzione dei nodi nel grafico attraverso una o più unità di esecuzione. Il calcolo del flusso di dati è abbastanza adatto a situazioni in cui i calcoli di trigger che generano in modo asincrono cascate di calcoli funzionano meglio che cercare di far governare l'esecuzione da un clock (o da un clock).

La programmazione reattiva importa questa idea di "cascata di esecuzione" e sembra pensare al programma in un modo simile al flusso di dati, ma con la condizione che alcuni dei nodi siano agganciati al "mondo esterno" e le cascate di esecuzione si attivino quando questi sensoriali -come i nodi cambiano. L'esecuzione del programma sembrerebbe quindi qualcosa di analogo a un arco riflesso complesso. Il programma può o meno essere sostanzialmente sessile tra stimoli o può stabilirsi in uno stato sostanzialmente sessile tra stimoli.

la programmazione "non reattiva" sarebbe programmata con una visione molto diversa del flusso di esecuzione e della relazione con gli input esterni. È probabile che sia in qualche modo soggettivo, dal momento che le persone saranno probabilmente tentate di dire qualcosa che risponde agli input esterni "reagisce" a loro. Ma guardando lo spirito della cosa, un programma che esegue il polling di una coda di eventi a intervalli fissi e invia tutti gli eventi trovati alle funzioni (o ai thread) è meno reattivo (perché partecipa solo all'input dell'utente a intervalli fissi). Di nuovo, è lo spirito della cosa qui: si può immaginare di inserire un'implementazione di polling con un intervallo di polling veloce in un sistema a un livello molto basso e programmarlo in modo reattivo.


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.

Scopri Rx, Reactive Extensions per .NET. Sottolineano che con IEnumerable stai praticamente 'tirando' da un flusso.Le query di Linq su IQueryable / IEnumerable sono operazioni impostate che "succhiano" i risultati da un set. Ma con gli stessi operatori su IObservable puoi scrivere query Linq che "reagiscono".

Ad esempio, è possibile scrivere una query Linq come (da m in MyObservableSetOfMouseMovements dove mX <100 e mY <100 seleziona un nuovo punto (mX, mY)).

e con le estensioni Rx, il gioco è fatto: hai un codice UI che reagisce al flusso in entrata di movimenti del mouse e disegna ogni volta che ti trovi nella casella 100.100 ...


Se vuoi avere un'idea di FRP, potresti iniziare con il vecchio tutorial di Fran del 1998, che ha illustrazioni animate. Per i documenti, iniziare con Animazione reattiva funzionale e quindi seguire i link sul link pubblicazioni sulla mia home page e il collegamento FRP sul wiki Haskell .

Personalmente, mi piace pensare a cosa significa FRP prima di affrontare come potrebbe essere implementato. (Il codice senza una specifica è una risposta senza una domanda e quindi "non è nemmeno sbagliato".) Quindi non descrivo FRP in termini di rappresentazione / implementazione come fa Thomas K in un'altra risposta (grafici, nodi, spigoli, spari, esecuzione, eccetera). Ci sono molti possibili stili di implementazione, ma nessuna implementazione dice cos'è FRP.

Sono in risonanza con la semplice descrizione di Laurence G che FRP parla di "tipi di dati che rappresentano un valore" nel tempo "". La programmazione imperativa convenzionale cattura questi valori dinamici solo indirettamente, attraverso lo stato e le mutazioni. La storia completa (passato, presente, futuro) non ha una rappresentazione di prima classe. Inoltre, solo i valori in evoluzione discreta possono essere (indirettamente) catturati, poiché il paradigma imperativo è temporalmente discreto. Al contrario, FRP cattura direttamente questi valori in evoluzione e non ha difficoltà con i valori in continua evoluzione.

Il FRP è anche insolito in quanto è concomitante senza incappare in un nido di topi teorici e pragmatici che affligge la concorrenza imperativa. Semanticamente, la concorrenza di FRP è a grana fine , determinata e continua . (Sto parlando di significato, non di implementazione. Un'implementazione può o meno implicare una concorrenza o un parallelismo.) La determinatezza semantica è molto importante per il ragionamento, sia rigoroso che informale. Mentre la concorrenza aggiunge un'enorme complessità alla programmazione imperativa (a causa di interleaving non deterministico), è senza sforzo in FRP.

Quindi, qual è il FRP? Potresti averlo inventato tu stesso. Inizia con queste idee:

  • I valori dinamici / in evoluzione (cioè i valori "nel tempo") sono valori di prima classe in se stessi. Puoi definirli e combinarli, passarli dentro e fuori dalle funzioni. Ho chiamato queste cose "comportamenti".

  • I comportamenti sono costituiti da pochi primitivi, come costanti (statici) e tempo (come un orologio), e quindi con una combinazione sequenziale e parallela. n i comportamenti sono combinati applicando una funzione n-ario (su valori statici), "punto-saggio", cioè, continuamente nel tempo.

  • Per spiegare i fenomeni discreti, avere un altro tipo (famiglia) di "eventi", ognuno dei quali ha un flusso (finito o infinito) di occorrenze. Ogni occorrenza ha un tempo e un valore associati.

  • Per trovare il vocabolario compositivo da cui tutti i comportamenti e gli eventi possono essere costruiti, gioca con alcuni esempi. Continua a decostruire in pezzi più generali / semplici.

  • Affinché tu sappia che sei su una base solida, dia all'intero modello una base compositiva, usando la tecnica della semantica denotazionale, il che significa semplicemente che (a) ogni tipo ha un corrispondente tipo matematico semplice e preciso di "significati", e ( b) ogni primitivo e operatore ha un significato semplice e preciso in funzione dei significati dei costituenti. Mai e poi mai mischiare considerazioni sull'implementazione nel tuo processo di esplorazione. Se questa descrizione non ti è chiara, consulta (a) Progettazione denotativa con morfismi di classe tipo , (b) Programmazione reattiva funzionale push-pull (ignorando i bit di implementazione), e (c) la pagina Denotational Semantics Haskell wikibooks . Attenzione, la semantica denotazionale ha due parti, dai suoi due fondatori Christopher Strachey e Dana Scott: la parte Strachey più facile e più utile e la parte più difficile e meno utile (per la progettazione di software) di Scott.

Se ti attieni a questi principi, mi aspetto che otterrai qualcosa di più o meno nello spirito di FRP.

Dove ho preso questi principi? Nella progettazione del software, pongo sempre la stessa domanda: "cosa significa?". La semantica denotazionale mi ha fornito una struttura precisa per questa domanda, che si adatta alla mia estetica (a differenza della semantica operazionale o assiomatica, che mi lasciano entrambi insoddisfatti). Quindi mi sono chiesto che cosa è il comportamento? Presto mi resi conto che la natura temporalmente discreta del calcolo imperativo è un adattamento a un particolare stile di macchina , piuttosto che una descrizione naturale del comportamento stesso. La più semplice descrizione precisa del comportamento che posso pensare è semplicemente "funzione del tempo (continuo)", quindi questo è il mio modello. Deliziosamente, questo modello gestisce la concorrenza continua e deterministica con facilità e grazia.

È stata una vera sfida implementare questo modello correttamente ed efficientemente, ma questa è un'altra storia.


Un modo semplice per raggiungere una prima intuizione su come è immaginare che il tuo programma sia un foglio di calcolo e tutte le tue variabili sono celle. Se una qualsiasi delle celle di un foglio di calcolo cambia, anche le celle che fanno riferimento a quella cella cambiano. È lo stesso con FRP. Ora immagina che alcune delle cellule cambino da sole (o meglio, sono prese dal mondo esterno): in una situazione della GUI, la posizione del mouse sarebbe un buon esempio.

Ciò necessariamente manca molto. La metafora si rompe piuttosto velocemente quando si utilizza un sistema FRP. Per uno, di solito ci sono tentativi di modellare anche eventi discreti (ad es. Il clic sul mouse). Sto solo mettendo questo qui per darti un'idea di com'è.


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:





frp