我们有这样的代码:
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 - 所以那里有一个循环。这是故意的。汽车 布局调整gui状态直到达到平衡然后 它返回Nothing,因此它应该停止循环。一个新的模型改变 当然可以再次开始。
但是当我们将它们组合在一起时,我们会遇到无限循环 编译函数调用。即使autoLayout = Nothing,它仍然会在编译期间导致堆栈溢出。
如果我在guiState中删除了union调用并删除了evtAutoLayout 图片......
guiState :: Discrete GuiState
guiState = stepperD (GuiState []) $ mkGuiState <$> changes model
它工作正常。
有什么建议吗?
答案 0 :(得分:15)
问题
reactive-banana库是否支持递归定义的事件?
不仅有一个,而且有三个答案。简短的答案是:1。一般不,2。有时是,3。有解决方法是。
这里的答案很长。
reactive-banana的语义不支持直接用定义 Event
。
这是Conal Elliott在其原始FRP语义中做出的决定,我决定坚持下去。它的主要好处是语义仍然非常简单,你可以随时考虑
type Behavior a = Time -> a
type Event a = [(Time,a)]
我提供了一个模块Reactive.Banana.Model,它几乎可以精确地实现这个模型,你可以查询它的源代码,以获得有关反应性香蕉语义的任何问题。特别是,您可以使用它来推断您的示例:使用笔和笔进行计算。论文或在GHCi中尝试(带有一些模拟数据)将告诉您值evtAutoLayout
等于_|_
,即未定义。
后者可能会令人惊讶,但正如您所写,该示例确实未定义:GUI状态仅在evtAutoLayout
事件发生时才会更改,但只有在您知道GUI状态是否发生更改时才会发生,反过来等等你总是需要通过插入一个小延迟来打破扼杀反馈循环。不幸的是,反应性香蕉目前没有提供插入小延迟的方法,主要是因为我不知道如何以允许递归的方式描述[(Time,a)]
模型的小延迟。 (但请参见答案3.)
可以并鼓励根据再次引用该事件的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中暗示的。
然而,这可能不是你想要的那种递归。
仍然可以在反应香蕉中插入小延迟,它只是核心库的一部分,因此没有任何保证的语义。此外,您需要事件循环的一些支持才能做到这一点。
例如,您可以使用wxTimer在处理完当前事件后立即安排事件。 Wave.hs示例演示了wxTimer与reactive-banana的递归使用。我不太清楚当你将定时器间隔设置为0
时会发生什么,但它可能执行得太早。您可能需要进行一些实验才能找到一个好的解决方案。
希望有所帮助;随时可以要求澄清,例子等。
披露:我是反应香蕉库的作者。