15

我们有这样的代码:

 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,而 guiState 又输入 evtAutoLayout——所以那里有一个循环。这是故意的。自动布局调整 gui 状态,直到达到平衡,然后它返回 Nothing,因此它应该停止循环。当然,新的模型变化可以重新开始。

但是,当我们把它放在一起时,我们在编译函数调用中遇到了一个无限循环。即使 autoLayout = Nothing,它仍然会在编译期间导致堆栈溢出。

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

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

它工作正常。

有什么建议么?

4

1 回答 1

15

问题

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

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

这里是长答案。

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

    这是 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. 可以并鼓励根据再次引用事件Event的 a来定义 an。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仅返回大于先前返回的那些事件。这在函数的文档中stepper有所暗示。

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

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

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

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

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

于 2011-10-21T16:21:29.743 回答