2

我想在 Behavior 上映射一个 IO 操作,但 (threepenny-gui) 中没有这样的函数 是否有某种方法可以使用公开的 API 构建它。它在语义上合理吗?

我已经在三便士内实现了这个,在我的用例中工作正常。

-- 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 被事件触发。

4

1 回答 1

2

对于理想的 FRP 语义,行为是随时间变化的函数:Time → a——它可以随时间不断变化。这意味着在每次更改时调用 IO 操作在语义上并不合理,因为语义中没有离散更改的概念。

这在实践中也很有意义:取决于行为的值何时更改通常过于依赖于实现。例如,考虑鼠标位置:值更改的频率取决于轮询的频率,这非常依赖于系统。即使实际行为不是连续的,离散的变化仍然是我们不希望泄漏到您的程序中的细节。(在理想的世界中,这种行为也许也是连续的——也许系统会进行某种插值或平滑来补偿硬件中的底层轮询。)

为了避免依赖这些任意的实现细节,您必须明确何时对您的行为进行采样以触发您的 IO 操作。您可以通过获取另一个事件流的时间分量来执行此操作,同时创建一个包含事件的新事件流,但值基于行为的当前值。在 Reactive Banana 中,您可以使用操作员执行此<@操作。专门针对我们的类型,我们得到:

(<@) :: Behavior a -> Event b -> Event a

本质上,我们将行为的值映射到事件流上,从而为我们提供了一个新的事件流。然后我们可以使用标准函数来触发每个事件的 IO 操作。

那么,最后一个问题是您从哪里获取事件流,这取决于您的特定程序。如果由于某些特定的用户操作而导致行为发生变化,您可以获得该操作的流。您也可以只创建一个计时器事件并以给定的时间间隔进行轮询。或者您甚至可以同时使用union! 一个很好的说明 FRP 是如何非常可组合的。

于 2014-08-14T22:42:28.627 回答