Why Would I Ever Elm?

This is the blog post version of a talk I gave at NottsJS.

I’ve now been writing Elm for over a year, including in production at work. My only real exposure to front-end frameworks before that was AngularJS which now I think about it is kind of crazy. Before this I’d try to avoid writing too much JavaScript. My attitude has now changed: it is fine deploying complex web applications, but I’d still prefer not to write them in JavaScript.

Throughout this I want to list what I think typed functional programming provides us when writing (but not limited to) front-end applications, with Elm as an example of a language that has those characteristics.

Attributes of typed functional programming

I’m going to list what I think of when trying to define the attributes typed functional programming languages give you over any others. This is not a standard list, hopefully it is not too controversial.

Considering JavaScript we can say that we have 1st-class functions. We could also use a package like immutable-js to get immutable data. Union types can be faked easy enough, but it is diffuclt to enforce correct usage across your codebase in an automatic way.

I’m going to go through each of these and show how it applies to making a typical web application.

Functions make views safe

My main issue with AngularJS (and other frameworks) is writing correct views. They are just HTML in a string, or required from a separate file, or some other random syntax. There is no way of knowing whether any of the values you reference actually exist, or whether any of the handlers for events have been defined.

This is true in React.

class HelloMessage extends React.Component {
  render() {
    return (
      <div>
        Hello {this.props.name}
      </div>
    );
  }
}

I want to know before I open my web browser that:

  1. All of the HTML elements and attributes are spelt correctly
  2. All of the values referenced exist

In the HelloMessage React component I could easily mispell <div>, or I could not pass a name in the properties where it is used. If I did make a mistake like that something my build would still work and I’d have to open my browser to check it.

Elm solves these issues by making everything a typed function.

helloMessage : { name : String } -> Html msg
helloMessage model =
  Html.div [] [ Html.text ("Hello " ++ model.name) ]

There are functions for each of the HTML elements and attributes, my view is a function that has a type based on the values I reference, and the compiler will fail my build if something doesn’t match. As a bonus I don’t have to write HTML!

name : Model -> Html Msg
name model =
  Html.div
    [ Attr.class "field"
    , Attr.classList [("valid", List.isEmpty model.name.errors)]
    ]
    [ Html.input [ Attr.class "input", Events.onInput SetName ] []
    , if List.isEmpty model.name.errors then
        Html.text ""
      else
        Html.div [ Attr.class "help" ]
          (List.map Html.text model.name.errors)
    ]

Since views are just functions, and not some random other syntax mixed with JavaScript, I can easily write complex views without having to remember how to do an if, or apply a function over a list.

Union types for representing states

When writing our complex web applications with pages, users, forms, and decisions, we probably know that we could represent this as a state machine. I’d bet at some point when writing a feature someone wrote a flow chart for part of it too, and then when it came to implement it how did you do it? A few booleans works for most situations, right?

Let’s go through the stages of requesting a user’s new posts. I can think of four states this request could be in:

  1. The request hasn’t started yet
  2. The request is in progress, we may want to display something to show that it is loading
  3. The request succeeded, we can display the new posts
  4. The request failed, we might want to display an error or retry the request

Here is the “simple” boolean approach to this:

class PostsRequest {
  constructor(userId) {
    this.posts = []
    this.isDone = false
    this.isLoading = false
    this.isSuccess = false
  }

  get() {
    // do the request and keep all of the booleans correct
  }
}

Now in our views we can check isLoading to show/hide a spinner, or isSuccess for an error message, but we’d also have to check isDone or we might show an error when the request hasn’t even started. We are beginning to see the complexity of this and it is due to the fact that these 3 booleans can represent 8 different states, double the number we expect to exist. We could use a number, like an enum, which is how XMLHttpRequest.readyState works. Then all that you need to do is remember what 2 means.

In Elm things are much simpler.

type PostsRequest
  = Initial
  | Loading
  | Success (List Post)
  | Failure Error

We can create a Union type with four different “tags” (or “constructors”/whatever you want to call them). The really cool thing about this is we can associate data with certain tags, so here we have a list of posts when the request is a success, and an error when it fails.

Also when writing our view we will be forced to handle all of the possible cases by the compiler,

view : Model -> Html msg
view model =
  case model.request of
    Initial ->
      Html.button [ Html.Events.onClick FetchPosts ]
        [ Html.text "Get New Posts" ]

    Loading ->
      Html.div [] [ spinner ]

    Success posts ->
      Html.ul [] (List.map viewPost posts)

    Failure error ->
      Html.div [] [ errorToHumanFriendlyDescription error ]

This particular example is of such a common pattern that a package exists to do this.

We could also consider how to deal with form validation using this pattern. In other frameworks we may have something which we can query to see if it has been touched, or is valid. Here is an example from the Angular guide.

<input id="name" name="name" class="form-control"
       required minlength="4" appForbiddenName="bob"
       [(ngModel)]="hero.name" #name="ngModel" >

<div *ngIf="name.invalid && (name.dirty || name.touched)"
     class="alert alert-danger">

  <div *ngIf="name.errors.required">
    Name is required.
  </div>
  <div *ngIf="name.errors.minlength">
    Name must be at least 4 characters long.
  </div>
  <div *ngIf="name.errors.forbiddenName">
    Name cannot be Bob.
  </div>

</div>

If we pretend that we have something similar in Elm we can work backwards to find the type required.

nameInput : Html Msg
nameInput =
  Html.input
    [ Attr.id "name"
    , Attr.name "name"
    , Attr.class "form-control"
    , Attr.required True
    , Events.onInput NameSet
    ]

nameErrors : Field -> Html msg
nameErrors field =
  case field of
    Initial ->
      Html.text ""

    Valid value ->
      Html.text ""

    Invalid errors ->
      Html.div [ Attr.class "alert alert-danger" ]
        (List.map displayNameError errors)

displayNameError : Error -> Html msg
displayNameError error =
  case error of
    Required ->
      Html.text "Name is required"

    MinLength min ->
      Html.text ("Name must be at least " ++ toString min ++ " characters long.")

    Forbidden existingName ->
      Html.text ("Name cannot be " ++ existing ++ ".")

Given this we can work out what we need Field to be.

type Error = Required | MinLength Int | Forbidden String

type Field = Initial | Valid String | Invalid (List Errors)

This is only part of the solution, we’d need some function to do the actual validation too. And if we did start using this the definition of Error is probably too specific, it works well for this single input field but not for others. Luckily there are plenty of packages for validation in Elm that could be used instead of defining it all ourselves. (You can search http://package.elm-lang.org/ for them, I like elm-validation).

Immutability makes change easy to manage

For a few years now there has been a move towards thinking about “state management” with packages like Redux or MobX. This is good as trying to keep track of where a value is modifed is difficult. Centralising this in to one place helps and also enables you to easily layer other things like logging on top.

Here is an example showing what this general pattern looks like.

const initialState = { count: 0 }

const next = (message, prevState) => {
  switch (message) {
    'INC':
      return { count: prevState.count + 1 }
    'DEC':
      return { count: prevState.count - 1 }
  }

  throw new Error(`next doesn't know how to ${message}`)
}

const state0 = initialState          //=> { count: 0 }
const state1 = next('INC', state0)   //=> { count: 1 }
const state2 = next('INC', state1)   //=> { count: 2 }
const state3 = next('DEC', state2)   //=> { count: 1 }
const state4 = next('INC', state3)   //=> { count: 2 }
// ...and so on

We have an initial state; a function that takes a message representing something to do or that has happened, and the previous state, and that returns a new state; and then below we just apply that function over the previous states.

This is the same idea as is used in Elm. But it is necessary in Elm because there are no “variables”. We need a way to modify the immutable data that makes up our application’s model of the world, a function that takes a message and the previous value and then gives us the new one is perfect. The pattern is referred to as The Elm Architecture in the Elm world. Here is the previous example in Elm, for example:

initial : Int
initial = 0

type Msg = Increment | Decrement

update : Msg -> Int -> Int
update msg prevCount =
  case msg of
    Increment ->
      prevCount + 1

    Decrement ->
      prevCount - 1

As before I define an initial state, here called initial, and a function to work out what is next, here update. We also get the benefit of using a union type for the message, so the compiler will enforce that only these two things will ever happen. I don’t need a default case that throws an error!

Closing

I think that these ideas are powerful, and so do others, that’s why there are attempts to bring them to JavaScript in packages we can easily include in existing applications. I want to see more of that, in particular a good way of doing union types with exhaustion checks.