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