Cos’è la programmazione retriggers (funzionale)?

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

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

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

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 retriggers 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, nodes, 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 class. 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 class 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 stream (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 debuild 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 class tipo , (b) Programmazione retriggers 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.

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 retriggers funzionale. Questa è la combinazione di programmazione funzionale e programmazione retriggers. (L’articolo di Wikipedia a cui sei collegato riguarda quest’ultimo.)

L’idea alla base della programmazione retriggers è 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 = ; y = ; 

In qualsiasi momento, x e y avrebbero le coordinate del mouse. A differenza della programmazione non retriggers, è necessario eseguire questa assegnazione solo una volta e le variabili x e y rimarranno “aggiornate” automaticamente. Questo è il motivo per cui la programmazione retriggers e la programmazione funzionale funzionano così bene insieme: la programmazione retriggers 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 32×32 sarà disegnata attorno al puntatore del mouse e la seguirà ovunque si muova.

Ecco un bel documento sulla programmazione retriggers funzionale .

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’è.

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 retriggers 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.

OK, dalla conoscenza di fondo e dalla lettura della pagina di Wikipedia a cui hai indicato, sembra che la programmazione retriggers sia qualcosa come il stream di dati, ma con specifici “stimoli” esterni che triggersno un insieme di nodes da triggersre ed eseguire i loro calcoli.

Ciò è particolarmente adatto alla progettazione dell’interfaccia utente, ad esempio, in cui il touch 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 nodes 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 retriggers 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 nodes, ognuno dei quali esegue un semplice calcolo. Questi nodes sono collegati tra loro da collegamenti di grafici che forniscono gli output di alcuni nodes agli input di altri.

Quando un nodo triggers o esegue il suo calcolo, i nodes connessi alle sue uscite hanno i loro ingressi corrispondenti “innescati” o “contrassegnati”. Ogni nodo con tutti gli input triggersti ​​/ contrassegnati / disponibili viene triggersto automaticamente. Il grafico potrebbe essere implicito o esplicito a seconda esattamente di come viene implementata la programmazione retriggers.

I nodes 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 nodes nel grafico attraverso una o più unità di esecuzione. Il calcolo del stream 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 retriggers importa questa idea di “cascata di esecuzione” e sembra pensare al programma in un modo simile al stream di dati, ma con la condizione che alcuni dei nodes siano agganciati al “mondo esterno” e le cascate di esecuzione si attivino quando questi sensoriali -come i nodes 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 retriggers” sarebbe programmata con una visione molto diversa del stream 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.

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

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

Qual è l’essenza della programmazione retriggers 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 retriggers 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 retriggers 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 dynamic 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: inserisci la descrizione dell'immagine qui

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 questo 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.

Disclaimer: la mia risposta è nel contesto di rx.js – una libreria di “programmazione retriggers” 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 stream di eventi (implementato con un osservabile *) e applicare invece gli HoF. In questo modo è ansible 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 object è spinto all’osservatore ogni volta che viene aggiunto alla sequenza, e quindi l’enumerazione è guidata dal produttore.

La carta Reattività funzionale semplicemente efficiente di Conal Elliott ( PDF diretto , 233 KB) è un’introduzione abbastanza buona. Anche la libreria corrispondente funziona.

La carta è ora sostituita da un’altra carta, programmazione retriggers funzionale Push-pull ( PDF diretto , 286 KB).

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?

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 2014), The Haskell School of Music .

Secondo le risposte precedenti, sembra che matematicamente pensiamo semplicemente a un ordine superiore. Invece di pensare a un valore x che ha tipo X , pensiamo a una funzione x : TX , dove T è il tipo di tempo, sia esso i numeri naturali, i numeri interi o il continuum. Ora quando scriviamo y : = x + 1 nel linguaggio di programmazione, intendiamo effettivamente l’equazione y ( t ) = x ( t ) + 1.

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 stream distribuito, può facilmente cadere preda di un problema generale di coerenza dello stato attraverso la rete di nodes, 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 :).

Ho trovato questo bel video sul subreddit Clojure su FRP. È abbastanza facile da capire anche se non conosci Clojure.

Ecco il video: http://www.youtube.com/watch?v=nket0K1RXU4

Ecco la fonte a cui fa riferimento il video nel 2 ° semestre: https://github.com/Cicayda/yolk-examples/blob/master/src/yolk_examples/client/autocomplete.cljs

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

Alcune citazioni dall’articolo:

La programmazione retriggers è 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:

Fare clic sul diagramma del flusso di eventi

Riguarda le trasformazioni matematiche dei dati nel tempo (o ignorando il tempo).

Nel codice questo significa purezza funzionale e programmazione dichiarativa.

I bug di stato sono un enorme problema nel paradigma imperativo standard. Vari bit di codice possono modificare alcuni stati condivisi in “tempi” diversi nell’esecuzione dei programmi. Questo è difficile da gestire.

In FRP descrivi (come nella programmazione dichiarativa) come i dati si trasformano da uno stato all’altro e ciò che lo innesca. Questo ti permette di ignorare il tempo perché la tua funzione sta semplicemente reagendo ai suoi input e usando i loro valori attuali per crearne uno nuovo. Ciò significa che lo stato è contenuto nel grafico (o albero) dei nodes di trasformazione ed è funzionalmente puro.

Questo riduce enormemente la complessità e il tempo di debug.

Pensa alla differenza tra A = B + C in matematica e A = B + C in un programma. In matematica stai descrivendo una relazione che non cambierà mai. In un programma, dice che “In questo momento” A è B + C. Ma il prossimo comando potrebbe essere B ++, nel qual caso A non è uguale a B + C. In matematica o in programmazione dichiarativa A sarà sempre uguale a B + C, non importa in che momento lo chiederai.

Quindi rimuovendo le complessità dello stato condiviso e cambiando i valori nel tempo. Il tuo programma è molto più facile da ragionare.

Un EventStream è un EventStream + una funzione di trasformazione.

Un comportamento è un EventStream + un valore in memoria.

Quando l’evento viene triggersto, il valore viene aggiornato eseguendo la funzione di trasformazione. Il valore che questo produce è memorizzato nella memoria dei comportamenti.

I comportamenti possono essere composti per produrre nuovi comportamenti che sono una trasformazione in N altri comportamenti. Questo valore composto ricalcolerà come il fuoco degli eventi di input (comportamenti).

“Poiché gli osservatori sono apolidi, spesso abbiamo bisogno di molti di essi per simulare una macchina a stati come nell’esempio di trascinamento, dobbiamo salvare lo stato in cui è accessibile a tutti gli osservatori coinvolti, come nel percorso variabile sopra.”

Citazione di – Decriminazione del modello di osservatore http://infoscience.epfl.ch/record/148043/files/DeprecatingObserversTR2010.pdf

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

Un [modulo / Componente / object] è 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.

Scopri Rx, Reactive Extensions per .NET. Sottolineano che con IEnumerable stai praticamente ‘tirando’ da un stream. 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, è ansible 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 stream in entrata di movimenti del mouse e disegna ogni volta che ti trovi nella casella 100.100 …

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

Dai un’occhiata al post di Andre Staltz sulla programmazione retriggers per iniziare.