反应性香蕉可以在网络中处理循环吗?

men*_*ics 15 haskell reactive-programming reactive-banana

我们有这样的代码:

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

 evtAutoLayout :: Event GuiState
 evtAutoLayout = fmap fromJust . filterE isJust . fmap autoLayout $ changes guiState
Run Code Online (Sandbox Code Playgroud)

你可以看到evtAutoLayout提供给guiState,它被输入到evtAutoLayout中 - 所以那里有一个循环.这是故意的.自动布局调整gui状态直到达到平衡,然后它返回Nothing,因此它应该停止循环.当然,新的模型改变可以再次启动它.

但是,当我们将它们组合在一起时,我们会在编译函数调用中遇到无限循环.即使autoLayout = Nothing,它仍会在编译期间导致堆栈溢出.

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

 guiState :: Discrete GuiState
 guiState = stepperD (GuiState []) $ mkGuiState <$> changes model
Run Code Online (Sandbox Code Playgroud)

它工作正常.

有什么建议?

Hei*_*mus 15

这个问题

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

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

这里的答案很长.

  1. 反应性香蕉的语义不支持Event直接用自身定义.

    这是Conal Elliott在他最初的FRP语义中做出的决定,我决定坚持下去.它的主要好处是语义仍然非常简单,你可以随时思考

    type Behavior a = Time -> a
    type Event    a = [(Time,a)]
    
    Run Code Online (Sandbox Code Playgroud)

    我提供了一个模块Reactive.Banana.Model,它几乎精确地实现了这个模型,你可以查询它的源代码,以获得有关reactive-banana语义的任何问题.特别是,您可以使用它来推断您的示例:使用笔和纸计算或在GHCi中尝试(使用一些模拟数据)将告诉您该值evtAutoLayout等于_|_,即未定义.

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

  2. 可以并且鼓励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)]
    
    Run Code Online (Sandbox Code Playgroud)

    给定事件流,该函数filterRising仅返回大于先前返回的事件.这stepper函数文档中有所暗示.

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

  3. 仍然可以在反应香蕉中插入小延迟,它不是核心库的一部分,因此没有任何保证的语义.此外,您需要从事件循环中获得一些支持才能执行此操作.

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

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

披露:我是反应香蕉库的作者.