How to create monadic behaviour in reactive-banana

2019-08-14 18:57发布

问题:

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?

回答1:

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 like Interpreter), 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 like

mapIO' :: (a -> IO b) -> Event a -> MomentIO (Event b)

See a previous answer for more on this.

In the case of the Interpreter monad from hint, I recommend running it in the main thread, forking a separate thread for your FRP logic, and using TVars or TChan for communication between the two. (I like to calls this the forklift pattern). This way, you can access the Interpreter monad from IO.