Everybody’s talking about Elm, the functional programming language that makes front-end web development a lot less hairy. Working with a small team, I recently converted part of a client project to Elm, and have some lessons to share with the rest of the class.

Pick the right size of problems

Before we go any further: if you’re thinking of rewriting your entire SPA in Elm as your first in-production use of Elm, don’t do it. You’ll run way over budget and schedule, people will be mad at you.

Since Elm compiles down to JS, they have a really nice interop API that lets you mount your Elm component into a regular page.

Pick something small: I started with a non-customizable list view with basic filters that was backed by a JSON API. Even then, I think I might have bitten off too much to start.

Figure out why you’re doing it

I’m not going to lie; learning a new language is fun, and converting projects to that language is a little less fun but still a lot of fun.

If you’re doing it just to be cool, it might be better suited to use on a “green field” project, especially if deadlines are especially tight.

For us, the existing codebase had these problems, justifying an Elm move.

  1. Business logic (including event-processing code) was mixed in with view templates;
  2. Poor error handling meant that some parts of requests would fail, while others would go through, producing inconsistencies in the database. When it happened, the user would have no idea what was going on;
  3. Multiple front-end libraries had been (mis-)used and then abandoned, sometimes in the same file;
  4. Nests of inline styles and the occasional global CSS override rule meant that views were inconsistent, even inside the same module of the project. This was noticeable to customers and as a result began to work its way slowly up the “concerns list” over the past year;
  5. No automated testing - of any kind - meant that views would frequently completely break when shared logic was updated for another view, but we would have no idea until deployment;

With this in mind, it seemed like we were looking at a near-complete rewrite, whether in Elm or React. We didn’t proceed until we had unanimous buy-in from the team.

Resist the urge to generalize

One of my favourite things about Elm is that it’s fairly opinionated. A lot of the best practices come directly from people who are in small teams like myself. This means that, for the most part, the documentation also doubles as an advice column.

A good example of this kind of advice in the official Elm guide is this segment about how and when to split up a file into multiple modules.

To summarize, assume similar code is unique by default. (It usually is in user interfaces in the end!) If you see logic that is the same in different definitions, make some helper functions with appropriate comment headers. When you have a bunch of helper functions about a specific type, consider making a new module. If a new module makes your code clearer, great! If not, go back. More files is not inherently simpler or clearer.

A nice feature of Elm (and most other functional programming languages, if you stay away from the metaprogramming toys) is that the boilerplate is at least really readable, with a few exceptions. I think this helped later team members figure out what the code was doing right away.

This explicitness was a refreshing change from a lot of other JavaScript-based front end libraries, where invisible mutations of variables and “magic at a distance” is common, making it hard to understand really what is going on without a debugger.

Obviously, this kind of aggressive pushing of opinions isn’t for everyone, and I can see it rankling certain types of developers. If that describes you, then you could also try a more laissez-faire solution such as PureScript.

Think about it

Functional programming is all about the data structures you choose. If you choose weird ways to compose your data, or just start growing a big ball of mud, it’s often way more awkward to decompose that structure and write small functions.

In a few places, I jumped into writing code right off the bat and then got punished for it. As I was moving onto my third and fourth component of the new stuff, I got more used to how to map the existing entities from the JSON API into patterns that would write tighter, but more readable, code.

Ellie is good

Ellie is the live web editor for Elm, and it features an entire Elm development environment including public packages and time-travel debugging. It’s great for passing snippets and demonstration code around the team, and is pretty solid overall.

I used it a lot for prototyping, since it was much faster than creating a new project and compiling it to test a theory.

Ports are really easy

Ports are the mechanism that Elm uses to speak to the “unsafe” JavaScript world outside of its nice little runtime. At first, I figured this would be a bit of a mess, but it’s not really any different conceptually than hitting an API and interpreting its response.

You’ll probably need to refactor at least some of your existing JavaScript to reduce the amount of hair-pulling you’re doing on the Elm side, or write some shims, but I was very surprised at how nicely this came together.

But Decoders are hard to understand at first

I had a lot of trouble with Decoders, despite having some experience in the past with parser combinators. It is a little difficult to understand how the Decode typeclass works.

Eventually, my fear of Decoders was overcome (not least because I had enough working code on hand). However, when making fuzzers for unit tests, I still have a lot of difficulty wrapping my head around this way of thinking. It is definitely a blind spot on my part, one I am working hard to correct.

0.19 just happened

Elm had a fairly torturous (by their standards) transition to 0.19, which broke a bunch of existing code, moved imports around and made some work for those who had adopted earlier. Like with Rust, I’m glad that they are willing to sacrifice backwards compatibility for developer ergonomics.

If you’re starting on a new Elm project, it should absolutely be in 0.19. I haven’t had a particularly difficult time finding libraries that are compatible, though on occasion my “first choice” is stuck on 0.18 for the time being.

Libs are small, so contribute patches

Most of the elm libraries I’ve been using are extremely small (fewer than 500 lines total) and worked on by a single maintainer. For most libraries, it’s easy to read the code, and the community prides itself on being welcoming to helpful PRs.

Gaps

IE/Edge story

I’m not super surprised anymore when support for IE/Edge is poor, but the Html module in Elm doesn’t give you much help or even guidance. One of the first problems we ran into is that we couldn’t get messages coming back from checkboxes or select boxes, and that’s because the Elm onInput event doesn’t work on Edge/IE - because it’s mapped to oninput, which Edge does not want to support.

Luckily, writing a custom event handler isn’t too bad - once you get over the weirdness of having to parse JSON (as you do with almost all JS/Elm interop).

onSelect : (String -> msg) -> Html.Attribute msg
onSelect msg =
  let
    targetValueParsed = Json.Decode.at [ "target", "value" ] Json.Decode.string
  in
    on "change" (Json.Decode.map msg targetValueParsed)
view model =
  select
    [ onSelect IceCreamFlavourChanged ]
    [ option [ value "pistachio" ] [ text "Pistachio" ]
    , option [ value "vanilla" ] [ text "Vanilla" ]
    , option [ value "rroad" ] [ text "Rocky Road" ]
    ]

You can fix a similar problem with input[type=checkbox] by using onClick instead of onInput.

Private packages

There’s no built-in Elm package manager support for private packages. Everything is public. We used elm-git-install to work around this, but it has its own flaws:

  1. Adds an awkward extra step to run before compiling;
  2. If you don’t do it, the error messages look a lot like broken code;
  3. It only works with git tags, so you’re spending a lot of time making one small commit in an upstream library and then touching up everyone who depends upon it;
  4. At the time, private packages couldn’t easily depend on other private packages (you have to create a stub application inside the dependency, to test the sub-dependency).

Continuous integration helped with #2 a little bit, but the procedure for updating is still a little arcane. It would be nice to have the Elm package manager eventually support private packages, although I definitely understand their reasons for not doing so yet.

Editor Support

Autocomplete and static analysis tools are trailing behind the language’s development, though a lot of really smart people are making great strides. I spent a lot of time scrolling up and down trying to remember just how one specific Message was formatted.

A nice part of Elm is that the language is pretty easy to parse, so writing your own narrowly-focused analysis tools (“stop swearing in comments!”) could be a weekend task instead of a whole career.

Web Components

Unfortunately for this project, as you can tell above from the IE/Edge section, we were tasked with targeting some older browsers. We also had a rich-text control (Froala) that we wanted to use like a regular textbox inside our Elm context.

The philosophy of the Elm language, or at least the front-end runtime (Virtual DOM), is that Elm owns the entire DOM. This means that if you want to put fancy JavaScript controls inside your Elm context, it’s going to be a bit of a struggle.

At the time, Web Components were the recommended way to do this, and I agree wholeheartedly that it’s a great solution for modern browsers. On older browsers, we ended up bodging in a series of polyfills that worked “most of the time.”

The good news is that using Web Components from Elm is really easy! It’s nice to see a new web/browser standard that delivers on its promises.

Resources

If you’re thinking about proceeding with retooling part or all of your project in Elm, it’s worth doing your research first. Here are the resources I found that were the most helpful:

  • Practical Elm is an excellent read for real-world applications of Elm. This is the book that we had the most success with while in the midst of the project.
  • Elm In Action is written by one of the lead developers (and most helpful people) in the language community.
  • The Elm Weekly newsletter always has something interesting to look at.
  • Norwegian transport provider Vy moved to Elm, and there’s an excellent writeup on the lessons learned about it.