I'm not sure whether this behavior is expected (i.e. I'm misusing Reactive.Banana.Switch) or a bug.
Let's say I have two like-typed input Behaviors, and I want to switch between them based on an Event. I wrote this function:
switchBehaviors ::
Behavior t a -- | Behavior to yield initially and after "True" events
-> Behavior t a -- | Behavior to yield after "False" events
-> Event t Bool -- | Select between behaviors
-> Moment t (Behavior t a)
switchBehaviors t f es = do
t' <- trimB t
f' <- trimB f
return $ switchB t $ (\e -> if e then t' else f') <$> es
This code seems innoccuous enough; it type-checks, compiles, and gives the desired result when embedded into a simple GUI mockup. (Two text entry fields for the Behaviors, a button emitting alternate True and False Events, and a label bound to the combined Behavior using sink
.)
However, after triggering the Event several times, it becomes obvious that there's a catastropic leak somewhere. The app starts taking longer and longer to react both to changes in the input Behaviors and to new Events. It also starts eating memory.
Here's a heap profile with -hC: I'm repeatedly toggling the Event; the two largest spikes are maybe the twentieth and twenty-first firings of the Event.
The use of trimB feels a bit like hand-waving to make the types add up; I don't know whether I'm using it correctly or abusing it somehow.
My sub-questions are:
1) Am I abusing the Reactive.Banana.Switch API, or is this a bug? If I am abusing the API, what am I doing wrong?
2) Should I do this without using dynamic event switching? Using apply
doesn't give the correct behavior, because the resulting Event doesn't fire when the underlying Behavior changes. If I unwrap all three inputs to Events, I imagine I can set up a fold, manually accumulating the most recent value of each input Event. Is that the correct approach?