Haskell Language
Reattivo-banane
Ricerca…
Iniezione di eventi esterni nella libreria
Questo esempio non è legato ad alcun concreto toolkit GUI, come ad esempio reactive-banana-wx. Invece mostra come iniettare azioni IO arbitrarie nei macchinari FRP.
Il modulo Control.Event.Handler fornisce una funzione addHandler che crea una coppia di valori AddHandler a addHandler a -> IO () . Il primo è usato dalla banana reattiva per ottenere un valore di Event a , mentre il secondo è una funzione semplice che viene utilizzata per attivare l'evento corrispondente.
import Data.Char (toUpper)
import Control.Event.Handler
import Reactive.Banana
main = do
(inputHandler, inputFire) <- newAddHandler
Nel nostro caso l' a parametro del gestore è di tipo String , ma il codice che permette compilatore dedurre che sarà scritto in seguito.
Ora definiamo EventNetwork che descrive il nostro sistema basato su FRP. Questo viene fatto usando la funzione di compile :
main = do
(inputHandler, inputFire) <- newAddHandler
compile $ do
inputEvent <- fromAddHandler inputHandler
La funzione fromAddHandler trasforma AddHandler a valore in un Event a , che è coperto nel prossimo esempio.
Infine, lanciamo il nostro "ciclo degli eventi", che genererebbe eventi in entrata dell'utente:
main = do
(inputHandler, inputFire) <- newAddHandler
compile $ do
...
forever $ do
input <- getLine
inputFire input
Tipo di evento
In banana reattiva il tipo di Event rappresenta un flusso di alcuni eventi nel tempo. Un Event è simile a un segnale di impulso analogico nel senso che non è continuo nel tempo. Di conseguenza, Event è solo un'istanza della Functor di Functor Functor. Non è possibile combinare due Event insieme perché potrebbero sparare in momenti diversi. Puoi fare qualcosa con il valore [corrente] di un Event e reagire ad esso con qualche azione IO .
Le trasformazioni sul valore Event vengono eseguite usando fmap :
main = do
(inputHandler, inputFire) <- newAddHandler
compile $ do
inputEvent <- fromAddHandler inputHandler
-- turn all characters in the signal to upper case
let inputEvent' = fmap (map toUpper) inputEvent
La reazione a un Event viene eseguita allo stesso modo. Per prima cosa si fmap con un'azione di tipo a -> IO () e quindi passarlo alla funzione reactimate :
main = do
(inputHandler, inputFire) <- newAddHandler
compile $ do
inputEvent <- fromAddHandler inputHandler
-- turn all characters in the signal to upper case
let inputEvent' = fmap (map toUpper) inputEvent
let inputEventReaction = fmap putStrLn inputEvent' -- this has type `Event (IO ())
reactimate inputEventReaction
Ora ogni volta che viene inputFire "something" viene stampato "SOMETHING" .
Tipo di comportamento
Per rappresentare segnali continui, le caratteristiche di banana reattiva Behavior a tipo. Diversamente Event , un Behavior è un Applicative , che ti consente di combinare n Behavior usando una funzione pura n-ario (usando <$> e <*> ).
Per ottenere un Behavior a Event a c'è la funzione accumE :
main = do
(inputHandler, inputFire) <- newAddHandler
compile $ do
...
inputBehavior <- accumE "" $ fmap (\oldValue newValue -> newValue) inputEvent
accumE prende il valore iniziale di Behavior e un Event , contenente una funzione che lo imposterà sul nuovo valore.
Come per gli Event s, puoi usare fmap per lavorare con il valore del Behavior corrente, ma puoi anche combinarli con (<*>) .
main = do
(inputHandler, inputFire) <- newAddHandler
compile $ do
...
inputBehavior <- accumE "" $ fmap (\oldValue newValue -> newValue) inputEvent
inputBehavior' <- accumE "" $ fmap (\oldValue newValue -> newValue) inputEvent
let constantTrueBehavior = (==) <$> inputBehavior <*> inputBehavior'
Per reagire alle modifiche del Behavior , esiste una funzione di changes :
main = do
(inputHandler, inputFire) <- newAddHandler
compile $ do
...
inputBehavior <- accumE "" $ fmap (\oldValue newValue -> newValue) inputEvent
inputBehavior' <- accumE "" $ fmap (\oldValue newValue -> newValue) inputEvent
let constantTrueBehavior = (==) <$> inputBehavior <*> inputBehavior'
inputChanged <- changes inputBehavior
L'unica cosa da notare è che le changes restituiscono Event (Future a) invece di Event a . Per questo reactimate' dovrebbe essere usato un tempo di reactimate invece di un reactimate di reactimate . La logica alla base di questo può essere ottenuta dalla documentazione.
Attuando EventNetworks
EventNetwork restituito da compile deve essere attivato prima che gli eventi reattivi abbiano un effetto.
main = do
(inputHandler, inputFire) <- newAddHandler
eventNetwork <- compile $ do
inputEvent <- fromAddHandler inputHandler
let inputEventReaction = fmap putStrLn inputEvent
reactimate inputEventReaction
inputFire "This will NOT be printed to the console!"
actuate eventNetwork
inputFire "This WILL be printed to the console!"