Horizontal and vertical events
Tags: webdev, functional
Published: 2022-04-28 15:34:02

---
title: Horizontal and vertical events
published: true
description: Some learnings about the DOM, reactive programming and events
tags: webdev, functional
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/279vi804t3gs64bsc8oq.jpg
---

The **tl;dr** of reactive programming is **events happen, stuff changes**. And the hardest part to get right is the "stuff" part. This is hard because, based on the events that transpire, whatever constituted "stuff" five minutes ago may no longer be the stuff of here-and-now.

Phil Freeman's [You Might Not Need the Virtual DOM](https://blog.functorial.com/posts/2018-03-12-You-Might-Not-Need-The-Virtual-DOM.html) proposed a radical and revolutionary idea for "stuff" management:

1. Separate each morsel into two distinct parts: its address and its properties. For example, I am addressable, whereas my properties are my hair color and shoe size.
2. Use events to modulate _only_ the properties, keeping addresses static.

Freeman called his system [SDOM](https://github.com/paf31/purescript-sdom), or the "static document object model." This is in contrast to the [VDOM](https://reactjs.org/docs/faq-internals.html), or the "virtual document object model" used by frameworks like [React](https://reactjs.org/).

This simplification makes reactive programming much easier to orchestrate, as we don't need to manage the presence and absence of addressable objects. In Freeman's system, objects are _always_ present, so we just need to shepherd events to their destinations.

I think that SDOM is a dramatic improvement over VDOM-based approaches like React, and I've enjoyed getting to know Freeman's library as well as some other libraries, like [SolidJS](https://solidjs.com) and [Svelte](https://svelte.dev/), that use a variation on the SDOM approach [1].

Recently, I set out to make some small tweaks to the SDOM model. In doing so, I found myself using the terms "horizontal" and "vertical" events to describe solutions to two different problems that arise in SDOM:

1. The slow performance of components with dense models.
2. The difficulty of creating dynamic objects like lists.

This article defines and gives examples of horizontal and vertical events in hopes that they'll be useful to those of you that are building reactive apps.

# Horizontal events

Horizontal events push the notion of a `model` so far to the boundary of a system that it becomes an event. I use the spatial metaphor "horizontal" because we wind up spreading events to all the nooks and crannies of our DOM that need updating.

Models are ubiquitous in DOM programming: they serve as an abstraction for the underlying data that a UI represents. Traditionally, models are hooked up to views that are modulated by controllers, also called the [MVC pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller). React and Solid create a model through a combination of `props` and hooks, and in SDOM, the model is a first-class citizen of the type system.

### React example

In React, our model is a mixture of properties that come from the external environment, often called [`props`](https://reactjs.org/docs/components-and-props.html), and _ad hoc_ members of an internal state created using [hooks](https://reactjs.org/docs/hooks-intro.html).

```jsx
function Example(props) {
  const [age, setAge] = useState(1);
  return (<div>
  <p>Hello {props.name}. I'm guessing your age is {age}.</p>
  <button onClick={() => setAge(age + 1)}>Guess again</button> 
</div>);
}
```

SolidJS has a similar syntax, and Svelte uses a similar approach, separating out the model into a `<script/>` tag.

### SDOM example

SDOM does not make a distinction between inherited and mutable properties: all initial properties are passed in at the moment of instantiation of an element, and all of the properties are mutable.

```haskell
example =
  E.div_
    [ E.p_ [ text \_{ name, age } -> "Hello " <> name <> ". I'm guessing your age is " <> show age <> "." ] ]
    , E.button
        []
        [ Events.click \_ _ -> pure (over (prop (Proxy :: _ "age")) (add 1)) ]
        [ text_ "Guess again" ]
    ]
```

### The hidden cost of models

The issue with models in both React and SDOM is that they can easily create spurious re-renders. In the case of React, you may update several `useState` hooks frequently, causing a whole component to re-render when in fact a much smaller render might be needed. In the case of SDOM, every time an event is triggered, it is broadcast as a model to the entire component. This can be a really expensive operation for components with lots of elements.

Svelte and SolidJS, with their dedicated compilers, go a long way towards addressing this issue. The method that I'm proposing in this article, **horizontal events**, is a different approach to managing these performance tradeoffs that brings with it some neat additional properties, including _ad hoc_ internal states and fixed points over reactive systems.

### Horizontal events to the rescue

Let's look at the same example using horizontal events. I'll use [PureScript Deku](https://github.com/mikesol/purescript-deku), a web micro-framework that I wrote to explore the concept of horizontal and vertical events.

[Try it here](https://try.purescript.org/?github=/mikesol/purescript-deku/main/article/Intro.purs)
```haskell
example name = bus
 \setAge -> lcmap (bang 1 <|> _) \age -> plant $
    ( Proxy:: Proxy
"""
<div>
  <p>Hello ~name~. I'm guessing your age is ~age~.</p>
  <button ~setAge~>Guess again</button>
</div>
"""
    ) ~~
      { name: nut $ text name
      , age: nut $ text $ show <$> age
      , setAge: attr D.OnClick <<< setAge <<< add 1 <$> age
      }
```

Here, both `name` and `age` are events, with the only difference being that `age` has a pusher created via the `bus` function. In this way, "hooks" vs "props" is recast as "events to which I can push stuff" and "events that I'm just listening to." It's slightly more flexible than hooks in that one can bypass the initial state or create one using `bang`.

#### Performance

In the example above, the `text` node with `name` will only update whenever `name` updates and the `click` of `button` and the `text` node with `age` will only update when the `age` changes. SolidJS uses a similar approach to reactivity, only updating the parts of a component that need updating instead of pinging all of the listeners when a change happens. The performance gains are substantial, and make [experiments like this](https://deku-glide-benchmark.surge.sh/) possible. Of course, few apps need this level of performance, but it's good to know you have the headroom in case you need it!

#### Filtering

One advantage of models is that they represent conceptual units that are easy to work with, like a full user profile. Even with a large, monolithic model, it's still possible to gain the performance benefits of events. Because `Event` from `purescript-event` implements the `Filterable` typeclass, we can use `filterMap` or `filter` to focus on specific parts of an event and _only_ react to changes in that specific part.

In the example below, an event is only fired for the bottom-most text when the age is above 5.

[Try it here](https://try.purescript.org/?github=/mikesol/purescript-deku/main/article/Filtering.purs)
```haskell
example model = D.div_
  [ D.div_ [ text_ "Name: ", text (_.name <$> model) ]
  , D.div_ [ text_ "Age: ", text (_.age >>> show <$> model) ]
  , text
      (filter (_.age >>> (5 < _)) model $> "They sure do grow up fast!")
  ]
```

#### Composition

Functional programming is all about composition, and events are no exception. In PureScript, the `Event` type provides two primitives for composition: `sampleOn` and `keepLatest`.

`sampleOn` is used to combine together two events but _only_ inherit the temporality of the first.

[Try it here](https://try.purescript.org/?github=/mikesol/purescript-deku/main/article/SampleOn.purs)
```haskell
example counter = bus \push event -> plant $ D.div_
  [ D.button (bang (D.OnClick := push unit))
      [ text_ "Show current counter" ]
  , text (sampleOn (show <$> counter) (event $> identity))
  ]
```

`keepLatest` takes a nested event and flattens it, keeping the temporality of the most recent inner event. If the inner event has immediate temporality via `bang`, `keepLatest` effectively is [`flatMap`](https://www.geeksforgeeks.org/scala-flatmap-method/) from Scala, allowing us to take one event and turn it into several.

[Try it here](https://try.purescript.org/?github=/mikesol/purescript-deku/main/article/KeepLatest.purs)
```haskell
padMe :: Int -> String
padMe i =
  "margin: " <> show (mod i 256) <> "px;"

example counter = D.div_
  [ text_ "Hello margin"
  , D.input
      ( keepLatest $
          ( \i -> oneOfMap bang
              [ D.Style := padMe i
              , D.Xtype := "checkbox"
              , D.Checked := show (if mod i 2 == 0 then true else false)
              ]
          ) <$> counter
      )
      blank
  ]
```

Composition exists in other SDOM-family frameworks as well. For example, the Svelte compiler allows you to perform certain operations, like addition and string concatenation, directly on two reactive values. I personally like `Event` because the composition primitives are defined in terms of the temporality of events, as we saw in both the `sampleOn` and `keepLatest` examples.

#### Behaviors

Events can also be composed with `Behavior`-s. Behaviors are continuous functions over time, meaning that they can be sampled and are guaranteed to produce a value. In this way, they are isomorphic to events that fire all the time. `Behavior`-s are ideal for things that should always exist, like the time of day, a random number generator or an access token. Behaviors have a more rich and nuanced set of composition combinators, like those of `Apply` and `Applicative`, because their temporality is well-defined at every instant.

[Try it here](https://try.purescript.org/?github=/mikesol/purescript-deku/main/article/Behavior.purs)
```haskell
random = behavior \e ->
  makeEvent \k -> subscribe e \f ->
    Random.random >>= k <<< f

example counter = D.div_
  [ text_ "Here are some random numbers"
  , text (show <$> sample_ (Tuple <$> random <*> random) counter)
  ]
```

Behaviors provide a powerful way to compose effect systems that, for me, is more ergonomic than working with closure-based logics like [`useEffect`](https://reactjs.org/docs/hooks-effect.html) from React.

#### State

Using `fold` from `event`, we can enrich our events with internal states. Fold is a great way, for example, to create an event that remembers its previous value.

[Try it here](https://try.purescript.org/?github=/mikesol/purescript-deku/main/article/Fold.purs)
```haskell
main :: Effect Unit
main = runInBody1
  ( bus \push event -> plant do
      let
        top =
          [ D.input
              ( oneOfMap bang
                  [ D.OnInput := cb \e -> for_
                      ( target e
                          >>= fromEventTarget
                      )
                      ( value
                          >=> push <<< Right
                      )
                  ]
              )
              blank
          , D.button
              (bang $ D.OnClick := push (Left unit))
              (text_ "Finalize text")
          ]
        events = partitionMap identity event
        current = sampleOn events.right (events.left $> identity)
        previous = compact $ mapAccum (\a b -> Just a /\ b) current Nothing
      D.div_
        [ D.div_ top
        , D.div_ $ text (("Current value: " <> _) <$> current)
        , D.div_ $ text (("Previous value: " <> _) <$> previous)
        ]
  )
```

#### Fixed-points

Fixed points are events whose outputs are fed back into their input. We've already seen this in the very first example, where the output of the `age` event was used as its own input via `setAge`. Fixed points can also be used for "closed" events, meaning events we cannot push to, like timers. This is done using the `fix` operator from `purescript-event`. In the example below, we create a simple debounce using a fixed point operator. The original implementation of this debounce comes from Phil Freeman's event library.

[Try it here](https://try.purescript.org/?github=/mikesol/purescript-deku/main/article/Fix.purs)
```haskell
debounce
  :: forall a b
   . (Event a -> Event { period :: Milliseconds, value :: b })
  -> Event a
  -> Event b
debounce process event = fix \allowed ->
  let
    processed :: Event { period :: Milliseconds, value :: b }
    processed = process allowed

    expiries :: Event Instant
    expiries =
      map
        (\{ time, value } -> fromMaybe time (instant (unInstant time <> value)))
        (withTime (map _.period processed))

    comparison :: forall r. Maybe Instant -> { time :: Instant | r } -> Boolean
    comparison a b = maybe true (_ < b.time) a

    unblocked :: Event { time :: Instant, value :: a }
    unblocked = gateBy comparison expiries stamped
  in
    { input: map _.value unblocked
    , output: map _.value processed
    }
  where
  stamped :: Event { time :: Instant, value :: a }
  stamped = withTime event

example counter = do
  let
    myCounter = debounce
      (map (\x -> { period: wrap (toNumber x), value: unit }))
      (fold (const (add 300)) counter 0)
  D.div_
    [ D.div_ (text_ "I get slowwwwwerrr")
    , text (show <$> fold (const (add 1)) myCounter 0)
    ]
```

For me, fixed points are the crown jewel of reactive programming. I'm sort of obsessed with them, and I use them to create audio feedback loops in the browser (I've put an example at the end of this article). But I also use them for more protean tasks, like debouncers, and working with them is quite natural in the horizontal events model.

### Going horizontal

By replacing `model` from `SDOM` and `props`/`hooks` from React with `Event`, we can get amazing performance without sacrificing the push/pull mechanism of hooks or the expressiveness of models. We can also compose together events, allowing us to express rich functional relationships and make fine-grained performance decisions. Furthermore, we have the possibility to develop arbitrary stateful logic for any event using `fold` and `fix`, opening the door to _ad hoc_ state machines nestled within any part of a component.

## Vertical events

If horizontal events fan out to leaves of a DOM, vertical events are how we modulate the leaves themselves as a function of time.

Different frameworks have different terminology for this: React uses a special property called [`key`](https://reactjs.org/docs/lists-and-keys.html) for each element of an array, SolidJS uses a primitive called [`For`](https://www.solidjs.com/tutorial/flow_for), and Freeman's SDOM uses the [incremental lambda calculus](http://www.informatik.uni-marburg.de/~pgiarrusso/papers/pldi14-ilc-author-final.pdf) (he published a second package, called [`purview`](https://github.com/paf31/purescript-purview), that explores this idea further).

### React example

In React, `key` is needed to make sure the list is well sorted. Otherwise, it would be costly (if not impossible) to know what elements changed and what elements stayed the same.

```jsx
function MyList() {
  const numbers = [1, 2, 3, 4, 5];
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
}
```

### Solid example

SolidJS accomplishes the same feat with `For`, using referential equality to compare nodes.

```jsx
<For each={cats()}>{(cat, i) =>
  <li>
    <a target="_blank" href={`https://www.youtube.com/watch?v=${cat.id}`}>
      {i() + 1}: {cat.name}
    </a>
  </li>
}</For>
```

It also has a similar system to react via a component called [`Index`](https://www.solidjs.com/tutorial/flow_index).

### Purview example

[`purescript-purview`](https://github.com/paf31/purescript-purview) uses incremental functions to accomplish a similar goal. The following creates an ordered list (`ol`) in purview:

```haskell
element_ "ol" $
  IArray.mapWithIndex (\i x ->
    let changeAt i_ change_ c = change_ (IArray.modifyAt i_ c)
        delete = Atomic.lift2 (\i_ change_ -> eventListener \_ ->
          change_ (IArray.deleteAt i_)) i change
      in element_ "li" $ IArray.static
          [ component (Atomic.lift2 changeAt i change) x
          , element "button" (constant (wrap mempty))
              (IMap.singleton "click" delete)
              (IArray.singleton (text (constant (wrap "Remove"))))
          ]
        ) xs
```

Here, the functions `modifyAt` and `deleteAt` act as CRUD operations on an incremental structure.

### What's in a name?

All of these approaches use indices to track changes over a data structure. In the case of React, the index `key` is explicit. In the case of `For` in Solid, the index is referential equality. For incremental functions, instead of providing the full data structure on each render, we only provide diffs that resemble CRUD operations.

The difficult thing to get right in all of these approaches are the indices. Essentially, we need to give things _names_ so that, when one needs to sort a collection or delete an element from a collection, we know exactly which index we're talking about.

**Vertical events** are a solution to this problem that, like horizontal events, pushes data so far to the boundary that we effectively make it synonymous with an event. But here, instead of pushing a model to the boundary and spreading it horizontally across many elements, we push an index to the boundary of its event until it goes one level deeper and becomes a nested event (thus the vertical).

### Getting vertical

Let's see a small example of a vertical event in `purescript-deku`. We'll use the most classic dynamic structure of them all: the todo list.

[Try it here](https://try.purescript.org/?github=/mikesol/purescript-deku/main/article/Todo.purs)
```haskell
data MainUIAction
  = AddTodo
  | ChangeText String

data TodoAction = Prioritize | Delete

main :: Effect Unit
main = runInBody1
  ( bus \push event -> plant do
      let
        top =
          [ D.input
              ( oneOfMap bang
                  [ D.OnInput := cb \e -> for_
                      ( target e
                          >>= fromEventTarget
                      )
                      ( value
                          >=> push <<< ChangeText
                      )
                  , D.OnKeyup := cb
                      \e -> for_ (fromEvent e) \evt -> do
                        when (code evt == "Enter") $ do
                          push AddTodo
                  ]
              )
              blank
          , D.button
              (bang $ D.OnClick :=  push AddTodo)
              (text_ "Add")
          ]
      D.div_
        [ D.div_ top
        , D.div_ $
            ( \txt -> keepLatest $ bus \p' e' ->
                ( bang $ Insert $ D.div_
                    [ text_ txt
                    , D.button
                        ( bang
                            $ D.OnClick := p' SendToTop
                        )
                        [ text_ "Prioritize" ]
                    , D.button
                        ( bang
                            $ D.OnClick :=  p' Remove
                        )
                        [ text_ "Delete" ]
                    ]
                ) <|> e'
            ) <$>
              filterMap
                ( \(tf /\ s) ->
                    if tf then Just s else Nothing
                )
                ( mapAccum
                    ( \a b -> case a of
                        ChangeText s -> s /\ (false /\ s)
                        AddTodo -> b /\ (true /\ b)
                    )
                    event
                    mempty
                )
        ]
  )
```

Here, the list of todo items has the type `Event (Event Child))`. The outer event is used to add elements to our MVC. The inner element are instructions with respect to the element: in our case, `Insert`, `SendToTop` and `Remove`. And `Child` itself holds an element when `Insert` is used. This element has its own internal event logic and may itself have vertical events, which may have their own elements, which may have their own vertical events, which... You get the idea! 

Vertical events solve the indexing problem by transferring the indexing mechanism to a hierarchy of events. Every time the outer event fires, a new inner event with its own logic is created.

Vertical events are very forgiving structures because they decouple emission and interpretation. If I were more hand-wavey, I'd even say that _the `Event` is the free incremental structure_ (ok, fine, I said it...). With an incremental data structure, the CRUD operations that are emitted have a semantic meaning that is fixed by the data structure. For example, `deleteAt` for an incremental array can only mean one thing. Vertical events, on the other hand, emit instructions that are interpreted by the rendering engine, very much like a Free Monad is interpreted by an interpreter. For example, `SendToTop` from our `Todo` example may mean multiple things in multiple contexts, and its meaning can be determined by the framework on an element-by-element basis. This allows one to create rich incremental algebras of vertical effects and to even, in the most exotic cases, have multiple levels of event nesting for phenomena that have multi-dimensional indices.

#### Recursion, and fixed points

As mentioned before, vertical events can go arbitrarily-many levels deep depending on the dimensionality of the use case. In many cases, the semiotics of depth will be fixed. For example, `Event (Event Child)` _means_ one thing, `Node` means something else, but `Event (Event (Event (Event Child)))` means nothing so our framework (in my case Deku) can simply not admit it. However, functional patterns open up a much larger range of possibilities. One that we've already seen are recursively defined nested structures, which is how we create a dynamic DOM tree. We can also have:

- arbitrarily deep recursion via `Free Event a`
- generative events via `Nu Event` (be careful if you do this to unsubscribe old events, otherwise your computer will fry!!)
- infinitely deep empty events via `Mu Event`. I have no idea why one would do this, but it feels sort of profound, so why not!

#### Shared events and providers

Vertical events and horizontal events can draw from the same source, which solves some classic problems in reactive apps. For example, we can use the same counter to increment a text node (horizontal) and increment a user list (vertical).

Shared events also pave the path to provider systems, which allow us to (for example) propagate a token across an entire application. Providers can be implemented as a distributive application monad, allowing us to pick of the elements we need when we need them _à la_ [React Providers](https://reactjs.org/docs/context.html).

## Horizontal and vertical events _in anger_

I've given several examples in this article showing horizontal and vertical events being used in [`purescript-deku`](https://github.com/mikesol/purescript-deku), but the impetus for this experiment was a rewrite of [`purescript-wags`](https://github.com/mikesol/purescript-wags). On the [front page of the wags documentation](https://docs.wags.fm), there is a single slider that emits events that are consumed five-layers deep of a nested feedback loop. Its performance is much better than the original wags, and it is less lines of code and less type-level machinery. Using this idea of feedback loops, I've started a project called _Machines of Infinite Fortune_ that take a single impulse and uses analyzer nodes to shape the resulting sound ad-infinitum (or at least until you close your browser window, which for me is never).

I'm pretty stoked about this concept of horizontal and vertical events as alternative to, and hopefully simplification of, the more traditional concepts of "model" and "dynamic collection. I hope that they serve as useful tidbits of thought for you in your own reactive projects as they scale horizontally and vertically to new heights!

[1] It is unclear which frameworks inspired which frameworks as SDOM ideas permeated the develo-sphere in the mid-to-late 2010s, but I assume they all borrowed from each other to a certain extent.