当我想在反应香蕉中使用动态输入时,我通常会设想一个大致类似的系统:
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
个组合来构建它)。由于execute
和switchB
无法在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
然后我可以使用setupDynamicInputs
从Event
获得plainChanges
,但是:
但是,不推荐这种方法[...]
所以我有点不愿意使用这种方法。
当规范保存在Behavior
时,是否有“更清洁”的方法来保持输入与规范同步?
正如Heinrich Apfelmus在答案中指出的那样,我原来问题的解决方案是不使用Behavior
来更新InputSpecification
。虽然我可以理解其背后的原因,但它并没有解决我遇到的问题,所以我将尝试解释为什么我想在这里使用Behavior
。
只要输入由单个输入指定,通过Event
更新输入就很容易。例如,如果动态输入由一系列输入组成,那些输入的规范就只是一个非负整数,表示应该显示多少输入。
一旦通过多个输入获得输入规范,它就会变得更加复杂。例如,假设我们的InputSpecification
变为(Word, Word)
并指定具有给定维度的输入网格。如果我通过两个不同的输入获得这些维度,我必须将两个Event Word
组合成一个Event (Word, Word)
,这对于Event
来说并不是一个简单的任务,因为它们不是有像Applicative
这样的Behavior
个实例。这就是为什么我通常喜欢在这种情况下使用Behavior
s,但正如之前所讨论的那样,当你真正想要创建输入时它们不会让你更进一步。因此,如果Behavior
s不是正确的解决方案而且Event
往往会变得乏味(或者在最坏的情况下不可能),那么这个问题的正确解决方案是什么?
答案 0 :(得分:3)
嗯,这可能不起作用的一个原因是它可能不起作用。有一个试金石用于确定使用Behavior
建模情况是否有意义:如果Behavior InputSpecification
是真正的连续函数,会发生什么?比如,你有一个连续的频率范围(例如对于无线电台),每个频率都与一个必须设置的新输入相关联。如果要进行连续频率扫描,则必须创建并丢弃无限多个输入,这是不可能的。这表明Event InputSpecification
是正确的类型背后有更深层次的原因。
更一般地说,Behavior
类型封装了两个重要的不变量:
Event
的出现次数都具有相同的值[(0 seconds, x), (2 seconds, x), ..]
,则此不变量表示将stepper
应用于此将产生Behavior
,即与pure x
。出于语用原因,可以使用changes
函数规避不变量2。如果你觉得它“在道德上保留了不变量”,你可以使用它。例如,只有在行为发生变化时,您才可以使用它在屏幕上显示文本值;这比以固定采样率进行轮询更有效。由于上面提到的任何一种行为的视觉结果都是相同的,所以在这种情况下你会在道德上保留不变量。
编辑:
看起来您需要更明确地控制何时更新。在这种情况下,您可以使用显式事件e :: Event ()
来跟踪应该更新输入的时间。然后,您可以使用以下组合仅在此事件触发时更新输入
e2 <- plainChanges (imposeChanges b e)
execute $ updateDynamicInputs <$> e2
...
(应该有一个纯粹的替代方案,我会研究这个。)
或者,您可以手动复制“带有更新通知的行为”机制,例如引入类型
data Dynamic a = D (Behavior a) (Event a)
并为此实现Applicative
等实例。这有点重量级,但可能正是您所需要的。