我正在用Haskell在响应式(Reactive)香蕉中编写音乐播放器。我遇到的一个问题是使用fromPoll获取最新值。我想让用户在播放时有选择地选择轨道的一部分。我的代码如下所示:

makePlayNetworkDescr :: Player a => AddHandler Command -> a -> NetworkDescription t ()
makePlayNetworkDescr addCmdEvent player = do
    bPosition <- fromPoll (getPosition player)
    eCmds <- fromAddHandler addCmdEvent

    let eSetStart = filterE (isJust) $ bPosition <@ filterE (==SetStart) eCmds
        eSetEnd = filterE (isJust) $ bPosition <@ filterE (==SetEnd) eCmds
        eClearRange = filterE (==ClearRange) eCmds

        bStart = accumB Nothing ((const <$> eSetStart) `union` (const Nothing <$ eClearRange))
        bEnd = accumB Nothing ((const <$> eSetEnd) `union` (const Nothing <$ eClearRange))

上面的getPosition是局部函数,在实际开始播放之前不返回Nothing。问题在于,一旦首次触发addCmdEvent,bPosition仍将保留Nothing值。 eSetStart/End基于此计算它们的值。只有这样,bPosition才会更新,这是下次addCmdEvent触发时将使用的值。依此类推,该值将始终是“一个一”。

有一个相关的SO question,但是在那种情况下,存在一个“触发”事件,该事件可用于计算行为的新值。 fromPoll可以实现这种功能吗?

最佳答案

从react-banana-0.5和0.6开始,只要外部事件触发事件网络,fromPoll函数就会更新行为。您可以使用来将这些更新作为事件进行访问

eUpdate <- changes bSomeBehavior

但是,请注意,行为表示不随时间变化的连续值,这些值不支持“更新事件”的一般概念。 changes函数将尝试返回有用的近似值,但是没有正式的保证。

或者,您可以更改外部事件以将玩家位置作为addCmdEvent的一部分。在您的情况下,这意味着要向SetStartSetEnd构造函数添加更多数据。然后,您可以使用
eSetStart = filterJust $ matchSetStart <$> eCmds
    where
    matchSetStart (SetStart pos) = Just pos
    matchSetStart _              = Nothing

这两种解决方案都要求您将最近的值作为事件而不是行为来观察。原因是用stepper创建的行为在更新时总是返回旧值(它们“落后一”),因为这对于递归定义非常有用。

无论如何,的潜在问题是,在addCmdEvent出现之前,玩家位置已在外部进行了很长时间的更新,但是问题在于,这不是事件网络所看到的。相反,网络认为fromPoll返回的行为与addCmdEvent同时更新。实际上,除非您有权访问负责更新玩家位置的外部事件源,否则这是它唯一能想到的。 (如果您有权访问,则可以使用fromChanges函数。)

我意识到fromPoll的这种行为对于您的常见用例而言并不令人满意。但是,我不确定是否应该在我的库中对其进行修复:在fromPoll返回最后一个值与changes函数试图尽力而为之间存在一个权衡。如果返回了最新值,则changes的行为就好像跳过了一个更新(当从外部更新该值时)并触发了一个多余的更新(当网络更新该值以匹配外部一个值时)。如果您对此有任何意见,请告诉我。

请注意,将行为与适用的运算符<*>结合将可以很好地结合最新值。

10-07 15:14