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!"