Functional reactive programming
Alternatives
How events function in an alternative universe.
In functional programming, we tend to be obsessed with two classes of operations.
- Composing stuff together
- 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…
VITE_START=AltAsAMuxer pnpm examplemodule 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
<|> 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.
VITE_START=TheOneOfFunction pnpm examplemodule 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.


