3

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?

AndrewC
  • 32,300
  • 7
  • 79
  • 115
Christian Conkle
  • 5,932
  • 1
  • 28
  • 52
  • Version 0.7 of reactive-banana doesn't implement garbage collection for dynamically switched events yet, so anything very dynamic is likely to eat up space. I'm working on it. :-) – Heinrich Apfelmus Mar 14 '13 at 08:52
  • @HeinrichApfelmus Is this bug still present? I tried an experiment today with dynamic switching (on a key press) and after 10-20 key presses my program became unresponsive and at least once it even crashed with a stack overflow. – Jason Dagit Jun 20 '13 at 06:45
  • 1
    @JasonDagit The development version fixes a few additional space leaks that might contribute to the problem. However, I still haven't come around to implementing garbage collection for dynamic event switching. – Heinrich Apfelmus Jun 20 '13 at 08:23

1 Answers1

3

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.)

Christian Conkle
  • 5,932
  • 1
  • 28
  • 52