Events are best served hot
Tags: functional
Published: 2023-07-26 12:37:32

---
title: Events are best served hot
published: true
description: Why you should stop using cold events
tags: functional
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7eerl4tuz77tnjj3jx0m.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-07-25 13:47 +0000
---

In most functional programming languages, a signal or event type is defined as such:

```haskell
newtype Event a = Event ((a -> Effect Unit) -> Effect (Effect Unit))
```

The contract is saying: "If you give me a way to report events `a -> Effect Unit`, I'll give you a way to turn on and off the event stream `Effect (Effect Unit)`. The outer effect turns on the stream, and the inner one turns it off.

This definition has been used to build many functional frameworks, including `purescript-hyrule`, `purescript-halogen-subscriptions` and almost all of the Rx implementations.

The definition has also given rise to a distinction between _hot_ and _cold_ events. A "hot" event is one through which information is already flowing when you subscribe to it, whereas a "cold" event starts the flow of information upon subscription. The [Rx documentation](https://reactivex.io/documentation/observable.html) does a good job describing this in the **“Hot” and “Cold” Observables** section.

In this article, I'll show some examples of hot and cold events. I'll then argue that cold events are evil and represent 99% of the problems folks have with event-based libraries. Lastly, I'll propose a definition of event that only admits hot events.

# Events hot and cold

In this section, I'll present two common examples of hot and cold events as well as an example of an event that straddles this divide, much to the chagrin of many a developer.

## Some like it hot

In PureScript, a _hot_ event is created with the `create` function.

```haskell
create :: forall a. Effect { event :: Event a, push :: a -> Effect Unit
```

When using `create`, you get an event and a pusher. This event is hot, meaning that anything that subscribes to it only gets things pushed to `push` _after_ the subscription is created.

Hot events are useful when, for example, you're building an event bus. You send the pusher to whatever corner of your application needs to push information, and you subscribe wherever you need to receive it.

## Purity is cold

The most common example of a cold event is `pure` via its applicative instance.

```haskell
instance Applicative Event where
  pure a = Event \k -> k a *> pure (pure unit)
```

Here, every time someone subscribes to an event by giving it a `k`, it immediately emits an `a`. It is the epitome of a cold event, replaying the exact same sequence for each subscription.

## Intervals

Whenever a programmer encounters one of PureScript's event libraries for the first time, if they spend enough time working with it, they eventually encounter:

```haskell
interval :: Int -> Event Instant
```

This emits the current time at intervals of `Int` milliseconds.

`interval` is cold, meaning every time you subscribe to it, a new interval starts. And, given the finickiness of most programming languages, even if you do `traverse interval [5, 5, 5, 5]`, which in theory should create four perfectly aligned events that go off every 5 milliseconds, the start times will be slightly offset and the gaps will only get worse as time goes on. This is because, instead of using the same interval 5 times, we are creating four different intervals.

# Cold events are evil

Robert Frost said it best:

> Some say the world will end in fire,
> Some say in ice.
> From what I’ve tasted of desire
> I hold with those who favor fire.
> But if it had to perish twice,
> I think I know enough of hate
> To say that for destruction ice
> Is also great
> And would suffice.

Cold events destroy programs. They potentially tuck away all sorts of side effects in the subscription phase, which happens during the first `Effect` of `Effect (Effect Unit)` in the definition of `Event`. While these side effects are cleaned up on unsubscription, they encourage composing together objects in a pure way that, at scale, dangerously masks what side effects are actually going on.

To drive the point home, consider a "today's news" event that, on subscription, pays 5 cents to your local newsmonger and starts the flow of headlines. On unsubscribe, it stops the flow of headlines.

Can you spot what's different and what's the same between these two programs?

### Program 1

```haskell
void $ subscribe (sequence (repeat 100_000 today'sNews) log)
```

### Program 2

```haskell
do
  { event, push } <- create
  void $ subscribe today'sNews push
  subscribe (sequence (repeat 100_000 event) log)
```

In both programs, we'll have each headline from today's news spammed to the log 100_000 times. But in the second program, we only pay 5 cents for today's news, whereas in the first one, we pay 5,000 dollars.

Now, imagine that you're working on a large team and you're working on an API that takes an `event` as a parameter. Is it safe to subscribe to? Will doing so incur a cost? Scratch your car? Kick your dog? This is why cold events, like Robert Frost's ice, are the great destroyer. They are footguns that do nothing that a hot event can't do with a bit of proper planning.

# We can do better!

So why did the magnanimous inventors of events create cold events? Let's look at the signature of `Event` again:

```haskell
newtype Event a = Event ((a -> Effect Unit) -> Effect (Effect Unit))
```

The thing that makes `cold` events so dangerous is the `Effect (Effect Unit)`, where we can introduce an arbitrary side effect upon subscription and another one upon unsubscription. But we _do_ need side effects on subscription and unsubscription, even for hot events. Let's look at the implementation of a function like `create`, which as we remember from above is how we make a hot event.

```haskell
create
  :: forall a
   . Event { push :: a -> Effect Unit, event :: Event a }
create_ = do
  -- here, FOST is Foreign.Object.ST
  -- which allows you to work with mutable objects
  subscribers <- liftST FOST.new
  idx <- liftST $ STRef.new 0
  pure
    { event:
        Event \k -> liftST do
          rk <- STRef.new k
          ix <- STRef.read idx
          void $ FOST.poke (show ix) rk subscribers
          void $ STRef.modify (_ + 1) idx
          pure $ liftST do
            void $ STRef.write mempty rk
            void $ FOST.delete (show ix) subscribers
    , push:
        \a -> do
          o <- liftST $ FOST.unfreeze subscribers
          for_ subscribers \rk -> do
            k <- liftST $ STRef.read rk
            k a
    }
```

In `event`, we are adding a listener to a subscriber cache on subscribe and removing it on unsubscribe. Then, on `push`, we iterate over the subscriber cache and push our value to each subscriber.

It's clear, then, that even in `hot` events, we need effects on subscription and unsubscription. But wait a second, do we really need `Effect` as our effect? Looking at the code, it looks like both the subscriber and unsubscriber are lifted from `ST Global` to `Effect`. So what if we dispensed with `Effect` and instead made the signature of `Event`.

```haskell
newtype Event a = Event ((a -> Effect Unit) -> ST Global (ST Global Unit))
```

We still have an `Effect` in the first argument to `Event` because `push` in the example above needs to be effectful and it can contain arbitrary side effects (like, for example, pushing the result of an API call). However, the subscription and unsubscription mechanism is entirely achievable in the `ST Global` monad because it is only ever creating and manipulating references to stateful objects.

As promised, this is the new definition of `Event` that I offer for folks's humble consideration. In this setup, anything of type `Event a` can only ever be hot because it's impossible to trigger any effect on subscription on unsubscription. The worst conundrum we can get in if we do something exotic like roll our own `create` is to forget either subscribing or unsubscribing, but this is far more anodine than the bomb detonators and Rick-and-Morty-esque "one is now zero" mechanisms one can implement using `Effect`.

So if we're bidding a farewell to cold events, what are we losing. The most bittersweet farewell is to `Event`'s `Applicative` instance because we no longer can trigger an event upon subscription. But did we ever really need applicative? `Applicative` only makes sense insofar as we have some computation where we need to "prime the pump" of an event so that there's something there when another event goes off. For example, let's look at the definition of `fold`, which folds over an event in time to create an accumulator.

```haskell
-- | Fold over values received from some `Event`, creating a new `Event`.
fold :: foral a b. (b -> a -> b) -> b -> Event a -> Event b
fold f b e =
  fix \i -> sampleOnRight (i <|> pure b) ((flip f) <$> e)
```

Here, we use `pure` in so that the following sequence of events (ha!) occurs:

1. `pure b` goes off.
2. `e` goes off.
3. `sampleOnRight` responds to `e` going off as it drives the sampling, and picks up that `pure b` has already gone off, so it emits.
4. This emission causes `i` (the fixed point) to emit, as the input is the output.
5. When `i` emits, as `e` hasn't emitted again, we wait for the next `e`.
6. Now when `e` emits again, it will combine with its previous incarnation (aka `i`). The `pure b` will never go off again: it just primed the pump so that the loop could start.

But do we really need `pure` here? We don't need an event to trigger immediately upon subscription, we just need it to trigger sometime before `e`. Or in other words, if `0` is the moment a system is subscribed to and `t` is the moment `e` fires, for the six-part sequence above to hold, we need `pure b` to fire anytime between `0` and `t - ϵ`, where `ϵ` is arbitrarily small. So what can get us an event at `t - ϵ`? `e` can!

"Woah woah waoh," you may protest, "Hold your horses. If `e` happens at time `t`, how the heck can it happen at `t - ϵ`? Are you a time traveler?" Yes, I am. The future sent me to your era to teach you about FRP. But no, I didn't need to use that superpower to conjure an event at time `t - ϵ` from an event at time `t`. Because events are read from left to right, any event that fires on the left side of an expression will, fire before the right side. This is because, at the end of the day, events just translate to imperative-looking effectful code, and two bits of effectful code that execute "simultaneously" still have to run before another. To reinforce that, let's look at the definition of `sampleOnRight`.

```haskell
sampleOnRight :: forall a b. Event a -> Event (a -> b) -> Event b
sampleOnRight (Event e1) (Event e2) =
  Event $ \k -> do
    latest <- Ref.new Nothing
    c1 <-
      e1 \a -> do
        Ref.write (Just a) latest
    c2 <-
      e2 \f -> do
        o <- Ref.read latest
        for_ o (\a -> runEffectFn1 k (f a))
    pure do
      c1
      c2
```

If `e1` and `e2` get an emission at the same time, the top of the `do` block will run before the bottom of the `do` block. That's the nature of imperative code.

So we almost have our `Applicative` instance back, but not quite. Instead of `pure b`, we could write:

```haskell
fold :: foral a b. (b -> a -> b) -> b -> Event a -> Event b
fold f b e =
  fix \i -> sampleOnRight (i <|> (e $> b)) ((flip f) <$> e)
```

Now the `e` on the left will fire slightly before the `e` on the right and we have our event at time `t - ϵ`. But we have another problem now. This does much more than `pure` because it will keep firing every time `e` fires. We only want it to fire once. Drats. So how do we pull that off? Again, `ST` to the rescue!

```haskell
once :: forall a. Event a -> Event a
once (Event e) =
  Event $\k -> do
    latest <- STRef.new Nothing
    u <- STRef.new $ pure unit
    c <-
      e \a -> do
        o <- liftST $ STRef.read latest
        case o of
          Nothing -> do
            void $ liftST $ STRef.write (Just a) latest
            k a
            liftST $ join (STRef.read u)
          Just _ -> pure unit
    void $ STRef.write c u
    o <- liftST $ STRef.read latest
    case o of
      Just _ -> c
      _ -> pure unit
    pure do
      c
```

We can use `ST` to monitor how many times an event is invoked and unsubscribe it automatically after the first emission. With this, we finally have something like `pure` back:

```haskell
fold :: foral a b. (b -> a -> b) -> b -> Event a -> Event b
fold f b e =
  fix \i -> sampleOnRight (i <|> once (e $> b)) ((flip f) <$> e)
```

So the most important `cold` event, `pure`, is achievable with respect to any arbitrary event `e` because we can always emit an event slightly before `e` using `once` that will achieve the same effect as `pure` did.

As for other cold events like `interval` or `today'sNews`, we can always create something akin to a subscription or unsubscription in an `Effect` block. Now you may protest: "I'm working with `Event`s and I'm not in the `Effect` monad. How am I gonna do something like `interval` if I need to be in `Effect`. The answer is that, if you are working with events, you'll have to subscribe to them at some point, and when you subscribe to them, it will be in an `Effect` monad. So long as you can pipe information through your system from this `Effect` monad, for example by using a `Reader` over `Event`, you're fine.

So, in conclusion, there is nothing you can achieve with cold events that you can't achieve in a hot world with a bit of elbow grease. And, for the price of that grease, you will avoid a plethora of pitfalls plus have a more consistent and manageable API.