Synchronous Events
It turns out that there’s a certain class of event transformers (Event a -> Event b) in Reactive that have some really neat and convenient properties. We’ll call this class synchronous event transformers.
The event transformers we’re interested in (
: Event a -> Event b) semantically have the following property.

where
pfst = map fst
In other words, synchronous event transformers do not change the number of occurrences nor the times of occurrences of the argument. Most of Reactive’s event transformers have this property. Lets make a simple wrapper for Synchronous transformers:
type EventT a b = Event a -> Event b newtype Synchronous a b = S {toEventT :: (EventT a b)}
There may be a better representation. Conal Elliott on #haskell suggested using MutantBots, but they aren’t expressive enough to encapsulate transformers like withNextE. For the purpose of this discussion, we’ll use the above representation and invite the reader to come up with a better one.
Some helper functions will come in handy. By the way, we’ll be using the style of Semantic Editor Combinators.
inS :: ((EventT a b) -> (EventT c d)) -> (Synchronous a b -> Synchronous c d) inS f = S . f . toEventT inS2 :: ((EventT a b) -> (EventT c d) -> (EventT e f)) -> (Synchronous a b -> Synchronous c d -> Synchronous e f) inS2 f (S a) (S b) = S (f a b)
An easy thing to see is that our Synchronous Event Transformers (SET’s) are Functors. The definition of fmap simply applies the function to the resultant event.
instance Functor (Synchronous a) where fmap = inS . fmap . fmap
One interesting thing about synchronous events is that we can apply one to another in parallel. In other words, if we have an event of functions and an event of values, we can apply each function event to each value event if they are synchronous.
-- Only works on events that are synchronous apSync :: EventG t (a->b) -> EventG t a -> EventG t b apSync = inEvent2 f where f = inFuture2 (\(ta,a) (_, b) -> (ta, g a b)) g (Stepper a ae) (Stepper b be) = (Stepper (a b) (apSync ae be))
If we have two synchronous event transformers, how can we combine them? One way would be to use apSync on their results:
apSyncT :: Synchronous e (d -> f) -> Synchronous e d -> Synchronous e f apSyncT = inS2 (\f g e -> apSync (f e) (g e))
Hey, that looks mighty familiar:
instance Applicative (Synchronous a) where pure = S . pure . pure (<*>) = apSyncT
Interesting. Now we have an Applicative instance for synchronous event transformers. Later on, we’ll see how this is useful. As it turns out we can declare Category and Arrow instances as well:
instance Category Synchronous where id = S id (.) = inS2 (.) instance Arrow Synchronous where arr = S . fmap (&&&) = liftA2 (,) -- Defined in terms of (&&&) first f = (f . arr fst) &&& arr snd
As with any applicative functor, we can define forwarded Num instances
-- Boilerplate taken from Reactive noOv :: String -> String -> a noOv ty meth = error $ meth ++ ": No overloading for " ++ ty noFun :: String -> a noFun = noOv "behavior" -- Eq & Show are prerequisites for Num, so they need to be faked here instance Eq (Synchronous a b) where (==) = noFun "(==)" (/=) = noFun "(/=)" instance Show (Synchronous a b) where show = noFun "show" showsPrec = noFun "showsPrec" showList = noFun "showList" instance Num b => Num (Synchronous a b) where negate = fmap negate (+) = liftA2 (+) (*) = liftA2 (*) fromInteger = pure . fromInteger abs = fmap abs signum = fmap signum
Uses
Now that we have all these properties of synchronous events, how can we use them? Lets consider the types of a couple functions in reactive
withTimeE :: Ord t => EventG (Improving t) d -> EventG (Improving t) (d, t) withTimeE_ :: Ord t => EventG (Improving t) d -> EventG (Improving t) t
Several Synchronous event functions come in pairs like this. The more general version passes along the data of the originating event and the convenient underscore variant doesn’t. The latter is implemented in terms of the former. With Synchronous, we can implement the more complex version in terms of the simpler version:
timeE :: Synchronous a TimeT timeE = S withTimeE_ timeE' :: Synchronous a (a, TimeT) timeE' = liftA2 (,) id timeE -- or, using Beelsebob's Applicative infix library -- timeE' = id <^(,)^> timeE -- or, if we make a default instance (for AFs) for Zip in Conal's TypeCompose -- timeE' = zip id timeE
Synchronous eliminates the need to have two versions of these functions and saves us the strain of having implement the more complex variants.
More Synchronous Fun
Lets pull some more synchronous event transformers into our Synchronous data type:
prevE, nextE :: Synchronous a a prevE = S withPrevE_ where withPrevE_ = (fmap.fmap) snd withPrevE nextE = S withNextE_ where withNextE_ = (fmap.fmap) snd withNextE mealyE :: b -> (b -> b) -> Synchronous a b mealyE a b = S (mealy_ a b) splitE' :: Event b -> Synchronous a (Event b) splitE' = S . splitE_ where splitE_ :: (Ord t) => EventG t b -> EventG t a -> EventG t (EventG t b) splitE_ = (fmap.fmap.fmap) snd splitE
Recall our elapsedFirstTry function from the event tutorial.
elapsedFirstTry :: Event () -> Event TimeT elapsedFirstTry e = fmap f (withPrevE (withTimeE_ e)) where f (tCur, tPrev) = tCur - tPrev
Since elapsed is a synchronous event transformer, we no longer need to implement a more complex variant of it. Thinking in terms of synchronous event transformers, we can further simplify the above definition:
elapsed :: Synchronous a TimeT elapsed = timeE - (prevE . timeE)
This can be read as “the time minus the previous time” for each occurrence. Our metronome implementation also sheds some complexity.
-- Old definition metronome :: BellMachine metronome = switchE . fmap f . withTimeE . mealy Idle next . withElapsed_ where f ((dt,PlayingCycle), t) = period dt t f _ = mempty
to
metronome :: BellMachine metronome = switchE . toEventT e' where e' = f <$> mealyE Idle next <*> timeE <*> elapsed where f PlayingCycle t dt = period dt t f _ _ _ = mempty
Using applicative in this case relieves us from tuple juggling and allows us to express more directly what we’d like. Note that toEventT was required here because switchE is not a synchronous transformer.
Arrows
I’m not certain it is all that useful, but arrows allow us to use the special arrow syntax. We can rewrite e' this way:
e' :: Synchronous a (Event ()) e' = proc e -> do m <- mealyE Idle next -< e t <- timeE -< e dt <- elapsed -< e returnA -< if m == PlayingCycle then period t dt else mempty
– David Sankel @ Anygma.