Dynamics

Posted on September 25, 2017

Previously we had a look at Behaviors, which finished our coverage of the two main FRP types.

Now we’ll have a look at an additional tool that reflex gives us - Dynamics.

What is a Dynamic?

A Dynamic is the combination of an Event and a Behavior.

A Dynamic in reflex looks like this:

and we can think of it as being a pair of an Event and a Behavior:

The Behavior carries some state around, and the Event fires with the value of the new state whenever the state is changed. This is useful for reasons of efficiency. Behaviors are pull-based, which means we need to poll them for changes, so combining a Behavior with an Event that fires when it updates means that we can write code that reacts to changes in state at the time when the changes occur.

This is very useful when we want to update a piece of the DOM. A Dynamic t Text can be passed from where it was created, through the application, down to a DOM node that needs to display some changing text. The Behavior t Text takes care of tracking the current state of the text to display, and reflex-dom can set up some code to replace the text in the DOM node whenever the Event signals that there is a change.

The reflex and reflex-dom libraries aren’t prescriptive about how you structure your application, but the common advice is to pass Dynamics as far down as possible into the code that generates DOM. If you follow that advice then you can arrange things so that your code is doing the same changes to the DOM as the various virtual DOM libraries would, but skipping the need to diff and patch the various DOM trees.

There is some reference to building an Event and Behavior simultaneously in the reactive-banana documentation, and it appears in one of the examples, and so I suspect that the idea behind the Dynamic is probably lurking in the background or in the folklore of other Event-and-Behavior FRP systems.

Working with Dynamics

We can get hold of the Event if we need it:

and we can get hold of the Behavior:

We also still have the handy typeclass instances that were available for Behaviors:

We have to construct them directly, rather than combining existing Events and Behaviors, so that the two components stay synchronized.

There is an equivalent of hold but for Dynamics:

and there is a variant that lets us fold a function through a series of Event firings:

which we’ll often be using with the function application operator:

In order to unify:

and:

we need c ~ d.

We then have:

being unified with:

and so a ~ (c -> c) and b ~ c.

After the dust settles, we have:

The MonadFix constraint is being used because foldDyn uses value recursion internally.

An example of using Dynamics

We’re going to build a simple counter.

To do that we’re going to build a up a Dynamic t Int, which will start at 0 and will be transformed when various Events fire.

We can use foldDyn to get started:

We’ll add an Event corresponding to the pressing of an “Add” button:

When that button fires, we’ll need to use that Event to supply a function of type Int -> Int:

and fortunately we have just the thing:

It is really common in FRP systems like these to deal with Events which have functions for values, which is worth remembering when you’re starting out and trying to solve problems like these for the first time. It can seem really strange at first but you will get comfortable with it if you practice for a while and try to remember that functions are values too.

Let’s modify our counter so that we can reset it.

We’ll start with what we had before:

and add an Event that will fire when “Clear” is pressed:

We are planning on creating some buttons to produce these Events, and so the Events won’t happen simultaneously. However, since we have separated out the logic from the controls, and are taking Events as inputs, we can’t guarantee that the Events will be happening in different frames.

We are working with Event t (Int -> Int), so we’ll combine the Events using mergeWith and function composition:

Now we just need to put the Event in place:

and supply a suitable function:

So far, so good.

Bringing RecursiveDo into the picture

Behaviors have values at all points in time, and Events only have values at certain instants in time. This means that all Behaviors have a value before any Events in the application fire, and so any Event can be used to sample from a Behavior.

We can use one firing of a particular Event to build up a Behavior and a later firing of the same Event to sample a Behavior. Going further than this, we can use one firing of a particular Event to both build up and sample from a Behavior. The reason this is fine is that hold updates the Behavior in the next frame rather than the current frame.

The result of this is that we are able to have loops in the graph of our FRP network. This is fine, and is often very useful, but we need a language extension to be able to input them into Haskell in a convenient manner.

Imagine that we had an application that contained a counter that we wrote earlier, and that we wanted to specify an upper limit for the value of the counter.

We could add something like this to the settings page for the application:

and then plumb the results into a revised form of our counter.

We would start with our old counter:

and then pass in the limit:

We can then check if we are within the limit:

and use that to create a version of eAdd which only fires if we are within the bounds of the limit:

Now all we have to do is replace the use of eAdd in the foldDyn with eAddOK.

That is going to look a little weird and fail to compile, due to the cyclic dependency it introduces:

but we can resolve this by adding the RecursiveDo language pragma and replacing the the do keyword with mdo:

If you play around with this:

you’ll see that it works, and that it is linked to the limit widget above.

If we hadn’t needed the MonadFix constraint in order to use foldDyn, we would need it now in order to use mdo.

We could take this further, and create a data type to manage the settings:

We can pull the settings apart for use in our counter:

and we can build the settings up from the individual pieces on our hypothetical settings page:

We’re still playing with basic examples, but hopefully these examples plus a bit of imagination are enough to help see the usefulness of building up, passing around and pulling apart first-class values for state management.

Removing extraneous updates

There is a small trap here when we start decomposing our Dynamics.

Imagine that we constructed a Dynamic that keeps track of a pair of Colours:

and in some other part of our application we would like to break that pair apart into a pair of Dynamics:

This probably won’t do what we want.

Imagine that the first Event passed to dynPair never fires. Whenever the second Event passed to dynPair fires, the output Dynamic will update. If that output is passed through splitPair we’ll have a pair of Dynamics that are updating, although the first of these will have an Event firing that doesn’t correspond to a change in state.

This is particularly problematic if we’re trying to minimize the number of times we have to update a DOM tree.

We can see that in action here if we click back and forth between “Red” and “Blue” for one set of inputs:

We can solve this by using holdUniqDyn:

to create a new version of splitPair:

Which does what we want with respect to minimizing unnecessary updates:

This highlights an additional issue with our implementation of dynPair - if we clicked the same button over and over, we’d trigger updates even when the state wasn’t changing. If this was important to us we could address this by using holdUniqDyn within dynPair itself.

Playing along at home

If you want to test out your understanding of Dynamics, there are Dynamic-themed exercises here. These exercises build up incrementally as the series progresses, so it would probably best to start the exercises beginning at the start of the series.

Next up

We now have all the pieces that we need to build an FRP network.

In the next post we’ll start looking at how to create DOM elements using reflex-dom.

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.