如何在 reactive-banana 中创建单子行为

How to create monadic behaviour in reactive-banana

假设我捕捉到按键并相应地操作代码缓冲区:

let
    bCode = accumB emptyCode eModifications
eCodeChanges <- changes bCode

我想创建另一个行为bEval

bEval = accumB freshEnv (magic eCodeChanges)

它将代码的任何状态映射到它的求值(只有当某些事情真正发生变化时才会触发)。

但是,评估发生在 monad Interpreter 中(认为 hint 来自 hackage)。我真的可以定义这样的行为 bEval 吗?我想我可以将 Interpreter String 作为我行为中的状态,用 currentAccumState >>= eval nextEvent 累积,但是我 runInterpreter 在哪里才能真正强制评估?


编辑:重要的是动作不仅仅是IO (),而且应该修改一些状态。例如,考虑在拉链周围清除 buffer/reseting counter/moving。

我的想法是这样的:

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

我不明白为什么不能有这样的东西。但是,我也看不到如何摆脱行为中的 IO :).

为什么 reactive-banana 中没有出现 IO 实际上只有 MonadIO

简短的回答是 accumB 等组合器只能与 函数一起使用。它们无法使用来自 IO monad(或其他类似 IO 的 monad,如 Interpreter)的函数,因为无法以任何有意义的方式定义操作顺序。

要使用 IO 操作,Reactive.Banana.Frameworks 中的组合器是合适的。例如,您可能想要一个像

这样的函数
mapIO' :: (a -> IO b) -> Event a -> MomentIO (Event b)

请参阅 了解更多信息。

对于来自 hintInterpreter monad,我建议 运行 它在主线程中,为你的 FRP 逻辑分叉一个单独的线程,并使用 TVars 或 TChan 用于两者之间的通信。 (我喜欢称其为 forklift pattern)。这样,您可以从 IO.

访问 Interpreter monad