Question

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: leaking memory 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?

Was it helpful?

Solution

This behavior is actually trivial to implement without dynamic switching, if you use a Behavior for the selection input and recall that Behavior is an instance of Applicative. I hadn't really internalized the f <$> x <*> y <*> z ... idiom when I asked this question, so here's an explicit working-out for others like me:

switchBehaviors 
    :: Behavior t a    -- | Behavior to yield when it's "True"
    -> Behavior t a    -- | Behavior to yield when it's "False"
    -> Behavior t Bool -- | Select between behaviors
    -> Behavior t a
switchBehaviors t f es = 
    (\e x y -> if e then x else y) <$> es <*> t <*> f

(Heinrich Apfelmus addressed the first question in a comment. As he notes, Reactive.Banana.Switch is still very much experimental, and its performance characteristics are improving.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top