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 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
<|>
operator alwaysmuxes from right to left for simultaneous events. For example, pure 0 <|> pure 1
will emit 0 and then 1.The oneOf function
Alt
ing 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 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.