Reflex exercises: Dynamics

Posted on September 26, 2017

If you haven’t done it already, I’d work through the Behavior exercises, since these build from there.

None of these exercises will change the overall behavior of our application. Instead they’ll expose more of the application that was previously being taken care of behind the scenes.

You’ll probably want to copy pieces of your solutions from exercise to exercise as they progress.

The first change

The first thing we’re going to do is to change the Behaviors into Dynamicss in the Inputs data type:

-- src/Ex05/Common.hs
data Inputs t =
  Inputs {
    idMoney    :: Dynamic t Money
  , idCarrot   :: Dynamic t Stock
  , idCelery   :: Dynamic t Stock
  , idCucumber :: Dynamic t Stock
  , ibSelected :: Dynamic t Text
  , ieBuy      :: Event t ()
  , ieRefund   :: Event t ()
  }

Update your solution to reflect this change.

You can play with this in a browser by running:

> nix-shell
nix-shell> ./ex05.sh

from the exercises directory and then visit http://localhost:9090 in your browser (although there is currently a bug in jsaddle-warp effecting Firefox).

It should update the browser every time that you save your code while it is in a compilable state.

What it should look like

If you succeed, you should get something that behaves like this:

which should be behaving the same way as everything else on this page.

Hints

You are given Dynamics and you should already have code that works with Behaviors. You’ve got this.

There is a solution in src/Ex05/Solution.hs, which you should look at when you’re done or if you give up.

The second change

The next change is to start producing some of the Dynamics that are used in display.

The Outputs data type now includes two new Dynamics:

-- src/Ex06/Common.hs
data Outputs t =
  Outputs {
    oeVend   :: Event t Text
  , oeSpend  :: Event t Money
  , oeChange :: Event t Money
  , oeError  :: Event t Error
  , odChange :: Dynamic t Money
  , odVend   :: Dynamic t Text
  }

The odChange Dynamic will be used to display the change issued from the machine, which will be cleared back to zero whenever anything else happens.

The odVend Dynamic will show the name of the product that is vended in the event of a successful sale, will show an error message if an error occurred, and will be cleared back to an empty string whenever anything else happens.

There is a helper function available to generate error messages:

-- src/Ex06/Common.hs
errorText :: Error -> Text

Update your solution to reflect this change.

As an optional extra exercise, you could write the code to create these Dynamics with new top-level functions.

If you do this: which style do you prefer? Lots of functions, or inlining everything into one big function?

You can play with this in a browser by running:

> nix-shell
nix-shell> ./ex06.sh

from the exercises directory.

What it should look like

If you succeed, you should get something that behaves like this:

which should be behaving the same way as everything else on this page.

Hints

Both of the Dynamics that you need to produce will hold onto the last value that they’ve seen.

There is a solution in src/Ex06/Solution.hs, which you should look at when you’re done or if you give up.

The third change

The next change is to add the code that manages the amount of money in the machine.

We add in an Event which fires when the customer adds a dollar to the machine:

data Inputs t =
  Inputs {
    ibCarrot   :: Dynamic t Stock
  , ibCelery   :: Dynamic t Stock
  , ibCucumber :: Dynamic t Stock
  , ibSelected :: Dynamic t Text
  , ieAdd      :: Event t ()
  , ieBuy      :: Event t ()
  , ieRefund   :: Event t ()
  }

and we have moved the Dynamic tracking the amount of money in the machine from the Inputs to the Outputs:

data Outputs t =
  Outputs {
    oeVend   :: Event t Text
  , oeSpend  :: Event t Money
  , oeChange :: Event t Money
  , oeError  :: Event t Error
  , odMoney  :: Dynamic t Money
  , odChange :: Dynamic t Money
  , odVend   :: Dynamic t Text
  }

Update your solution to reflect this change.

Separate the code for managing the money out into it’s own function. Make sure that the amount of money can never go negative.

Separating out the function is only one step along the separation-of-concerns axis. We could go further and create a MoneyInputs type to gather the Events that are used as inputs to the function.

Give that a go if it sounds interesting to you. What do you think of it? Is being that explicit worth the extra typing for you, or would you rather just solve problems of this size inline and move on?

You can play with this in a browser by running:

> nix-shell
nix-shell> ./ex07.sh

from the exercises directory.

What it should look like

If you succeed, you should get something that behaves like this:

which should be behaving the same way as everything else on this page.

Hints

There are three Events which will effect the amount of money in the machine. Make sure that you deal with them all.

There is a solution in src/Ex07/Solution.hs, which you should look at when you’re done or if you give up.

The fourth change

You now need to fill out the body of the function which creates the Dynamics for the items in stock:

-- src/Ex08/Exercise.hs
mkStock ::
  ( Reflex t
  , MonadHold t m
  , MonadFix m
  ) =>
  Int ->
  Product ->
  Event t Text ->
  m (Dynamic t Stock)

The function takes an initial stock level, the product to stock, and an Event which fires with the name of the product when a product is vended. Make sure that you will never end up with a negative quantity of stock.

In case a reminder is helpful, the Product and Stock data types look like this:

-- src/Ex08/Common.hs
data Product =
  Product {
    pName :: Text
  , pCost :: Money
  }

data Stock =
  Stock {
    sProduct  :: Product
  , sQuantity :: Int
  }

You can play with this in a browser by running:

> nix-shell
nix-shell> ./ex08.sh

from the exercises directory.

What it should look like

If you succeed, you should get something that behaves like this:

which should be behaving the same way as everything else on this page.

Hints

You should have all of the pieces you need for this by now, although it might take more Event-wrangling than the previous exercises on this page.

There is a solution in src/Ex08/Solution.hs, which you should look at when you’re done or if you give up.

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.