【问题标题】:Dynamic inputs from a Behavior in reactive-banana反应香蕉中行为的动态输入
【发布时间】:2017-05-21 11:01:04
【问题描述】:

当我想在响应式香蕉中使用动态输入时,我通常会设想一个大致如下工作的系统:

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

那么我可以在从plainChanges 获得的Event 上使用setupDynamicInputs,但是:

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

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

当规范保存在Behavior 中时,是否有一种“更简洁”的方法可以使我的输入与规范保持同步?

编辑

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

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

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

【问题讨论】:

    标签: haskell reactive-banana


    【解决方案1】:

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

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

    1. 它不依赖于采样率。
    2. 您无法检测到它“更改”的时间或频率。例如,如果您有一个Event,其所有出现的值都具有相同的值[(0 seconds, x), (2 seconds, x), ..],那么这个不变量表示将stepper 应用于此将产生一个Behavior,它与@无法区分 987654329@.

    出于务实的原因,可以使用changes 函数来规避不变量 2。如果您觉得它“在道德上保持不变性”,您可以使用它。例如,您可以使用它仅在 Behavior 更改时在屏幕上显示文本值;这比以固定采样率进行轮询更有效。由于上述任何一种行为的视觉最终结果都是相同的,因此在这种情况下,您在道德上保留了不变量。


    编辑:

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

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

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

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

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

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

    【讨论】:

    • 感谢您指出这一点。但它并不能帮助我解决我的实际问题,所以我编辑了我的答案以澄清它实际上是什么。
    • 哦,我明白了,我喜欢这个新的例子。但是,这也表明讨论Behavior 的语义很重要。例如,我想网格的尺寸取自两个滑块小部件。如果用户抓住滑块,稍微移动它,但不足以改变它的值,会发生什么。问题:是否会重新创建网格中的所有输入,以便用户之前输入的任何值丢失
    • 我认为您打算在第一个示例中使用 plainChanges,否则我看不出它是如何进行类型检查的。
    • 通常我的目标是重用以前创建的输入,因此它们的当前值不会丢失。但是,这只适用于accumEaccumB,它们需要Events 作为输入。我认为您在编辑中给出的示例很好地解决了这个问题。组合器 executeLater :: Event (Future (MomentIO a)) -&gt; Event a 可以方便地使用 changes 而无需诉诸 plainChanges
    猜你喜欢
    • 2011-09-25
    • 2017-11-22
    • 2015-01-28
    • 2013-06-20
    • 1970-01-01
    • 2013-11-09
    • 1970-01-01
    • 2013-06-16
    • 1970-01-01
    相关资源
    最近更新 更多