4

Let's say I have

x :: Event t (A,B)

I can get the first component of it:

fst <$> x :: Event t A

However, this event will fire even when the first component doesn't change. I want to avoid that, because it would trigger an expensive recomputation.

A is an instance of Eq, so I want to be able to remove the successive events where the first component is not changed compared to its last value.

Ideally, I'd like a function

filterDups :: Eq a => Event t a -> Event t a

which would do that without resorting to the Moment monad. Is it possible? Or what's the best way to do this?

Roman Cheplyaka
  • 37,738
  • 7
  • 72
  • 121

3 Answers3

2

I've never used reactive-banana, and haven't tested this, so beware. Nevertheless, here's one idea that at the very least typechecks. We'll use accumE to remember something about past events.

notice x (old, new) = (new, Just x)

changed (Just old, Just new) = guard (old /= new) >> return new
changed (_, new) = new

justChanges :: Eq a => Event t a -> Event t a
justChanges e = filterJust $ changed <$> accumE (Nothing, Nothing) (notice <$> e)
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
2

This solution uses (or abuses) the fact that the stepper function updates the Behavior "slightly after" the Event, see the comment in the docs.

First create a Behavior based on the Event, you have to find a suitable first value for the Behavior in your solution, for simplicity I am assuming that the first element of your pair is an Int:

x :: Event t (Int, b)

firstB :: Behavior t Int
firstB = stepper 0 $ fst <$> x

Then you can use the filterApply function:

filterDups e = filterApply (firstNotEq <$> firstB) e
    where firstNotEq old (new, _) = new /= old
          firstB                  = stepper 0 $ fst <$> e

Take a look at this gist for a simple example using the Threepenny GUI.

Rodrigo Taboada
  • 2,727
  • 4
  • 24
  • 27
  • 1
    This implementation is correct in spirit, but there is a subtle problem when the event contains simultaneous event occurrences. The Behavior will only remember the last value of simultaneous occurrences, never the values in between. A completely correct implementation would use `accumE` here. However, future versions of reactive-banana will get rid of simultaneous occurrences in a single event altogether, so that the definition here becomes correct again. – Heinrich Apfelmus Oct 12 '13 at 10:41
  • Thanks for the clarification, this was my first time using reactive-banana and I wasn't sure if this solution was correct for all cases. – Rodrigo Taboada Oct 12 '13 at 16:29
2

You have to remember information about the history of the event to do what you want. As other answers have already mentioned, you can use accumE for that purpose. Here a succinct definition:

unique :: Eq a => Event t a -> Event t a
unique = filterJust . accumE Nothing
       . fmap (\a acc -> if Just a == acc then Nothing else Just a)
Heinrich Apfelmus
  • 11,034
  • 1
  • 39
  • 67