Learning Elm

Elm is "a delightful language for reliable webapps."— or so its homepage says. Today, I'm going to dive in head-first to find out what all the fuss is about.

For those of you unacquainted, here are some of Elm's purported features, straight from its homepage:

  • Compiles to JavaScript: The language targets JavaScript specifically
  • No runtime exceptions: Instead of producing runtime exceptions, Elm uses type inference to detect problems during compilation.
  • Great performance: Like React, Elm uses a virtual DOM designed for simplicity and speed.

Sounds cool. Let's fire it up then, shall we?

Installing Elm

If you're on a Mac like I am, Elm has a Mac installer available here. The current stable version is 0.18. The '0' at the beginning doesn't give me much confidence in using Elm in production, but if Pivotal is doing it then maybe it's worth a try?

After completing, the installer tells us:

A bunch of useful programs were just placed in /usr/local/bin/

Installing the Vim plugin

Since I use Vim, I'm going to go ahead and install the elm.vim plugin:

  $ cd ~/.vim/bundle
  $ git clone https://github.com/lambdatoast/elm.vim.git

Easy enough.

Hello World

When learning a new language, there are two benchmarks of expertise: Hello World and Todo.

Elm appears to be no exception. Let's follow this Hello World tutorial on the elm-tutorial.org site.

Create a project folder

First we create a new folder for our Hello World application. I called mine hello-elm:

  $ mkdir hello-elm

Install packages

It looks like first we need to install the package elm-lang/html. Why that's not installed by default I'm hopeful will be revealed later on...

  $ elm package install elm-lang/html
  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]

Ooh, do I approve? I feel like a boss. Yeah, I approve.

Starting downloads...

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

Packages configured successfully!

First impressions of package management: Elm is pretty freaking mature for a 0.18 release! Would be nice to see NPM/Yarn-style progress bars, but so far this feels solid.

The elm package install command seems to have created the following stuff in the directory:

elm-package.json
elm-stuff

Write some code

Okay, now we're going to create our first Elm module. Exciting. Here's mine:

module Hello exposing (..)

import Html exposing (text)

main =
    text "Hello"

I put that in Hello.elm. Right now it's not clear to me whether that should be in the elm-stuff directory or in the root directory. I put it in the root.

Now we run elm reactor:

  $ elm reactor

Interesting. So it looks like Elm has a sort of "dashboard" view of your files, packages, and dependencies. Clicking on our Hello.elm file takes us to our application, which at this point is just the text "Hello" in the top-left corner of the page.

Some thoughts about what I've seen so far:

  1. There's a module named Html. The fact that Elm treats the DOM as a first-class concept as opposed to relying on class-like abstractions as in standard JavaScript is a welcome change as we move toward functional UI paradigms.
  2. The compile time for a Hello World application seemed awfully long. I'm not sure if it was just a fluke on my machine, but it took 2-3 seconds before my app loaded. I could understand if we had a complex application, but this is Hello World...

Okay, let's keep going...

Rendering complex markup

So far, it looks like we can render text using the text function. I'm digging around in the documentation and found an examples page. I poked around a few of these until I found an example of rendering an unordered list.

It looks like Elm's composition syntax is Lisp-ish, but not Lisp:

import Html exposing (li, text, ul)
import Html.Attributes exposing (class)

main =
  ul [class "carbonated-beverages"]
    [ li [] [text "La Croix"]
    , li [] [text "Zevia"]
    , li [] [text "Pepsi"]
    ]

Some observations here:

  1. Because Elm is functional, even attributes need be imported. Whereas in ES6, we need only import a module in order to gain access to all its getters and setters, Elm requires explicitly importing things like class in order to use them.
  2. It is a welcome change to see a main function that returns a DOM tree. In React, there's a bunch of boilerplate to attach a component to the DOM. In Elm, all of that boilerplate is baked into the language.
  3. The slow compile time appears to have been a fluke. This new example compiled almost instantly.

A word about Elm's architecture

It looks like Elm has its own application architecture that it lovingly calls the ... Elm Architecture.

It consists of:

  • Model: the state of the application
  • Update: a way to update the state of the application
  • View: the resulting view of your state as HTML (or XML, or SVG)

From what I can tell, these roughly correspond to React/Redux concepts thusly:

  • Elm's Model layer is equivalent to Redux's store
  • Elm's Update layer is equivalent to Redux's actions
  • Elm's View layer is equivalent to React itself

These analogies are made even more convincing when you consider the fact every value in Elm is immutable, just like Redux when used with Immutable.js, and that the Elm Architecture makes use of the one-way data flow paradigm made famous by Facebook's Flux.

Responding to user input

Okay, so we've produced a Hello World example and then rendered some slightly more complex markup. That's fine, but how does Elm deal with user input? Let's try creating an example where clicking a button renders the text "Hello Elm" below the button.

According to Lucas Reis's blog, Elm supplies a function Html.App.program which automatically routes an application for the Elm Architecture:

Elm apps use a centralized state pattern, which I've written about in this blog. It's a simple "loop" described as such:

Model > View > Update > Model > View > ...

First you describe a model of your app. It is the skeleton, the data you need to render the application.

The view is then a function of the model. It takes the data and renders it.

After rendering, the application "waits" for user interaction or any other event. When that happens, it triggers the update function. The update function is a function that receives the old model and data of the event, and returns a new model. This model is then rendered, and the loop continues.

Evidently, there's a simpler version of this function supplied with Elm called beginnerProgram. I couldn't find a decent explanation anywhere for what this function does that the regular program doesn't (or vice versa). If you know, please leave a comment!

Here's the full text of the user input example I just created:

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

type alias Model =
    String

model : Model
model = ""

type Msg = ShowGreeting

update : Msg -> Model -> Model
update msg model =
    case msg of
        ShowGreeting ->
            "Hello Elm"

view : Model -> Html Msg
view model =
    div [ ]
        [ button
            [ onClick ShowGreeting ]
            [ text "Click Me" ]
        , p [ ] [ text model ]
        ]

main =
    Html.beginnerProgram
        { model = model
        , view = view
        , update = update
        }

Let's walk through this line by line together.

First, we import some HTML functions we'll use to render our markup and listen to the button for clicks:

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

Then we'll create a new type called Model which is an alias for a string:

type alias Model =
    String

Next, we'll create an instance of the Model type called model and initialize it to an empty string literal:

model : Model
model = ""

Then, we'll set up our update function. This confused me at first, so maybe my explanation will help you if you come from the Redux universe.

The update function in Elm works essentially like reducers in Redux. It takes a message (an action in Redux-speak) and the model (the state in Redux-speak) and returns a newly mutated model.

Except Elm is much more suited to this paradigm for the following reasons:

  1. Elm's union types make the usage of symbolic enumerations like user actions checkable at compile time instead of relying on runtime checks.
  2. Because Elm data is always immutable, state reduction doesn't require clunky external libraries like Immutable.js.

So our update function takes a msg (in this case, only the value ShowGreeting), and returns a new model state based on whatever msg it received:

type Msg = ShowGreeting

update : Msg -> Model -> Model
update msg model =
    case msg of
        ShowGreeting ->
            "Hello Elm"

Okay, now that we've defined how our model will change in response to user actions, let's define the view. Again, Elm comes to the rescue by allowing us to pass the model directly into the view function. No complex binding a la Redux containers!

view : Model -> Html Msg
view model =
    div [ ]
        [ button
            [ onClick ShowGreeting ]
            [ text "Click Me" ]
        , p [ ] [ text model ]
        ]

Notice how we're passing model to the text function inside the p tag. When we boot the application, the model's value will be an empty string, but as soon as we click the button, we fire the ShowGreeting message and the model changes to the string "Hello Elm".

Finally, we glue everything together via the beginnerProgram function:

main =
    Html.beginnerProgram
        { model = model
        , view = view
        , update = update
        }

My impressions

All in all, I was pretty impressed with Elm after playing with it for an hour or so. While it's clear the framework has a ways to go in terms of documentation and external library support, its philosophy removes a lot of the boilerplate that plagues modern JavaScript tooling. In addition, it takes concepts like immutability, functional programming, one-way data flow, and type safety which are must-haves in today's JavaScript development world, and makes them first-class members of the language.

Would I use Elm in a production project yet? I'm not sure if I'd subject my clients' businesses to that risk just yet. While it's impressive, the idea of relying on a small community for support on complex topics like JavaScript interoperability would make me a bit cautious. But I'll definitely consider Elm the next time I build a personal project.

Hiring a web developer? Read this first

You're about to build a new web application, but you're terrified at the breadth of terminology and wary of consultants nickel-and-diming you.

My free book Why Software Projects Fail offers that framework. In this companion to your hiring and discovery process, you'll learn how to inform your next decisions and to empower yourself along the way.

In the book, you'll learn:

  • How to find and hire a trustworthy consultant
  • Why it's critical you pay for a software discovery
  • How to assess your consultant's bid
  • What to expect—and be wary of—during the development process
  • How to take control of your project

Enter your email address below and then click the "Send Me My Free Gift" button. I'll send you Why Software Projects Fail, and you'll be equipped for success on your next project.