This example is not tied to any concrete GUI toolkit, like reactive-banana-wx does, for instance. Instead it shows how to inject arbitary IO actions into FRP machinery.
The Control.Event.Handler module provides an addHandler function which creates a pair of AddHandler a and a -> IO () values. The former is used by reactive-banana itself to obtain an Event a value, while the latter is a plain function that is used to trigger the corresponding event.
import Data.Char (toUpper)
import Control.Event.Handler
import Reactive.Banana
main = do
(inputHandler, inputFire) <- newAddHandler
In our case the a parameter of the handler is of type String, but the code that lets compiler infer that will be written later.
Now we define the EventNetwork that describes our FRP-driven system. This is done using compile function:
main = do
(inputHandler, inputFire) <- newAddHandler
compile $ do
inputEvent <- fromAddHandler inputHandler
The fromAddHandler function transforms AddHandler a value into a Event a, which is covered in the next example.
Finally, we launch our "event loop", that would fire events on user input:
main = do
(inputHandler, inputFire) <- newAddHandler
compile $ do
...
forever $ do
input <- getLine
inputFire input
In reactive-banana the Event type represents a stream of some events in time. An Event is similar to an analog impulse signal in the sense that it is not continuous in time. As a result, Event is an instance of the Functor typeclass only. You can't combine two Events together because they may fire at different times. You can do something with an Event's [current] value and react to it with some IO action.
Transformations on Events value are done using 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
Reacting to an Event is done the same way. First you fmap it with an action of type a -> IO () and then pass it to reactimate function:
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
Now whenever inputFire "something" is called, "SOMETHING" would be printed.
To represent continious signals, reactive-banana features Behavior a type. Unlike Event, a Behavior is an Applicative, which lets you combine n Behaviors using an n-ary pure function (using <$> and <*>).
To obtain a Behavior a from the Event a there is accumE function:
main = do
(inputHandler, inputFire) <- newAddHandler
compile $ do
...
inputBehavior <- accumE "" $ fmap (\oldValue newValue -> newValue) inputEvent
accumE takes Behavior's initial value and an Event, containing a function that would set it to the new value.
As with Events, you can use fmap to work with current Behaviors value, but you can also combine them with (<*>).
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'
To react on Behavior changes there is a changes function:
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
The only thing that should be noted is that changes return Event (Future a) instead of Event a. Because of this, reactimate' should be used instead of reactimate. The rationale behind this can be obtained from the documentation.
EventNetworks returned by compile must be actuated before reactimated events have an effect.
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!"