Introduction to Elm programming language for React developers

Introduction to Elm programming language for React developers

A delightful language for reliable web applications.

Taking a short break from the Let's code a Virtual DOM! series, I decided to write an article on the technology that has inspired me to create my own virtual DOM in the first place.

That technology is a programming language called Elm. It can be used to build highly reactive front-end applications.

What makes it different from Javascript or Typescript?

Why should you care?

It's a purely functional statically typed programming language - a dialect of haskell.

If at this point, you are a bit sceptical about it - that's fine. Functional languages tend to be complex and hard to learn. But Elm is different.

Its simplicity makes it easy to learn - you are not expected to know maths or anything from category theory upfront. Of course, there will be some new things to learn (and probably unlearn too).

It uses a virtual DOM and redux-like state management to build reactive UIs (Redux was even inspired by Elm). So if you are a React developer you'll feel right at home!

Elm is simple and logical. Its compiler will guide you along the way - almost like a pair-programming buddy. All that makes it a perfect language to learn functional programming.

Why else should you bother to give Elm a try?

No Runtime Exceptions

How many times have you encountered the following errors in your Javascript applications: TypeError: undefined is not a function, TypeError: Cannot read property 'value' of null?

Elm is advertising that there are "No Runtime Exceptions" in applications built with it. It's because the compiler will catch all those errors in the build time. It can guarantee that if the code compiles, it will not crash.

Excellent compiler

In Elm, the compiler is your best friend. On the top of the "no runtime exceptions" promise, it's known of well thought and helpful error messages.

It will guide you by hand, and will even offer some suggestions as to how to fix the error!

Zrzut ekranu 2022-05-10 o 23.18.22.png

Enforced app structure

In React there are a million ways to do the same thing. Let's think about state management. Redux, Flux, MobX, Recoil and other libraries are used to do the same thing in a slightly different manner. If you happen to change a project, and it uses the library you don't know it means that you need to waste your time learning the new technology again.

In Elm, all applications look the same. All follow the same pattern. If you know how to work with one Elm app, you will quickly be able to switch between different applications without having to worry about re-learning everything from scratch.

Zrzut ekranu 2022-05-10 o 23.15.38.png

Great performance

Elm is fast. As we've mentioned before, it uses a custom implementation of a virtual DOM, that has been designed for speed and simplicity. Elm compiles to Javascript but produces small assets, which can boost the performance of your app.

Zrzut ekranu 2022-05-10 o 23.16.15.png

Show me the code!

If all of that convinces you to give Elm a try, it's high time to look at some code.

The easiest way to start working with Elm is to use an Ellie editor.

Ellie

Feel free to open this link in a new tab and follow along.

Imports

Imports, like in many languages are placed on the top of the file. In Elm, each file is a module and needs to have a unique name. It's set in the first line of the file, along with a list of exposed (or exported in JS term) functions.

module Main exposing (main)

Type declarations

In Elm we are able to create custom types like this:

type alias Model =
    { count : Int }

This is an equivalent of Typescript's:

type Model = { count: number }

Note the alias keyword. It's very important here, as Elm also allows us to create custom types that will be treated as values. The syntax for them can be seen below:

type Msg
    = Increment
    | Decrement

Declaring functions

In Elm, everything is a function.

Let's take for example initialModel:

initialModel =
    { count = 0 }

Even though it looks like a variable assignment, in reality, it's a function. In Typescript it would look like this:

const initialModel = () => ({ count: 0 })

If the function takes any arguments, they're placed before the = sign.

update msg model = 
  -- ...

| -- is a comment in elm

Type annotations

In the application generated by Ellie, over each function you can find a Type annotation for that function. They're optional but highly encouraged.

update : Msg -> Model -> Model
update msg model =
  -- ...

It can be a little confusing at first, but Msg -> Model -> Model means that the first argument is of type Msg, the second is of type Model and the function returns a Model type.

It's like that because of a partial application, a pattern found in many functional languages.

The application architecture

A basic Elm application consists of three functions: view, init and update.

main : Program () Model Msg
main =
    Browser.sandbox
        { init = initialModel
        , view = view
        , update = update
        }
  • init is the initial state of the model of our application.
  • view is the function that renders UI onto the screen
  • update is a function fired every time a Msg (message or action in Redux) is sent. It would be a reducer in React world.

The view function

The view function is a core of the app in Elm. It takes a Model as its argument and returns an Html Msg type.

view : Model -> Html Msg
view model =
    div []
        [ button [ onClick Increment ] [ text "+1" ]
        , div [] [ text <| String.fromInt model.count ]
        , button [ onClick Decrement ] [ text "-1" ]
        ]

What would this code look like in React?

const view = (model: Model) => (
  <div>
    <button onClick={() => dispatch('Increment')}>
      +1
    </button>
    <div>
      {String(model.count)}
    </div>
    <button onClick={() => dispatch('Decrement')}>
      -1
    </button>
  </div>
)

The result of this code is a simple counter.

counter

Now, what happens when someone clicks the button?

Dispatching events

Let's focus on the part when an event is dispatched. It's happening in the onClick attribute of a button.

button [ onClick Increment ] [ text "+1" ]

The onClick Increment part is dispatching the Increment action on a button click.

Actions are called Messages in Elm. Two messages - Increment and Decrement - were defined in the code above:

type Msg
    = Increment
    | Decrement

Those messages are sent to the update function automatically every time the onClick handler is fired. No need to manually dispatch them.

The update function

The state is managed in the update function. Every time a new message is sent, the update function updates the state.

update : Msg -> Model -> Model
update msg model =
    case msg of
        Increment ->
            { model | count = model.count + 1 }

        Decrement ->
            { model | count = model.count - 1 }

The update function takes two arguments, the first is a message sent, and the second is a previous model.

How could it be written in typescript?

const update = (msg: Msg, model: Model): Model => {
  switch (msg) {
    case 'Increment':
      return { ...model, count: model.count + 1 }

    case 'Decrement':
      return { ...model, count: model.count - 1 }

    default:
      return model
  }
}

It behaves exactly like a reducer function known from Redux or the useReducer hook.

What's nice, Elm will make sure that all possible outcomes are handled properly:

Let's add a new Message Reset:

type Msg
    = Increment
    | Decrement
    | Reset

Now if we run the code we'll get the following compiler error:

missing Reset

The compiler is telling us what we have missed!

Conclusion

I hope that this short introduction got you interested in learning Elm. It's a fantastic language that makes development much more interesting. Of course, it will not replace React or Javascript. But it can help you to develop good practices and learn new approaches to software development.

If you would like to play around with the Elm code, I encourage you to try the following task:

| Add a reset button to the page

This will involve editing the view function, updating the Msg type and adjusting the update function to reset the counter.

If you are interested in learning more about Elm, let me know. I will be happy to continue this topic on this blog.

Thank you for your time, and see you in the next post 😊

Did you find this article valuable?

Support Krzysztof Kałamarski by becoming a sponsor. Any amount is appreciated!