我想在行为上映射IO动作但是(threepenny-gui)中没有这样的功能 有没有办法用暴露的API构建它。它在语义上是否合理?
我已经在threepenny中实现了这个,在我的用例中运行正常。
-- Just bind the IO action to the Latch
unsafeMapIOB :: (a → IO b) → Behavior a → Behavior b
unsafeMapIOB f (B l e) = B (Prim.bindL (Prim.Latch ∘ f) l) e
-- Wrap the IO bind
bindL :: (a -> Latch b) -> Latch a -> Latch b
bindL f l = Latch { readL = (readL ∘ f) =<< readL l}
-- Map the IO action over both Behavior and Event
unsafeMapIOT :: (a -> IO b) -> Tidings a -> Tidings b
unsafeMapIOT f x = tidings (unsafeMapIOB f $ facts x) (unsafeMapIO f $ rumors x)
我从来没有单独使用unsafeMapIOB只是unsafeMapIOT。我相信正在发生的是 unsafeMapIOB只执行一次,然后用事件触发unsafeMapIO。
答案 0 :(得分:2)
对于理想的FRP语义,行为是一种随时间变化的函数:Time → a
- 它可以随着时间的推移连续 。这意味着在每次更改时调用IO操作在语义上都不合理,因为语义中不存在离散更改的概念。
这在实践中也很有意义:取决于行为更改的值何时通常过于依赖于实现。例如,考虑鼠标位置:值的变化频率取决于它的轮询频率,这是非常依赖于系统的。即使实际行为不连续,离散变化仍然是我们不想泄漏到您的程序中的细节。 (在一个理想的世界中,也许这种行为也是连续的 - 也许系统会进行某种插值或平滑以补偿硬件中的基础轮询。)
为避免依赖于这些任意实现细节,您必须明确说明何时采样行为以触发IO操作。您可以通过获取另一个事件流的时间组件,同时创建包含事件的新事件流,但基于行为的当前值创建值来实现此目的。在Reactive Banana中,您可以使用<@
运算符执行此操作。专门针对我们的类型,我们得到:
(<@) :: Behavior a -> Event b -> Event a
本质上,我们在事件流上映射行为的值,为我们提供一个新的事件流。然后我们可以使用标准函数来触发每个事件的IO动作。
最后一个问题是,您从哪里获得事件流,这取决于您的具体计划。如果由于某些特定用户操作而导致行为更改,则可以获取该操作的流。您还可以创建一个计时器事件并以给定的时间间隔进行轮询。或者您甚至可以使用union
同时执行这两项操作! FRP如何组合的一个很好的例证。