What is a Behavior?

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:

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.

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.

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.

(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:

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.