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