Elm is a language that was mostly designed to handle web frontend development using functional programming and immutable languages. It was mainly inspired by Haskell, so you won’t have a hard time learning the language if you are familiar with Haskell and its strong typed, pure functional way. The philosophie of Elm is to find problems at compile time thanks to its strong typing and to have zero runtime errors.

In my professional software development career, I mostly did backend development. Whenever a web frontend was needed, I relied on frameworks like Primefaces or ZK so that the HTML, Javascript and CSS details were mostly taken care of. I never really got into frontend development, partly because I was reluctant to use Javascript as I think it is a language with a lot of unfortunate design choices where you often only notice at runtime when something goes wrong. In my pet projects, I tried out a bit Vue.js which I really like as it is not too invasive and very light weight, but you still have to use Javascript (or something like CoffeScript or Typescript). As a big Clojure fan, I also tried ClojureScript, but it never really felt as the correct solution for me. So I gave up again to find the language/framework to encourage me to improve my frontend development capabilities.

Then I read about Elm (in the excellent book Seven more languages in seven weeks). As I recently rediscovered Haskell Elm and its architecture felt like a good and fun way to do frontend development. The syntax came pretty naturally to me as it is very similar to Haskell. Let me show you how to write a simple web application in Elm and demonstrate how it looks like.

Install Elm

Let’s start with the basics. First you’ll have to install Elm (the easiest way is via npm or download from the officialsite). At the time of writing version 0.19 was the current version. There were a lot of incompatible changes in the previous versions of Elm, so if you read this post when a newer Elm version is available, some code may not work anymore. I’ll try to keep the code up to date…

Setup an empty elm project

  • Create a directory where you want to create your object
  • Open a shell (or similar console) and go into the directory
  • execute the command elm init
  • Respond ‘Y’ when prompted, if you want to create an elm.json file
  • This will create a src/ directory and a elm.json file and nothing more

elm.json contains the following code

{
    "type": "application",
    "source-directories": [
        "src"
    ],
    "elm-version": "0.19.0",
    "dependencies": {
        "direct": {
            "elm/browser": "1.0.1",
            "elm/core": "1.0.2",
            "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": {}
    }
}

As you can see, this will only setup a minimal project with dependencies to some important core packages. For our first example this is enough, but for more advance stuff there are better templates from which to start. I like it, because you really only get a simple project file from which to start without dependencies to tons of 3rd party libraries that you don’t know what they are good for.

The src/ directory is still empty, so we will create a new file named Main.elm. Let’s add following content:

module Hello exposing (..)

import Html exposing (Html,text)

main : Html.Html msg
main =
    text "Hello World"

After creating this file, we can just compile it by running elm make src/Main.elm. This will create file index.html, which will contain the generated javascript code together with the Elm runtime. If you open the file in the browser, you will see “Hello World” appearing.

Write an integer calculator in Elm

So now that we saw the obligatory “Hello World”, we can do something slightly more complex: a calculator! With this calculator, we will type in two numbers and it will return all result from the basic arithmetic operators +, -, * and /, so for example for number1 = 12 and number2 = 3 it will return

12 + 3 = 15
12 - 3 = 9
12 * 3 = 36
12 / 3 = 4

For this exercise we will use the bootstrapper create-elm-app which will create us the base structure of an Elm program. You need to install it with npm install -g create-elm-app. This bootstrapper creates us a few more things, like default css files, a service worker, a skeleton html file where your compile code will end up and most importantly a basic src/Main.elm.

After installing create-elm-app via npm, call create-elm-app elm-calculator. This will create the project and especially a Main.elm file with following content:

module Main exposing (..)

import Browser
import Html exposing (Html, text, div, h1, img)
import Html.Attributes exposing (src)


---- MODEL ----


type alias Model =
    {}


init : ( Model, Cmd Msg )
init =
    ( {}, Cmd.none )



---- UPDATE ----


type Msg
    = NoOp


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    ( model, Cmd.none )



---- VIEW ----


view : Model -> Html Msg
view model =
    div []
        [ img [ src "/logo.svg" ] []
        , h1 [] [ text "Your Elm App is working!" ]
        ]



---- PROGRAM ----


main : Program () Model Msg
main =
    Browser.element
        { view = view
        , init = \_ -> init
        , update = update
        , subscriptions = always Sub.none
        }

Now you can see the Elm architecture. You have the following sections:

  • Model: contains the data structure and the initial data for the dynamic parts of your page
  • Update: contains a function that describes changes to the model when messages are triggered. Messages can get triggered when some HTML events occur like for example the onClick event on a button
  • View: This is more or less the template of the program. It transforms the model into an HTML representation
  • Subscription: We don’t use subscriptions here, but subscriptions are another possibility to trigger messages. You can for example have subscriptions on time intervals or on events like keys being hit on the keyboard. So subscriptions can be used to implement timers or to handle keyboard input in games
  • Program: in the program section we put all the other parts together

Next call elm-app start. This will compile your Elm files and start a webserver that serves your webpage at localhost:3000. It will reload when we save a file from the project and display compilation errors in the browser when we have some. As one of Elm key features is its strong typing, it’s very probable that you will get compilation errors from time to time. Fortunately, the Elm creator took great care of writing meaningful error messages, so you will usually get a useful hint on how to fix them and not some hard-to-understand error messages. The good thing about this is that finding the problems at compile time will prevent you from finding them at runtime.

So now that we have our structure in place, we can start implementing our program. We’ll start with the model. Our model will contain the two numbers as well as the results of all arithmetic operations. Open the file src/Main.elm and change the type alias Model:

type alias Model =
    {operand1 : Int, operand2 : Int, sum : Int, product : Int, quotient : Int, difference : Int}

Model is now a type alias for a record with the properties we specified. The record type in elm is a dictionary with determined properties. The order of the properties is not important.

Next we need our initial values. We will use 0 as initial value for operand1 and operand2, the other properties are then derived from it. But wait: what is the initial value for quotient if operand2 is 0? As division by 0 is not allowed, we need to readjust our model slightly to indicate that quotient can have no value. For this we use the Maybe type. If you know Haskell, you’ll know this type, if not: Maybe a is a type class (a generic type) that has either no value (called Nothing) or a value of type a called Just, e.g. if a=Int a value of type Maybe Int could be Just 1234. So in the next code sample, I’ll adjust the model to use Maybe Int and will add the initial values for the model:

type alias Model =
    {operand1 : Int, operand2 : Int, sum : Int, product : Int, quotient : Maybe Int, difference : Int}

init : Model
init = {operand1 = 0, operand2 = 0, sum = 0, product = 0, quotient = Nothing, difference = 0}

Next we will describe the view (first without events):

view : Model -> Html Msg
view model =
    div []
        [ p [] [ text "First Operand: ", input [ type_ "numeric", value <| String.fromInt model.operand1 ] [] ]
        , p [] [ text "Second Operand: ", input [ type_ "numeric", value <| String.fromInt model.operand2 ] [] ]
        , p [] [ text "Sum: ", input [ type_ "numeric", value <| String.fromInt model.sum ] [] ]
        , p [] [ text "Product: ", input [ type_ "numeric", value <| String.fromInt model.product ] [] ]
        , p [ hidden <| Nothing == model.quotient] [ text "Quotient: ", input [ type_ "numeric", value <| Maybe.withDefault "NaN" <| Maybe.andThen (\x -> Just <| String.fromInt x) model.quotient ] [] ]
        ]

We only see the initial values of zero, we can change any value without anything happening.

So we first need to write what should happen, when we update the values of the two operands. We do this in the UPDATE section of our program. First we define two messages, one for each operand. Next in the update function, we define what should happen, when these events occur. As in both cases we recalculate all operations, I extracted this common part to a separate function called updateCalculatedValues:

---- UPDATE ----


type Msg
    = FirstOperandChanged String
    | SecondOperandChanged String


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        FirstOperandChanged value ->
            ( { model
                | operand1 =
                    if value == "" then
                        0

                    else
                        Maybe.withDefault model.operand1 <| String.toInt value
              }
                |> updateCalculatedValues
            , Cmd.none
            )

        SecondOperandChanged value ->
            ( { model
                | operand2 =
                    if value == "" then
                        0

                    else
                        Maybe.withDefault model.operand2 <| String.toInt value
              }
                |> updateCalculatedValues
            , Cmd.none
            )


updateCalculatedValues : Model -> Model
updateCalculatedValues model =
    { model
        | sum = model.operand1 + model.operand2
        , product = model.operand1 * model.operand2
        , quotient =
            if model.operand2 == 0 then
                Nothing

            else
                Just <| model.operand1 // model.operand2
    }

Now that we added those functions, we just need to make our view send these messages when the values of the input fields change. For this we use the onInput events. Finally, we also set the readonly attributes of all other fields to true, so that we cannot change them anymore.

view : Model -> Html Msg
view model =
    div []
        [ p [] [ text "First Operand: ", input [ type_ "numeric", value <| String.fromInt model.operand1, onInput FirstOperandChanged ] [] ]
        , p [] [ text "Second Operand: ", input [ type_ "numeric", value <| String.fromInt model.operand2, onInput SecondOperandChanged ] [] ]
        , p [] [ text "Sum: ", input [ type_ "numeric", readonly True, value <| String.fromInt model.sum ] [] ]
        , p [] [ text "Product: ", input [ type_ "numeric", readonly True, value <| String.fromInt model.product ] [] ]
        , p
            [ hidden <| Nothing == model.quotient ]
            [ text "Quotient: "
            , input
                [ type_ "numeric"
                , readonly True
                , value <| Maybe.withDefault "NaN" <| Maybe.andThen (\x -> Just <| String.fromInt x) model.quotient
                ]
                []
            ]
        ]

Now we have everything in place and we can change the operands in our calculator as we want it and the results are automatically updated.

Where to go from here

The main purpose of this post was to demonstrate how a simple frontend can be written in Elm. I focused more on the web framework than on the language which may be a topic for another post. As usual if you want to learn the language, I can recommend you to do it on Exercism which also has an Elm track. The language guide to Elm is the first starting point if you want to learn its syntax. Then just play around with Elm and try a bit more complex things. You could for example try to make our calculator more beautiful by adding CSS styles to it.

One important tool for Elm development is html-to-elm where you can paste some html code which will be transformed into an Elm view.

Another cool tool is Ellie where you can write ELM code online, immediately see the rendered result and also have the possibility to share code examples with others.

I hope I gave you an insight on frontend development with Elm. Feel free to give me feedback via e-mail or if you have any questions.