Cat Racing, Or: How to Handle Requests Life Cycle in Elm, Part III — Representing Request Life Cycle with Types.

In this third and final part of my Cat Racing HTTP Requests tutorial we’ll finally see how we can use Elm’s advanced type system to represent the state of things in our application.

Previously…

So far, we had created a simple application that has only one page in which there’s a form that can be submitted to the server. Have a look at the code.

Goal

Our goal for this part is simple: To represent the state of the form. But what does it mean, exactly? Elm programming is filled with Kōan like questions, such as “What is a State?” and “What is a state of form?”.

So what is a the state of a form? There is no one right answer, and we can consider several, for example:

  • It is the current data being represented in the form.
  • It is whether the inputted data is valid or not.
  • It is whether the form had been sent to server or not.
  • It is whether the form had been sent, and whether the server had replied.

I could have probably think on several more, but we can stop here. For our purpose, we’ll just choose something as our state. We’ll define:

The state of our form is the following: 
1. Whether the user had inputted anything into the form, or modified the data received from the server. 
2. Whether the form had been sent to the server or not. 
3. Whether the server had accepted the request, or some error had occurred.

Now we can think about how to represent these different states. In Elm, we can represent everything with types, and we had seen that we can create a type with several different constructors, of values. Lets construct a FormState type:

Organizing Our Model

We need to decide where to use this new state type. Since our model is very simple, there are only two options:

  1. Adding it to the Model type
  2. Adding it to the Cat type

Lets consider the first one. If we add it to the Model type, we are now limiting ourselves to just one form, one request in a time. So it doesn’t look like a good fit.

But if we add it to the Cat type, we can have as many Cat records as we want, each with its own state. A much better fit.

On the other hand, is a Cat object inherently stateful? Consider this:

type alias Cat =
{ name : String
, id : Maybe String
, score : Maybe Int
, state : FormState
}

Doesn’t the state field seems a little out of place to you? name, id and score are all fields that describe a Cat, both at the front-end and at the back-end. state is only used for some book keeping in the front-end.

We should find some way to decouple the two concepts: the data representing a cat, and the data representing the state of the form, related to some cat.

Parametric Types

We had already seen parametric types in Elm. We seen them when we first encountered the Maybe type, defined as

type Maybe a = Just a
| Nothing

Or the Result type, that has two parameters. By now, we should know that this a means “any type”, so we can define Maybe String or Maybe Cat. We used that for some book-keeping about the “inner” type, for example, in Maybe Cat we know that we either have a Cat, or we have Nothing.

Now we’re going to define our own parametric type for handling state. It is in fact very simple, we’re just going to add a type parameter to our existing definition of a state, and rename it slightly.

We can now redefine our model to make use of our new Stateful type:

type alias Model =
{ cat : Stateful Cat }

And of course, this Stateful type is agile enough to be used in various other places, for example, maybe we have several cats:

type alias Model = { cats : List (Stateful Cat) }

Or maybe just the Cat being edited should be stateful, and the rest not:

type alias Model = 
{ cats : List Cat
, editing: Stateful Cat
}

Or maybe we have both a Cat and a Jockey?

type alias Model =
{ cat : Stateful Cat
, jockey: Stateful Jockey
}

How Do We Use It?

We had decided to use our Stateful a type, and we should now update our code accordingly. But at first glance, it seems complicated. Should our update function know how to update the state? And there are all those parts that expect to handle a Cat, not a Stateful Cat, what should we do about them?

The answer is simple: write some functions to work with Stateful! First and foremost, we need to extract the Cat from the Stateful Cat. That’s easy, because no matter what the state of Stateful a it is, it always has an inner value a.

We need to pattern match on every possible constructor of Stateful but other than that, getting the underlying value is pretty straight forward.

What about updating it? Again, we should create some helper function for that. We’ll call it mapStateful:

This one is a bit more complicated. It takes two values, a function from a to a and a Stateful a. It will then apply the function on the inner a value and return the new Stateful. We can also see that this mapping is state-aware, for example, if the state was NotChanged then after we map over it, it will become Changed. If we’re Waiting or had failed, we do not allow mapping at all.

This pattern of creating a mapping function is very common. Consider for example List.map: It is defined as map : (a -> b) -> List a -> List b. This a -> b part is telling us the we may change the underlying type of the list, transforming a list of integers to a list of booleans, for example. But in our Stateful type we do not allow the underlying type to change.

Finally, we might want to change the state and the data based on some logic. A good example is when the server returns a response. Our code will now have to decide whether the request had been successful or not, and maybe use the old value in some way too.

Our updateState is very similar to our mapStateful function, but now the user has to supply a function that maps the inner value a to some new Stateful a type.

Putting Stateful Into Use

Equipped with our Stateful type and its functions, we are ready to update our update function.

Lets examine each branch separately, starting with ChangeName:

In ChangeName we used setName to rename our cat. The type of setName is String -> Cat -> Cat. For modifying a value inside a Stateful we need to supply mapStateful with a a -> a function as its first argument. setName newName is such a function, because in Elm functions can be partially applied.

In SaveChanges we need to update that we are now waiting for the server to respond. We can use updateState for that. The first argument to updateState has to be a a -> Stateful a function. Amazingly, the type constructor Waiting of Stateful is such a function!

In UpdateFailed we need to do some mix & matching. The server had returned an error, and we want to save the value before submitting. We will then construct a completely new Stateful Cat value with the type constructor Failed which expects an Http.Error and a Cat. We’ll get the Cat value from the current model.cat by using getValue.

Finally, in UpdateSucceeded we don’t care about the old value of model.cat. So we’ll just construct a new one with the result returned from the server.

Summary

Our three parts journey in handling requests and their life cycle in Elm comes now to a conclusion. We had seen how to create a simple form program in Elm in the first part; We had seen how to communicate with the server in the second part; And today we seen how we can use types to better structure our program.

I wish you a smooth sail in seas of Elm. It is wonderful language that had changed the way I view front-end programming completely.

Until next time!

Like what you read? Give Yoav Luft a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.