I am learning reactive-banana. In order to understand the library I have decide to implement a dummy application that would increase a counter whenever someone pushes a button.
The UI library I am using is Gtk but that is not relevant for the explanation.
Here is the very simple implementation that I have come up with:
import Graphics.UI.Gtk
import Reactive.Banana
import Reactive.Banana.Frameworks
makeNetworkDescription addEvent = do
eClick <- fromAddHandler addEvent
reactimate $ (putStrLn . show) <$> (accumE 0 ((+1) <$ eClick))
main :: IO ()
main = do
(addHandler, fireEvent) <- newAddHandler
initGUI
network <- compile $ makeNetworkDescription addHandler
actuate network
window <- windowNew
button <- buttonNew
set window [ containerBorderWidth := 10, containerChild := button ]
set button [ buttonLabel := "Add One" ]
onClicked button $ fireEvent ()
onDestroy window mainQuit
widgetShowAll window
mainGUI
This just dumps the result in the shell. I came up to this solution reading the article by Heinrich Apfelmus. Notice that in my example I have not used a single Behavior
.
In the article there is an example of a network:
makeNetworkDescription addKeyEvent = do
eKey <- fromAddHandler addKeyEvent
let
eOctaveChange = filterMapJust getOctaveChange eKey
bOctave = accumB 3 (changeOctave <$> eOctaveChange)
ePitch = filterMapJust (`lookup` charPitches) eKey
bPitch = stepper PC ePitch
bNote = Note <$> bOctave <*> bPitch
eNoteChanged <- changes bNote
reactimate' $ fmap (\n -> putStrLn ("Now playing " ++ show n))
<$> eNoteChanged
The example show a stepper
that transforms an Event
into a Behavior
and brings back an Event
using changes
. In the above example we could have used only Event
and I guess that it would have made no difference (unless I am not understanding something).
So could someone can shed some light on when to use Behavior
and why? Should we convert all Event
s as soon as possible?
In my little experiment I don't see where Behavior
can be used.
Thanks
Anytime the FRP network "does something" in Reactive Banana it's because it's reacting to some input event. And the only way it does anything observable outside the system is by wiring up an external system to react to events it generates (using reactimate
).
So if all you're doing is immediately reacting to an input event by producing an output event, then no, you won't find much reason to use Behaviour
.
Behaviour
is very useful for producing program behaviour that depends on multiple event streams, where you have to remember that events happen at different times.
An Event
has occurrences; specific instants of time where it has a value. A Behaviour
has a value at all points in time, with no instants of time that are special (except with changes
, which is convenient but kind of model-breaking).
A simple example familiar from many GUIs would be if I want to react to mouse clicks and have shift-click do something different from a click when the shift key is not held. With a Behaviour
holding a value indicating whether the shift key is held down, this is trivial. If I just had Event
s for shift key press/release and for mouse clicks it's much harder.
In addition to being harder, it's much more low level. Why should I have to do complicated fiddling just to implement a simple concept like shift-click? The choice between Behaviour
and Event
is a helpful abstraction for implementing your program's concepts in terms that map more closely to the way you think about them outside the programming world.
An example here would be a movable object in a game world. I could have an Event Position
representing all the times it moves. Or I could just have a Behaviour Position
representing where it is at all times. Usually I'll be thinking of the object as having a position at all times, so Behaviour
is a better conceptual fit.
Another place Behaviour
s are useful is for representing external observations your program can make, where you can only check the "current" value (because the external system won't notify you when changes occur).
For an example, let's say your program has to keep tabs on a temperature sensor and avoid starting a job when the temperature is too high. With an Event Temperature
I'll have decide up front how often to poll the temperature sensor (or in response to what). And then have all the same issues as in my other examples about having to manually do something to make the last temperature reading available to the event that decides whether or not to start a job. Or I could use fromPoll
to make a Behaviour Temperature
. Now I've got a value that represents the time-varying value of the temperature, and I've completely abstracted away from polling the sensor; Reactive Banana itself takes care of polling the sensor as often as it might be needed without me needing to impending any logic for that at all!