HeinrichApfelmus / threepenny-gui

GUI framework that uses the web browser as a display.
https://heinrichapfelmus.github.io/threepenny-gui/
Other
439 stars 77 forks source link

Add scanE #58

Closed Shimuuar closed 10 years ago

Shimuuar commented 10 years ago

I found this combinator quite handy and usually more convenient than accumE

HeinrichApfelmus commented 10 years ago

In which situations/code examples do you find scanE to be more convenient?

The thing is that I'm intentionally discouraging fold-like type signatures for the accumulation functions. I have found that they tend to invite the creation of extra data types for each event, which clutters code very quickly. But I'm happy to discuss examples.

Shimuuar commented 10 years ago

Actually I found accumE diffucult to use and use scanE instead. Maybe it's beacause I too used to think in terms of folds. For example following function for merging event streams. Of course it's trivial to rewrite it using accumE but I found it easier to use scanE

ap :: (a,Event a) -> (b, Event b) -> IO ((a,b),Event (a,b))
ap (a0,evtA) (b0,evtB)
  = scanE step (a0,b0) $ joinE evtA evtB
  where
    step (_,b) (Left  a) = (a,b)
    step (a,_) (Right b) = (a,b)

joinE :: Event a -> Event b -> Event (Either a b)
joinE ea eb = unionWith const (Left <$> ea) (Right <$> eb)
HeinrichApfelmus commented 10 years ago

Your examples demonstrates what I mean with "invites the creation of extra data types for each event". :smile:

As you say, it's not difficult to replace the use of scanE with a direct use of accumE. But the point is that you can do even more than that: you can get rid of the intermediate Either type.

ap :: (a,Event a) -> (b, Event b) -> IO ((a,b),Event (a,b))
ap (a0,evtA) (b0,evtB) =
        accumE (a0,b0) $ unionWith (.) (stepA <$> evtA) (stepB <$> evtB)
    where
    stepA a (_,b) = (a,b)
    stepB b (a,_) = (a,b)

As an additional benefit, the code will now be correct when the two events occur simultaneously. Your original code discards the left occurence, which is probably not what you want. (In fact, you could say that joinE maps to the wrong type: the case where both events occur at once cannot be represented by Either.)

Shimuuar commented 10 years ago

I think I'm starting to understand what do you mean. Problems arise because of simultaneous events. Without them both functions are equivalent and choice is purely matter of taste. But we have to take simultaneous events into account when we merge event streams and we need event values to form semigroup (we don't necessarily want associativety though). accumE works fine because a → a is a monoid. On the other hand folds puts no such restriction on event type.

It's probably worth mentioning in the documentation. But it's all a bit vague