Haskell Language
Banan reaktywny
Szukaj…
Wstrzykiwanie zdarzeń zewnętrznych do biblioteki
Ten przykład nie jest powiązany z żadnym konkretnym zestawem GUI, jak na przykład reactive-banana-wx. Zamiast tego pokazuje, jak wstrzyknąć arbitralne działania IO
do maszyn FRP.
Moduł Control.Event.Handler
udostępnia funkcję addHandler
która tworzy parę wartości AddHandler a
oraz a -> IO ()
. Pierwszy z nich jest używany przez samego banana reaktywnego do uzyskania wartości Event a
, a drugi jest zwykłą funkcją, która jest używana do wyzwolenia odpowiedniego zdarzenia.
import Data.Char (toUpper) import Control.Event.Handler import Reactive.Banana main = do (inputHandler, inputFire) <- newAddHandler
W naszym przypadku parametrem obsługi jest typu a
String
, ale kod, który pozwala wnioskować, że kompilator będzie napisany później.
Teraz definiujemy EventNetwork
który opisuje nasz system oparty na FRP. Odbywa się to za pomocą funkcji compile
:
main = do (inputHandler, inputFire) <- newAddHandler compile $ do inputEvent <- fromAddHandler inputHandler
Funkcja fromAddHandler
przekształca wartość AddHandler a
w Event a
, które AddHandler a
w następnym przykładzie.
Wreszcie uruchamiamy naszą „pętlę zdarzeń”, która uruchamiałaby zdarzenia na podstawie danych wprowadzonych przez użytkownika:
main = do (inputHandler, inputFire) <- newAddHandler compile $ do ... forever $ do input <- getLine inputFire input
Typ wydarzenia
W reactive-banana typ Event
reprezentuje strumień niektórych zdarzeń w czasie. Event
jest podobne do analogowego sygnału impulsowego w tym sensie, że nie jest ciągłe w czasie. W wyniku tego Event
jest wystąpienie z Functor
tylko typeclass. Nie możesz łączyć dwóch Event
ponieważ mogą strzelać w różnych momentach. Możesz zrobić coś z wartością [bieżącą] Event
i zareagować na to za pomocą akcji IO
.
Transformacje wartości Event
są wykonywane za pomocą 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
Reakcja na Event
odbywa się w ten sam sposób. Najpierw fmap
go za pomocą akcji typu a -> IO ()
a następnie przekaż go do reactimate
funkcji:
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
Teraz za każdym razem, gdy inputFire "something"
, drukowane będzie "SOMETHING"
.
Rodzaj zachowania
Aby reprezentować ciągłe sygnały, reaktywne banany cechują się Behavior a
typu. W przeciwieństwie do Event
, Behavior
jest Applicative
, która pozwala łączyć n Behavior
za pomocą n-ary czystej funkcji (używając <$>
i <*>
).
Aby uzyskać Behavior a
ze Event a
dostępna jest funkcja accumE
:
main = do (inputHandler, inputFire) <- newAddHandler compile $ do ... inputBehavior <- accumE "" $ fmap (\oldValue newValue -> newValue) inputEvent
accumE
przyjmuje wartość początkową Behavior
i Event
, zawierające funkcję, która accumE
jej nową wartość.
Podobnie jak w przypadku Event
, możesz użyć fmap
do pracy z bieżącą wartością Behavior
, ale możesz także połączyć je z (<*>)
.
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'
Aby zareagować na zmiany w Behavior
dostępna jest funkcja 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
Jedyną rzeczą, na którą należy zwrócić uwagę, jest to, że changes
zwracają Event (Future a)
zamiast Event a
. Z tego reactimate'
należy stosować zamiast reactimate
. Uzasadnienie tego można znaleźć w dokumentacji.
Uruchamianie EventNetworks
EventNetwork
zwrócony przez compile
musi zostać aktywowany, zanim ponownie zareagowane zdarzenia zaczną działać.
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!"