【问题标题】:Can reactive-banana handle cycles in the network?反应香蕉可以处理网络中的循环吗?
【发布时间】:2011-12-12 14:49:52
【问题描述】:

我们有这样的代码:

 guiState :: Discrete GuiState
 guiState = stepperD (GuiState []) $
   union (mkGuiState <$> changes model) evtAutoLayout

 evtAutoLayout :: Event GuiState
 evtAutoLayout = fmap fromJust . filterE isJust . fmap autoLayout $ changes guiState

您可以看到 evtAutoLayout 馈入 guiState 馈入 evtAutoLayout——所以那里有一个循环。这是故意的。汽车 layout 调整 gui 状态,直到达到平衡,然后 它返回 Nothing ,所以它应该停止循环。新的模型变化 当然可以重新开始。

但是,当我们将这些放在一起时,我们会在 编译函数调用。即使autoLayout = Nothing,编译时还是会导致栈溢出。

如果我删除了 guiState 中的联合调用并删除了 evtAutoLayout 图片...

 guiState :: Discrete GuiState
 guiState = stepperD (GuiState []) $ mkGuiState <$> changes model

效果很好。

有什么建议吗?

【问题讨论】:

    标签: haskell reactive-programming reactive-banana


    【解决方案1】:

    问题

    reactive-banana 库是否支持递归定义的事件?

    不仅有一个,而且有三个答案。简短的回答是:1. 一般不会,2. 有时会,3. 有解决方法是。

    这里是长答案。

    1. reactive-banana 的语义不支持直接根据自身定义一个Event

      这是 Conal Elliott 在其原始 FRP 语义中做出的决定,我决定坚持下去。它的主要好处是语义保持非常简单,你总是可以考虑

      type Behavior a = Time -> a
      type Event    a = [(Time,a)]
      

      我提供了一个模块Reactive.Banana.Model,它几乎完全实现了这个模型,您可以查阅它的源代码以了解有关响应式香蕉语义的任何问题。特别是,您可以使用它来推理您的示例:使用笔和纸进行计算或在 GHCi 中尝试(使用一些模拟数据)将告诉您值 evtAutoLayout 等于 _|_,即未定义。

      后者可能令人惊讶,但正如您所写的那样,该示例确实是未定义的:GUI 状态仅在 evtAutoLayout 事件发生时才会发生变化,但只有在您知道 GUI 状态是否发生变化时才会发生,这在转弯等等。您总是需要通过插入一个小延迟来打破扼杀反馈循环。不幸的是,reactive-banana 目前不提供插入小延迟的方法,主要是因为我不知道如何以允许递归的方式描述 [(Time,a)] 模型中的小延迟。 (但请参阅答案 3。)

    2. 可以并鼓励根据再次引用事件的Behavior 定义Event。换句话说,只要你经过一个 Behavior,就允许递归。

      一个简单的例子是

      import Reactive.Banana.Model
      
      filterRising :: (FRP f, Ord a) => Event f a -> Event f a
      filterRising eInput = eOutput
          where
          eOutput  = filterApply (greater <$> behavior) eInput
          behavior = stepper Nothing (Just <$> eOutput)
      
          greater Nothing  _ = True
          greater (Just x) y = x < y
      
      example :: [(Time,Int)]
      example = interpretTime filterRising $ zip [1..] [2,1,5,4,8,9,7]
      -- example = [(1.0, 2),(3.0, 5),(5.0, 8),(6.0, 9)]
      

      给定一个事件流,函数filterRising 只返回那些大于之前返回的事件。 documentation for the stepper function 中暗示了这一点。

      但是,这可能不是您想要的那种递归。

    3. 不过,可以在响应式香蕉中插入小的延迟,它只是不是核心库的一部分,因此没有任何保证语义。此外,您确实需要事件循环的一些支持才能做到这一点。

      例如,您可以使用 wxTimer 来安排在您处理完当前事件后立即发生的事件。 Wave.hs 示例演示了 wxTimer 与 reactive-banana 的递归使用。我不太清楚将计时器间隔设置为0 时会发生什么,但它可能执行得太早。您可能需要进行一些试验才能找到一个好的解决方案。

    希望有所帮助;随时要求澄清,示例等。

    披露:我是响应式香蕉库的作者。

    【讨论】:

    • 既然你说我可以要求澄清/示例......在你的filterRising中,第一个参数是什么?如果它只是将一个事件转换为和事件,为什么它有 2 个参数?您将如何使用 filterRising?谢谢!
    • @taotree:啊,第一个参数只是某种起始值。我现在更改了示例以匹配描述。使用 filterRising 函数很简单:它将事件流作为参数并返回一个新的事件流作为结果,因此您可以将其应用于您选择的事件流并获取新的事件流。
    猜你喜欢
    • 2013-06-20
    • 2014-07-31
    • 2012-09-23
    • 2011-10-12
    • 2017-11-22
    • 2011-09-25
    • 2013-11-09
    • 1970-01-01
    • 2014-06-17
    相关资源
    最近更新 更多