Published

~7 minutes reading 🕑

Thu, 13 April 2017

← Back to articles

Getting started with Elm

Introduction

The past two weeks we have been traveling and meeting a bunch of developers.

It was a chance for us to tell them about Elm through talks at conferences and to present a short one-hour workshop.

You can find the slides of the two talks here:

However, this article is a summary of the take aways of this workshop for those who would like to know a bit more about Elm.

What is Elm and why should I be interested?

Elm is a Web application development platform, providing a programming language, a compiler, an architecture and tooling around. It focuses on making sure your app state and the HTML that reflects it are always in sync.

If you have been doing any frontend development during the last couple of years, you might have seen that the JavaScript ecosystem is a bit wild. Keeping up-to-date with the growing number of competing frameworks can take a lot of energy, not to mention the challenge in making sure your app works on all the available browser versions.

In addition, writing tests for the front-end can be tedious and supporting Flow or TypeScript, while tremendously useful, is yet another layer to setup, configure and maintain in your stack.

You can think of the Elm platform as the equivalent combined feature set of React, Redux, react-redux, Babel, eslint, Flow/Typescript and Webpack all bundled in a single homogeneous and consistent package!

Elm brings functional programming to your browser. The language is statically typed, so with Elm you won't see any runtime errors caused by inconsistent typing. Being functional and pure, Elm let you write code that is completely decoupled, which makes your code more reusable and easier to refactor.

How is Elm different?

Most of the time, using Elm, the two lines it takes to load your app from the HTML file is all the JavaScript you will write, as your Elm code will be compiled to JavaScript by the Elm compiler.

Most mistakes you make will be caught at compilation time, which means that you will barely ever encounter exceptions at runtime in the browser. Also that means less unit tests and defensive programming to write, which is always good.

Because there is a compilation step, Elm forces you to handle every possible case of your model state. It makes sure you covered them on the rendering side.

Also, the Elm compiler is super smart. It always does its best to help you with meaningful messages, provides guidance to fix your types and handle uncovered edge cases. It even finds your typos!

Cannot find variable `nane`

13|     hello ++ ", " ++ nane ++ "!"
                         ^^^^
Maybe you want one of the following?

    name
    tan
    Cmd.none
    Sub.none

Detected errors in 1 module.

The tooling is great. elm-live automatically updates the browser code to reflect your code update in realtime. It comes with a debugger that will show you the list of events and state of your application at a given point in time — you can even go back in time and replay previous events. elm-format will also format your code automatically to avoid fighting over coding styles.

Getting started

Installing Elm

First of all install elm and elm-format using npm — the nodejs package manager.

npm install -g elm elm-format

Looking for an IDE?

Have a look at Atom. Combined with the appropriate extension, it will run elm-format each time you save your file – which is super handy.

Elmjutsu is another useful package providing type-aware autocompletion among other niceties.

Take a moment to make sure your text editor is configured to work well with Elm files.

Starting your first project

No need for boilerplate here, you can just start by running elm-package install to install the core packages required to start creating your app:

~/tutorial$ elm-package install

Some new packages are needed. Here is the upgrade plan.

  Install:
    elm-lang/core 5.1.1
    elm-lang/html 2.0.0
    elm-lang/virtual-dom 2.0.4

Do you approve of this plan? [Y/n] Y
Starting downloads...

  ● elm-lang/html 2.0.0
  ● elm-lang/virtual-dom 2.0.4
  ● elm-lang/core 5.1.1

Packages configured successfully!

You now have an elm-package.json file in your project as well as a elm-stuff/ directory that contains libraries that elm-package installed. elm-package.json is to Elm projects what package.json is to nodejs ones.

~/tutorial$ tree -L2
├── elm-package.json
└── elm-stuff
    ├── exact-dependencies.json
    └── packages

2 directories, 2 files

Creating your first file

To get started you can simply create a new file named Main.elm with the following:

import Html
main = Html.text "Hello world"

Elm benefits from a full featured module system, with a broad ecosystem of external packages avaiable. Html is part of the core.

Playing with elm-format

If your editor is well configured with elm-format, you should see this as soon as you save it:

module Main exposing (..)

import Html


main =
    Html.text "Hello world!"

If not, you can run elm-format manually on your file:

~/tutorial$ elm-format --yes Main.elm

Opening your app in the browser

One way to run your app is to use elm-reactor, the core app browser provided by the platform:

~/tutorial$ elm-reactor
elm-reactor 0.18.0
Listening on http://localhost:8000

Then open http://localhost:8000/Main.elm in your favorite Web browser.

Learning about the Elm virtual DOM

Virtual DOM functions to generate HTML are in the Html module.

The Html module we used above to render some text also exposes many more functions for rendering HTML tags. You can import them all using:

import Html exposing (..)

Note that unlike with some other languages, the Elm compiler will complain if you try to import symbols already defined in the current module, which makes it actually useful and really enjoyable to use.

You can then use text directly for instance:

main = text "Hello world"

The Virtual DOM HTML nodes are functions named after standard HTML tags, and take two parameters:

  • A list of attributes
  • A list of children

If I want to create a div with a link it would look like this:

module Main exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)


main =
    div
        [ class "container" ]
        [ a
            [ href "http://www.servicedenuages.fr/" ]
            [ text "Blog" ]
        ]

We can also create a list of links in our div:

module Main exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)


main =
    div
        [ class "container" ]
        [ ul
            [ class "links" ]
            [ li
                []
                [ a
                    [ href "http://www.servicedenuages.fr/" ]
                    [ text "Blog" ]
                ]
            , li
                []
                [ a
                    [ href "http://www.elm-lang.org/" ]
                    [ text "Elm lang" ]
                ]
            ]
        ]

Adding some state

Now that you know how to render your page in HTML, let's see how to write a program that handles events.

The way Elm handles that is by having:

  • a Model, an Elm record: a bit like a JavaScript object with properties, that keep the state of the app
  • an update function that will handle all the app events and update the model state accordingly
  • a view function that will return the Virtual DOM that matches the state every time it's updated.

For those who know Redux, it has been heavily inspired by Elm. Basically update is a reducer.

The events and their parameters are defined in a Msg type, which is a bit like a enum that would take parameters.

In order to create our application that handle states, we can use the beginnerProgram from the Html package:

module Main exposing (..)

import Html exposing (..)


type Msg
    = NoOp


type alias Model =
    { name : String }


main =
    beginnerProgram { model = { name = "Rémy" }, view = view, update = update }


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


view : Model -> Html Msg
view model =
    text ("Hello " ++ model.name)

We can now handle an event and change the name when we click on it.

module Main exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)


type Msg
    = Switch


type alias Model =
    { name : String }


main =
    beginnerProgram { model = { name = "Rémy" }, view = view, update = update }


update : Msg -> Model -> Model
update msg model =
    case msg of
        Switch ->
            { model | name = "Séverine" }


view : Model -> Html Msg
view model =
    div []
        [ text "Hello "
        , a [ href "#", onClick Switch ] [ text model.name ]
        ]

You can refresh the page and try it.

If we want to switch back to Rémy when we click on Séverine we can add a if:

module Main exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)


type Msg
    = Switch


type alias Model =
    { name : String }


main =
    beginnerProgram { model = { name = "Rémy" }, view = view, update = update }


update : Msg -> Model -> Model
update msg model =
    case msg of
        Switch ->
            if model.name == "Rémy" then
                { model | name = "Séverine" }
            else
                { model | name = "Rémy" }


view : Model -> Html Msg
view model =
    div []
        [ text "Hello "
        , a [ href "#", onClick Switch ] [ text model.name ]
        ]

Enabling auto updates with elm-live

elm-reactor is good to get started but if you want hot-reloading of your app, you might want to setup elm-live.

To install it, you can use: npm install -g elm-live

Once installed, run:

$ elm-live Main.elm --open

If you have to use the debugger, you can use the --debug option:

$ elm-live Main.elm --open --debug

It will automatically generate an index.html file with the compiled JavaScript, and open it in your default Web browser.

You can use the --output option to save the JavaScript in its own file and load it in the HTML yourself.

First update the index.html to make it looks like:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Hello world</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>

  <body>
    <script src="elm.js"></script>
    <script>
    var app = Elm.Main.fullscreen();
    </script>
  </body>
</html>

Then you can run elm-live with the --output option:

$ elm-live Main.elm --open --debug --output elm.js

Now each time you will update your Elm code it will refresh the app in the browser.

Handling a second event

Let's add an input to let people choose who to great.

module Main exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)


type Msg
    = Switch
    | NewName String


type alias Model =
    { name : String }


main =
    beginnerProgram { model = { name = "Rémy" }, view = view, update = update }


update : Msg -> Model -> Model
update msg model =
    case msg of
        Switch ->
            if model.name == "Rémy" then
                { model | name = "Séverine" }
            else
                { model | name = "Rémy" }

        NewName new_name ->
            { model | name = new_name }


view : Model -> Html Msg
view model =
    div []
        [ text "Hello "
        , a [ href "#", onClick Switch ] [ text model.name ]
        , br [] []
        , input
            [ onInput NewName
            , value model.name
            ]
            []
        ]

The NewName event will be emitted with the content of the input each time we type in it.

Conclusion

That's about it. You now know more than you think about Elm. I hope you try it on your next project and enjoy Elm as much as we do.

If you want to learn more about it, don't hesitate to look at some of our projects or ask questions on the #kinto chan.

Revenir au début