来自反应性香蕉行为的动态输入

时间:2017-05-21 11:01:04

标签: haskell reactive-banana

当我想在反应香蕉中使用动态输入时,我通常会设想一个大致类似的系统:

data InputSpecification -- Some data structure that specifies the inputs
                        -- which should be active right now

data MyInterestingData -- Some data that is relevant to my business logic
                       -- and is gathered through the dynamic inputs.

emptyData :: MyInterestingData
emptyData = -- Some initial MyInterestingData

setupDynamicInputs :: Event (InputSpecification) -> MomentIO (Behavior MyInterestingData)
setupDynamicInputs specE = do
    newBehavior <- execute $ updateDynamicInputs <$> specE
    switchB emptyData newBehavior

updateDynamicInputs :: InputSpecification -> MomentIO (Behavior MyInterestingData)
updateDynamicInputs = -- Here the dynamic inputs are set up according to
                      -- the specification and set up to update the returned
                      -- Behavior

这非常好用,只要新的InputSpecification被触发,输入就会更新。

我经常遇到的一个问题是我的InputSpecification不是来自Event而是来自Behavior InputSpecification(可能是因为我需要Applicative个组合来构建它)。由于executeswitchB无法在Behaviors上使用,因此上述方法不起作用。

作为一个简单的解决方案,我可以使用reactive-banana documentation

中的此功能
plainChanges :: Behavior a -> MomentIO (Event a)
plainChanges b = do
    (e, handle) <- newEvent
    eb <- changes b
    reactimate' $ (fmap handle) <$> eb
    return e

然后我可以使用setupDynamicInputsEvent获得plainChanges,但是:

  

但是,不推荐这种方法[...]

所以我有点不愿意使用这种方法。

当规范保存在Behavior时,是否有“更清洁”的方法来保持输入与规范同步?

修改

正如Heinrich Apfelmus在答案中指出的那样,我原来问题的解决方案是不使用Behavior来更新InputSpecification。虽然我可以理解其背后的原因,但它并没有解决我遇到的问题,所以我将尝试解释为什么我想在这里使用Behavior

只要输入由单个输入指定,通过Event更新输入就很容易。例如,如果动态输入由一系列输入组成,那些输入的规范就只是一个非负整数,表示应该显示多少输入。

一旦通过多个输入获得输入规范,它就会变得更加复杂。例如,假设我们的InputSpecification变为(Word, Word)并指定具有给定维度的输入网格。如果我通过两个不同的输入获得这些维度,我必须将两个Event Word组合成一个Event (Word, Word),这对于Event来说并不是一个简单的任务,因为它们不是有像Applicative这样的Behavior个实例。这就是为什么我通常喜欢在这种情况下使用Behavior s,但正如之前所讨论的那样,当你真正想要创建输入时它们不会让你更进一步。因此,如果Behavior s不是正确的解决方案而且Event往往会变得乏味(或者在最坏的情况下不可能),那么这个问题的正确解决方案是什么?

1 个答案:

答案 0 :(得分:3)

嗯,这可能不起作用的一个原因是它可能不起作用。有一个试金石用于确定使用Behavior建模情况是否有意义:如果Behavior InputSpecification是真正的连续函数,会发生什么?比如,你有一个连续的频率范围(例如对于无线电台),每个频率都与一个必须设置的新输入相关联。如果要进行连续频率扫描,则必须创建并丢弃无限多个输入,这是不可能的。这表明Event InputSpecification是正确的类型背后有更深层次的原因。

更一般地说,Behavior类型封装了两个重要的不变量:

  1. 不依赖于采样率。
  2. 您无法检测到“更改”的时间或频率。例如,如果您的Event的出现次数都具有相同的值[(0 seconds, x), (2 seconds, x), ..],则此不变量表示将stepper应用于此将产生Behavior,即pure x
  3. 出于语用原因,可以使用changes函数规避不变量2。如果你觉得它“在道德上保留了不变量”,你可以使用它。例如,只有在行为发生变化时,您才可以使用它在屏幕上显示文本值;这比以固定采样率进行轮询更有效。由于上面提到的任何一种行为的视觉结果都是相同的,所以在这种情况下你会在道德上保留不变量。

    编辑:

    看起来您需要更明确地控制何时更新。在这种情况下,您可以使用显式事件e :: Event ()来跟踪应该更新输入的时间。然后,您可以使用以下组合仅在此事件触发时更新输入

    e2 <- plainChanges (imposeChanges b e)
    execute $ updateDynamicInputs <$> e2
    ...
    

    (应该有一个纯粹的替代方案,我会研究这个。)

    或者,您可以手动复制“带有更新通知的行为”机制,例如引入类型

    data Dynamic a = D (Behavior a) (Event a)
    

    并为此实现Applicative等实例。这有点重量级,但可能正是您所需要的。