Suppose I catch key presses and manipulate a code buffer accordingly:
let
bCode = accumB emptyCode eModifications
eCodeChanges <- changes bCode
I would like to create another behaviour bEval
bEval = accumB freshEnv (magic eCodeChanges)
which maps any state of code to its evaluation (triggered only when something really changes).
However, evaluation happens in a monad Interpreter
(think hint
from hackage). Can I actually define such a behaviour bEval
? I guess I could just drag Interpreter String
as the state in my behaviour, accumulating with currentAccumState >>= eval nextEvent
, but where would I runInterpreter
to actually force the evaluation?
Edit: Important thing is that the actions are not merely IO ()
but are supposed to modify some state. Consider for example clearing a buffer/reseting counter/moving around a zipper.
My idea was along the lines:
f :: a -> Maybe (b -> IO b)
mapJustIO :: (a -> Maybe (b -> IO b)) -> Event t a -> Event t (b -> IO b)
mapJustIO f e = filterJust $ f <$> e
accumIO :: a -> Event t (a -> IO a) -> Behaviour t (IO a)
accumIO z e = fold (>>=) (return z) e
I don't see why there could not be something like this. However, I don't see how to get rid of that IO
in the behaviour either :).
Why aren't IO
occurrences in reactive-banana
actually only MonadIO
?
The short answer is that combinators like
accumB
and so on can only work with pure functions. They can't work with functions from the IO monad (or another IO-like monad likeInterpreter
), because there is no way to define the order of actions in any meaningful way.To work with IO actions, the combinators from
Reactive.Banana.Frameworks
are appropriate. For instance, you probably want a function likeSee a previous answer for more on this.
In the case of the
Interpreter
monad fromhint
, I recommend running it in the main thread, forking a separate thread for your FRP logic, and usingTVar
s orTChan
for communication between the two. (I like to calls this the forklift pattern). This way, you can access theInterpreter
monad fromIO
.