Deku logo

Functional reactive programming

Alternatives

How events function in an alternative universe.

In functional programming, we tend to be obsessed with two classes of operations.

  1. Composing stuff together
  2. Applying an identity function.

This expresses itself in many different places:

  • Function composition and the identity function
  • Semigroups and monoids
  • Applicative functors

Another place we see this is the Alternative class, where composition fuses together collections and the identity element is the empty collection. Event is a great candidate for an Alternative instance, so let's see what alt-ing events looks like!


Alt

Alt is a way to combine two or more event streams, or to “mux” them in streaming lingo. We'll start with an example of simple muxing, followed by multiplexed muxing and finishing with some performance considerations to keep in mind.

Alt as a muxer

In the example below, we’ll use alt, aka <|>, and whose definition is Event a -> Event a -> Event a to mux together two streams controlling the background of a div. The result will be a rave in your browser… sort of…

View on GithubVITE_START=AltAsAMuxer pnpm example
module Examples.AltAsAMuxer where

import Prelude
import Deku.Toplevel (runInBody)

import Control.Alt ((<|>))
import Deku.DOM.Attributes as DA
import Deku.Control (text_)
import Deku.DOM as D
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import FRP.Event.Time (interval)
import FRP.Poll (sham)
import Deku.Toplevel (runInBody)

main :: Effect Unit
main = do
  i0 <- interval 200
  i1 <- interval 165
  void $ runInBody do
    D.div
      [ DA.klass
          ( sham
              ( (i0.event $> "bg-pink-300")
                  <|> (i1.event $> "bg-green-300")
              )
          )
      ]
      [ text_ "Par-tay!" ]
Par-tay!

The alternating of the two streams creates the strobe effect.

Order of alting

The <|> operator alwaysmuxes from right to left for simultaneous events. For example, pure 0 <|> pure 1 will emit 0 and then 1.

The oneOf function

Alting lots of events can get tedios. Too many tie fighters! To make life easier, there's the oneOf function that will alt a bunch of events. Surveys consistently reveal that this is the technique most often used when coders create a text-only version of Harder, Better, Faster, Stronger in the browser.

View on GithubVITE_START=TheOneOfFunction pnpm example
module Examples.TheOneOfFunction where

import Prelude
import Deku.Toplevel (runInBody)

import Data.Array (zipWith)
import Data.Foldable (oneOf, sequence_)
import Deku.Control (text)
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import FRP.Event.Time (interval)
import FRP.Poll (sham)
import Deku.Toplevel (runInBody)

main :: Effect Unit
main = do
  head <- interval 87
  shoulders <- interval 341
  knees <- interval 985
  toes <- interval 1401
  let hskt = [ head, shoulders, knees, toes ]
  void $ runInBody do
    text $ oneOf
      ( zipWith (\e s -> sham $ e.event $> s) hskt
          [ "head", "shoulders", "knees", "toes" ]
      )

Dredging

The functiondredge is like the functionsham except that, instead of taking an Event and returning a Poll, it takes an Event constructor of type Event a -> Event b and returns a Poll constructor of typePoll a -> Poll b.

Performance considerations

Because <|> adds an extra thunk to your stack, too many of them can degrade performance. To aleviate this, you can use the function merge from hyrule, which has the same signature as oneOf but without the overhead of Alt.


Plus

The Plus typeclass allows us to define an empty element for a collection. We've already sort-of seen this, as we need an empty element for oneOf to work on an empty array. Let's build some intuition about what the empty Event is and how it is lawful.

Emptiness

The empty event is none other than:

makeLemmingEvent \_ _ -> pure (pure unit)

It just ignores its inputs entirey and returns a no-op unsubscribe.

Lawfully alternative

Let's verify that alt and empty fulfill the laws for Alternative, namely:

  • Distributivity: (f <|> g) <*> x == (f <*> x) <|> (g <*> x)
  • Annihilation: empty <*> f = empty

The Distributivity condition is fulfilled by thinking of alt as the conjunction or, meaning that it's one event or the other. The function f or g applied to x is the same as the function f applied to x or the function g applied to x. Annihalation is true because, as we learned in the Applicatives section, the composite event only fires after both sides are fired. As one side never fires, the whole thing never fires, so it is empty.