Haskell 反应式系统中行为的动态输入
当我想在反应式香蕉中处理动态输入时,我通常设想一个大致如下工作的系统:Haskell 反应式系统中行为的动态输入,haskell,reactive-banana,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
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
然后,我可以对从plainChanges
获取的事件
使用setupDynamicInputs
,但是:
但是,不建议采用这种方法[……]
所以我有点不愿意使用这种方法
当规范保持在行为中时,是否有一种“更干净”的方法使我的输入与规范保持同步
编辑
正如Heinrich Apfelmus在他的回答中指出的,我最初问题的解决方案不是使用行为
来更新输入规范
。虽然我能理解其背后的原因,但它并不能解决我所面临的问题,因此我将尝试在这里解释为什么我想使用行为
只要输入由单个输入指定,通过事件更新输入就很容易。例如,如果动态输入由一系列输入组成,那么这些输入的规范将只是一个非负整数,表示应该显示多少个输入
一旦通过多个输入获得输入规范,它就会变得更加复杂。例如,假设我们的InputSpecification
变成(Word,Word)
,并指定具有给定维度的输入网格。如果我通过两个不同的输入获得这些维度,我将不得不将两个事件单词
组合成一个事件(单词,单词)
,这对于事件
来说并不是一个简单的任务,因为它们没有像行为
那样的应用程序
实例。这就是为什么我通常喜欢在这种情况下使用行为
s的原因,但正如前面所讨论的,当您真正想要创建输入时,它们并不能让您更进一步。因此,如果行为
在这里不是正确的解决方案,而事件
往往很难(或者在最坏的情况下不可能)组合在一起,那么这个问题的正确解决方案是什么呢?嗯,这可能不起作用的一个原因是它可能不起作用。有一个石蕊测试可以确定用行为
对情况建模是否有意义:如果行为输入规范
是一个真正连续的函数,会发生什么?比如说,您有一个连续的频率范围(例如,对于无线电台),每个频率都将与必须设置的新输入相关联。如果要进行连续频率扫描,则必须创建并丢弃无限多的输入,这是不可能的。这表明事件输入规范
是正确类型的背后有更深层次的原因
更一般地说,行为
类型封装了两个重要的不变量:
它不取决于采样率
您无法检测它何时或多久“变化”。例如,如果您有一个事件
,其所有出现的事件都具有相同的值[(0秒,x),(2秒,x),…]
,则此不变量表示对其应用步进器
,将产生与纯x
无法区分的行为
出于实用的原因,可以使用更改函数绕过不变量2。如果你觉得它“在道德上保持不变”,你可以使用它。例如,只有当行为发生变化时,才可以使用它在屏幕上显示文本值;这比固定采样率下的轮询效率更高。由于上述任何一种行为的视觉最终结果都是相同的,因此在这种情况下,道德上保持不变
编辑:
看起来您需要更明确地控制何时更新。在这种情况下,您可以使用显式事件e::event()
,它跟踪应该何时更新输入。然后,您可以使用以下组合仅在触发此事件时更新输入
e2 <- plainChanges (imposeChanges b e)
execute $ updateDynamicInputs <$> e2
...
并为此实现Applicative
etc实例。这有点重,但可能正是您需要的。谢谢您指出这一点。但这并不能帮助我解决实际问题,所以我编辑了我的答案来澄清这到底是什么。哦,我明白了,我喜欢这个新的例子。然而,它也表明讨论行为的语义是重要的。例如,我想象网格的维度是从两个滑块小部件中获取的。如果用户抓住滑块,稍微移动它,但不足以更改其值,会发生什么情况。问:是否会重新创建网格中的所有输入,以便丢失用户之前输入的任何值?我认为您在第一个示例中打算使用plainChanges
,否则我看不出它是如何进行类型检查的。通常我的目标是重复使用以前创建的输入,以便它们的当前值不会丢失。但是,这仅适用于需要事件作为输入的accumE
或accumB
。我认为你在编辑中给出的例子很好地解决了这个问题。组合器executerelater::Event(Future(MomentIO A))->Event A
可以方便地处理更改
,而无需诉诸普通更改
。
e2 <- plainChanges (imposeChanges b e)
execute $ updateDynamicInputs <$> e2
...
data Dynamic a = D (Behavior a) (Event a)