Tom MacWright

tom@macwright.com

Elm

In March I wrote about the darklang-based backend for my side project, Old Fashioned. The frontend is just as spicy: it’s written in Elm.

I’ve used Elm before a few times - demonstrating undo & redo, visualizing binary trees - but never on a long-lived independent project. Those early experiences were very positive, but most other projects were a better fit for some other technology.

But even before I started prototyping Old Fashioned, I knew that Elm would be perfect for it. The site is essentially a view into some fancy data structures. It doesn’t require deep integration with any browser APIs, and I could choose and design the backend to fit. I knew that Elm had a really nice type system for this kind of system, as long as you don’t ask too much of it. And that its pace of development can be blistering-fast if you use the right tools.

Data

Here’s a taste of the approach. This is what a recipe looks like, in Old Fashioned. This shows off a few pretty neat properties of the language.

{ name = "20th century"
, ingredients =
      [ ingredient gin (CL 4.5)
      , ingredient lemonJuice (CL 2)
      , ingredient whiteCremeDeCacao (CL 1.5)
      , ingredient kinaLillet (CL 2)
      , ingredient lemon (Custom "twist")
      ]
, description = "Combine ingredients in a cocktail shaker, shake with ice, strain into a cocktail glass."
, glass = Cocktail
, video = Just (Epicurious "19:51")
}

Maybe

Videos, which I currently source from a great Epicurious explainer, aren’t available for every cocktail.

video = Just (Epicurious "19:51")

This is nicely modeled with Elm’s concept of Maybe. Languages like Rust and Swift have similar concepts, as do most old-school functional languages. They sometimes get in your way, but usually are quite lovely.

Custom types

ingredient whiteCremeDeCacao (CL 1.5)

See some easy custom types. I wanted a flexibly structured way to store quantities. Check out how it works:

type Quantity
    = CL Float
    | Cube Int
    | FewDashes
    | Slice Float

And then other methods can smoothly interact with all the different kinds of quantities to format and convert them:

printQuantity : String -> Quantity -> Units -> String
printQuantity name quantity units =
    case quantity of
        CL a ->
            case units of
                Cl ->
                    formatFloat a ++ " Cl " ++ name

                Ml ->
                    formatFloat (a * 10) ++ " Ml " ++ name

The same goes for glassware. It’s a type:

type Glass
    = Collins
    | Highball
    | OldFashioned

That’s it: a super way to create a custom type, sort of akin to an enum in languages that have them.

UI

The frontend also uses elm-ui which is quite wild. It’s a layer on top of HTML, so that instead of dealing with divs and spans, you use columns, rows, paragraphs, and more. It works really, really well in its sweet spot.

Not only does it abstract around layout, it also creates a layer on top of input elements that ensures your compliance to ARIA accessibility standards, and makes it really easy to add labels and styles.

Input.radioRow
    [ spacing 10
    ]
    { onChange = SetMode
    , selected = Just model.mode
    , label = Input.labelHidden "Mode"
    , options =
        [ Input.option Normal (text "List")
        , Input.option Grid (text "Grid")
        ]
    }

Though elm-ui can interoperate with html (which you can also express via Elm, in a syntax that looks sort of like Haml, it’s a pretty complete and extreme worldview that you have to buy into totally to get the benefits.

Notes

Some thoughts working on this project:

Elm’s bundle size and performance is really pretty good. Old Fashioned currently ships 47.73KB (gzipped) of compiled JavaScript. It scores a 97/100 on Google’s Lighthouse performance audit. Elm’s custom types compile down to numbers, and match statements can compile down to switch. The only performance problems I encountered in this project were my own doing, asymptotically bad algorithms that would have been bad in any language.

Elm’s developer experience really is incredible. The errors are amazing. The documentation is great. The tooling - I used create-elm-app for this one - is really solid. It lives up to the hype.

Elm’s safety, like other forms of safety, makes you face the unexpected up front. For example, if you want to ask Elm for ‘the first recipe in a list’, you have to consider the possibility that there is no first element, because the list is empty. Elm won’t just let you blindly assume that things will work out. This is great, but attitudinally you need to appreciate the caution rather than curse the timidity.


Here’s a little about how I think about side-projects, technology, and choices.

After a really fun, successful, smooth experience like I had with Elm on Old Fashioned, I do make a mental note of what Elm is great for. But I should note that, with side projects, you make the project fit the technology. You make the project fit the time you have, and what you find interesting. So they aren’t necessarily indicative of how that technology would work elsewhere.

So would I advocate to port products to Elm? It depends massively on the company, the product, and the people. If the product didn’t require lots of interactivity, then it’s probably not ideal: you can implement something simpler, and the benefits of Elm are largely related to creating interfaces. If the product was truly speed-oriented, like something that involved Web Workers and WebGL, then Elm probably isn’t ideal either: its generated code can never be faster than hand-written JavaScript, and its ability to interface with native JavaScript isn’t a focus. The Elm team wants to have a mostly pure-Elm ecosystem, and their reasoning is solid.

But if you’re in the sweet spot of Elm, an interactive, complicated product with lots of data wranging and transformation, then it’s maybe the best technology possible.

The benefit of side projects is precisely that you can make a lot of repeated choices and figure out the shape of different solutions without betting everything on it. You can also do occasionally projects to check in on the progression of technology, like I’ve been doing with Elm. Maybe as time passes I’ll start to see a wider role for the language.

This type of fitting solutions to problems and vice-versa is fuzzy, cultural, fallible, and has many dimensions. It’s the furthest thing from science, but it’s very worthwhile.