An elm bush

My first Elm app

  • Kristofer Giltvedt Selbekk

I'm going to do something really different - I'm going to learn a new language in the open, and learn by trying to teach what I've learned step by step.

This might not be the greatest resource you'll find on the web, but I think you'll find it entertainingly honest, at least.

What is Elm?

The new language I'm going to learn is Elm. Elm is a functional statically typed language that just happens to compile down to JavaScript! It has "the world's most polite compiler", and a great community. Sounds like a great match to me.

Like React, it has a virtual DOM implementation to speed it up. It boasts about being really quick, and it's pretty small as well.

Speaking of React - Redux was actually based on what's known as the Elm architecture - and it looks pretty similar, too.

Since it's statically typed, Elm guarantees you no runtime errors, which is a bold thing to claim - but a great thing if it's true.

Why Elm and me?

The company I work for, Bekk, has a great Elm-community as well. The annual Oslo Elm Days conference sprung out from us, and we even employ a core Elm contributor!

Honestly though, since I've invested so much time and effort in mastering React and becoming sort of a half-known profile in that community, I probably wouldn't have wanted to invest without another reason to egg me on.

This February, I'm starting a new project with a client that uses Elm for a lot of their frontend apps. In other words - I better start learning. 😅

Let's get started!

To get started with a basic first application. To keep it REAL simple, I'll just create a regular old counter app. Because, remember, when you're learning a new framework and language at once, you need to start out simple.

First, I head over to the Elm website, and fetch the correct installer. Once that's set up, I create a new folder and run elm init in my terminal.

$ mkdir counter-app
$ cd counter-app
$ elm init

You're then met with a very nice greeting that makes you feel really appreciated. It makes sure you're agreeing to what it's about to do, and then it bootstraps a new project.

When it's done, you're left with an elm.json file, and an empty src folder.

The elm.json file looks very similar to the trusty old package.json file we all know.

{
    "type": "application",
    "source-directories": [
        "src"
    ],
    "elm-version": "0.19.1",
    "dependencies": {
        "direct": {
            "elm/browser": "1.0.2",
            "elm/core": "1.0.4",
            "elm/html": "1.0.0"
        },
        "indirect": {
            "elm/json": "1.1.3",
            "elm/time": "1.0.0",
            "elm/url": "1.0.0",
            "elm/virtual-dom": "1.0.2"
        }
    },
    "test-dependencies": {
        "direct": {},
        "indirect": {}
    }
}

Set up VSCode

I use Visual Studio Code for most of my coding, and luckily there's a great Elm plugin available. You'll find it here, or by searching the Extensions Marketplace for "Elm". Make sure you don't get the deprecated one!

Once that's installed, you need to install elm-format and elm-test via your preferred package manager as well.

$ yarn global add elm-format elm-test

The Elm Reactor

Elm comes with its own dev server. You can start it with running elm reactor in your project, and navigating to localhost:8000.

It's nice to see all of this being included in the elm CLI.

Let's start writing Elm!

With all of the setup out of the way, let's start writing some code!

We create a new file called Main.elm - and jump right in... but what should we write?

I started like any sane person does these days, and ran through the official tutorial. It's a great read, and if you're learning Elm yourself, I suggest you do the same.

Turns out, in Elm, there's three distinct parts of every "program" - the model, the view and the update function. The model is the state of your application, the view generates the HTML and the update function changes the state based on messages dispatched from the view. Sounds familiar?

This sounds a lot like Redux actually - and as it turns out, it basically is. Redux is based on the Elm architecture, so if you know Redux, this will look very familiar.

The model

Let's go through this first program step by step. First out - we're going to create the model. In our program, it's just the number to display on our counter. It looks like this:

type alias Model : Int

First, we define the type signature for our model. If you've ever used TypeScript, this will look pretty familiar. Our model is a simple integer.

Next, we set the initial value to 0:

init : Model
init =
    0

The update function

Now, let's write our "business logic" - the update function. It looks like a Redux reducer of sorts - it accepts the current state and a message (action), and it expects you to return the new updated state.

We start off by specifying all the actions that can happen in our app:

type Message = Increment | Decrement | Reset

So we can increment, decrement and reset the counter back to zero. Think of these types as actions you can dispatch to your reducer - or messages you can pass to update.

Next, the update function itself:

update : Message -> Model -> Model
update msg model =
    case msg of
        Increment ->
            model + 1

        Decrement ->
            model - 1

        Reset ->
            0

On the first like, we specify that the update function accepts a message and the current model value, and then it returns the new model value. Instead of accepting two arguments, the function is curried so that it accepts one at a time.

The next line might look a bit weird syntax wise, but it specifies a function update that accepts two arguments - msg and model. We then do something called "pattern matching", which can be understood as a fancy switch statement of sort. We check if the msg argument is Increment, Decrement or Reset, and in each of these cases, we return the updated value.

The syntax is a bit weird to me as a JavaScript developer, but I think I'll be able to warm up to it. Objectively speaking, it's clear, concise and easy to read and follow. Note that we also have implicit returns!

The view

The view is a function that accepts the model, and returns an Html value with the message type Msg as a parameterized type. If you're not familiar with parameterized types, you can think of them as type arguments (instead of value arguments).

The type signature looks like this:

view : Model -> (Html Msg)

and the function itself looks like this:

view model =
    div []
        [ h1 [] [ text "The counter" ]
        , p [] [ text ("Current count is " ++ String.fromInt model) ]
        , button [ onClick Decrement ] [ text "-" ]
        , button [ onClick Reset ] [ text "Reset" ]
        , button [ onClick Increment ] [ text "+" ]
        ]

Do you see what I mean when I say "kind of like React"? This looks very much like React.createElement with the element to create first, then a list of props, and finally a list of children.

Here, div, h1, p, button and text are Elm functions we import (we'll get back to the importing in a bit), which are just called one by one.

In the p function, we even do some string concatenation. Since Elm is a bit strict, we need to change our model from an int to a string to concatenate it.

Finally, we add the onClick event listener to all the buttons, and we provide it with the message we want to trigger.

If you wanted to write the same in React, you'd end up with something like this:

const View = (props) => (
  <div>
    <h1>The counter</h1>
    <p>Current count is {props.model}</p>
    <button onClick={props.decrement}>-</button>
    <button onClick={props.reset}>Reset</button>
    <button onClick={props.increment}>+</button>
  </div>
);

The imports

I didn't mention the imports to begin with, but just like in most other programming languages, you do need them.

The syntax is pretty nice, and you'll probably understand it better from looking at the imports we've used so far:

import Browser
import Html exposing (Html, button, div, h1, p, text)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)

Think of the exposing (..) stuff as named exports, and the other ones as default exports. You can rename imports with as if you want, just like in JavaScript.

The main

The last thing we need now is to kickstart our application somehow - and we do that with the main function! It's the same as the trusty ReactDOM.render(<App />, document.querySelector('.app'));

main =
    Browser.sandbox { init = init, update = update, view = view }

Refresh your browser, and you should see a completely unimpressive counter application that adds, subtracts and resets itself.

What have we learned?

Elm is a pretty cool language for creating modern web apps. It's a statically typed functional language with zero runtime errors and a very nice compiler.

Each Elm program has a model, a view and an update function - and kind of reminds me of Redux - for good reasons. There's a lot of new scary syntax, but once you try it out, it starts to make sense pretty quickly.

Creating a simple application was pretty straight forward - but what's next? Let's try to create something a bit more advanced next time.