Behaviors

Posted on September 22, 2017

Previously we had a look at Events, which describe values occurring at particular instants in time. Now we’ll have a look at the other half of the core FRP types - Behaviors.

What is a Behavior?

A Behavior is something that has a value at all points in time.

A Behavior in reflex looks like this:

and we can think of it as being like a function from time to values:

We are working with a discrete-time FRP system and so this function is going to be piecewise-linear, with any transitions happening when Events occur. We are already using Events as our logical clock, and so we will use Events to build up our time-varying Behaviors and we will use Events to sample Behaviors.

We’re going to be using Behaviors for working with state in the system. They are a very different way of handling state than the State monad, but you get used to them pretty quickly once you start writing some code with them.

Getting started with Behaviors

Let us start by creating a Behavior.

We can do this with hold, which is a method from the MonadHold typeclass:

This takes an initial value and an Event as input. The Behavior uses the initial value until the first firing of the input Event. After that, the Behavior has the value the Event had at the last time it fired.

(We’ll look at precisely what we mean by “after” in a moment)

The use of the MonadHold typeclass constraint indicates that we’re doing something that will have an effect on the behavior of the FRP network in future frames. Under the hood, hold is modifying the FRP network in order to add some state, so we can think of the MonadHold context as a builder for an FRP network.

We could use this to keep hold of the last colour that was clicked on:

We now need a way to poke and prod at bColour so that we can see what is going on.

To get something we can display, we can use tag to sample from it using other Events:

Here we are taking a Behavior and an Event that we’re using just to get hold of reference points of time. We massage things so that the reference Event fires whenever we are interested in the value of the Behavior. The output Event will fire at the same time but with the value of the Behavior at the times the Event is firing.

If we are viewing Behavior t a as being equivalent to t -> a and Event t b as being equivalent to [(t, b)], then we have something like

As an aside: notice that the tag function doesn’t have a MonadHold typeclass constraint. Since Behaviors have values at all points of time this will behave the same way - but with different values - in every frame where the input Event fires. We don’t need to modify the FRP network, or to read from a frame-specific value, or anything like that, which means tag is a pure function.

We can use tag to query the value of the Behavior we built up before:

Let’s have a play around with this:

If we start clicking “Sample”, we’ll see that it starts out Blue as we would expect. If we then start clicking on combinations of “Red”, “Blue”, and “Sample”, we’ll see that it tracks whatever the user has clicked on.

The state doesn’t change until the frame after the firing of the Events in hold. We can see that by sampling from the Behavior when any of the buttons are pressed:

This is why frames are sometimes referred to as transactions. The values of the Behaviors are fixed for the duration of each frame, so that every Event in the frame has a consistent view of the state of the Behaviors. Any updates to the Behavior that are triggered during the Event processing for the frame are carried out after the Event processing has finished, so that the new state will be available from the start of the next frame. If it weren’t for this, we’d have to worry about the order in which Events were processed within a frame, and we’d be back to square one as far as state management was concerned.

On the topic of state management, there are some parallels with the State monad here. Inside of the monad, there is a value for the state at all points in time. Any modifications to the state, or reads from the state, happen at discrete points of time.

We’re a lot less explicit about time when working with the State monad, so it might take some squinting to see the parallels there. It’s also not as straightforward to compose computations working with State s1 m and State s2 m into a computation working with State (s1, s2) m, or to decompose a computation that works with State (s1, s2) m into computations that work with State s1 m and State s2 m.

It’s also worth mentioning the MonadSample class at this point, although it may not do what you expect and we won’t be using it for a little while yet.

It gives us the sample function:

This gives us the value of the Behavior in the current frame. We were able to use tag without a monadic context since it was reading the value of a Behavior in whichever frames the input Events were firing in. We need a monadic context for sample because it is reading from the Behavior at the point we are up to in the construction of the FRP network.

Let’s take a look:

(Although the MonadSample constraint is redundant here, since MonadSample is a superclass of MonadHold)

If we have a click around on this, we’ll see how different it is to what we had before:

At the point we called sample, bColour could only have the value Blue, and so that’s what we get as the output. Later on we’ll come across components with similarities to hold, that work with an initial pure value and an Event for tracking updates. If we want these things to synchronize with a Behavior, then sample is one way to grab an appropriate initial value.

There a few other functions for reading from Behaviors that are worth knowing about:

These are all easy to use and you can probably work out how to use them from their type signatures. We won’t be using them all that much.

A function that we will be using quite a bit is gate:

It can be pretty handy to create a Behavior somewhere in your application:
and pass that value around through your application until it is used somewhere else in your application to filter Events:

This is our first demonstration of Behaviors as first-class values for managing state We can pass in and out of functions, we can store them in data types, we can do what we like with them.

This is half of what I think makes Behaviors exciting as a method of state management.

Interesting instances

The other half comes from the typeclass instances.

There is a Functor instance:

that can be used to transform the Behavior at all points of time.

It behaves as you would probably expect:

The Applicative instances step things up a notch:

We can use pure to create a constant Behavior:

We can use <*> to compose Behaviors, which is almost as exciting as the fact that they are first-class values.

Our first brush with this idea is pretty simple:

but we can do so much more with this idea.

We’ll cover some of that as we go, but as a teaser it might be worth thinking about the things you could do with something like:

If you had a Behavior t Bool that was tracking the state of every checkbox on a settings page, you could use sequence to gather them all into a single Behavior.

In addition to this, reflex wants to be your friend and so provides all kinds of other instances that you might find a use for:

Aside: some handy helpers for the Applicative instance

There are a couple of operators that come from reactive-banana:

which we can use to restart the last example a little more succinctly:

These are similar to the <*>:

and <*:

operators from Applicative.

The usual pattern is to chain several Behaviors together with <*> and to end the chain with <@> or <@ and an Event.

If we have

we can combine the values from the Behavior at the time of the Event along with the value of the Event with:

or, if we don’t care about the value of the Event we can do:

This becomes pretty handy when you have a few Behaviors in flight at the same time.

Playing along at home

If you want to test out your understanding of Behaviors, there are Behavior-themed exercises here. The exercises continue from where the Event exercises left off, so it’s probably worth doing them first.

Next up

Events and Behaviors are the core types of FRP.

The reflex library also has a type that combines the two together. This is done for performance reasons, but also nicely encapsulates a pattern from FRP folklore.

In the next post we’ll look at the combination of the two - Dynamics.

We’re preparing educational materials about the reflex library, and using it to see what exciting things we can do with FRP.

> Dave Laing

Dave is a programmer working at the Queensland Functional Programming Lab.