Have you tried using software from way off the beaten path? Maybe you tried to make software for your graphing calculator and realized that you were one of five people to ever try that and there was no documentation. The experience sucks. You may be the kind of person who enjoys working through arcane bugs and zero docs (and sometimes, that’s me), but I decided that in professional software development, where shipping matters, I would stick to only the most widely used tools.

I made it a hard and fast rule: if I found two technologies that could solve a problem, I would choose the one more people were using. I didn’t want to include an obscure graphics API and then discover that no one had ever called set_color() followed by resize_window() (resizing is hard) and somehow those two functions in sequence cause a segfault. I don’t want to be the guy that finds a bug in the compiler. I just need to ship the product.

Part of my hesitation to use new platforms was borne from two years writing Go before it had a story for dependency management. The community fragmented into dozens of painful or mediocre solutions: gopkg.in, godep, glide, gb, etc. It took years for developers to “settle” (I use the word lightly) on godep, and it’s not until recently, as Go’s adoption reached an incredible high, that the experience is relatively smooth. I recently learned that the Go developers may be introducing an official solution: golang/dep.

With my new rule in hand, I felt confident in my stack choices. When I looked for tools to build a web application, I considered only massively popular options like Bootstrap and jQuery. Each of these libraries has a bazillion monthly users, so all the bugs have been worked out, right? If I hit an issue, surely someone will have asked about it on StackOverflow. Smooth sailing. And hey, it works. You can go through the whole development lifecycle of the app and you’ll rarely encounter a situation where you can’t find a fix online in 5 seconds. Somebody else has already worked out the kinks. My strategy was flawless.

But when I joined Real Kinetic, I found out we were writing web client code in Elm. Elm? Really? The experimental language created for Haskell snobs who can’t handle stooping to the level of a regular blue-collar language like Javascript? I pictured Elm fans as hipsters standing around talking about how side effects in web applications are so 2008, bro. I definitely did not picture them as people who were shipping applications. Elm was not, in my mind, a match for a production environment.

So for the first few weeks, I was skeptical. But then, about my third time running through the Elm guide, the architecture finally clicked in my head. I got it. Wow. This is way better than Javascript. I can look at the code and predict its behavior, edge cases and all, with certainty. There are two reasons.

First, Elm has the natural predictability of a pure functional language; when you write Elm, the compiler forces you to consider every case. Consider the following example: You write some software for your friend to keep a digital catalog of the books in their library. They want a feature to produce a report that shows how many books are in the catalog, the copyright date of the oldest and newest book in the library, and the number of unique authors.

type alias Book = { isbn : String , title : String , authors : List String , copyright : Int , edition : Maybe String } type alias LibraryReport = { numBooks : Int , oldestCopyright : Int , newestCopyright : Int , uniqueAuthors : Int } createReport : List Book -> LibraryReport createReport books = let copyrightYears = List.map .copyright books in { numBooks = List.length books , oldestCopyright = List.minimum copyrightYears |> Maybe.withDefault 0 , newestCopyright = List.maximum copyrightYears |> Maybe.withDefault 0 , uniqueAuthors = books |> List.map .authors |> List.concat |> Set.fromList |> Set.size }

I can walk through some of this code more carefully. Consider this section:

newestCopyright = List.maximum copyrightYears |> Maybe.withDefault 0

Here we make use of two library functions: List.maximum and Maybe.withDefault. List.maximum ’s purpose and usage is obvious, and its type signature is revealing.

maximum : List comparable -> Maybe comparable

In this case, since our List is of type “List Int,” the maximum function returns a “Maybe Int.” The concept of Maybe will be familiar to people coming from Haskell ( Maybe ), Rust ( Option ), and Java ( Optional ). “Maybe” is a container with either zero ( Nothing ) or one ( Just x ) elements. We can use the function Maybe.withDefault to “unbox” a Maybe value, substituting a default value if the Maybe container is empty.

So, this section of code retrieves the maximum value in the copyrightYears list, if it exists. If not, we just return zero. Elm requires that you think through all the edge cases. You must consider and specify what will happen in every case. This is the reason Elm can realistically promise zero runtime exceptions.

The second reason that Elm is better than Javascript is that Elm is a natural fit for the DOM. The feeling is so natural that it feels like HTML was designed for Elm, rather than the other way around. Part of the reason is that Elm uses the concept of a Virtual DOM, which you may be familiar with from React. (The Virtual DOM causes the view to be updated automatically as the program model changes.) Here’s how we might render this LibraryReport type to HTML:

renderRow : String -> Int -> Html Msg renderRow title data = tr [] [ td [] [ text title ] , td [ style [ ( "text-align", "right" ) , ( "min-width", "5rem" ) ] ] [ data |> toString |> text ] ] viewReport : LibraryReport -> Html Msg viewReport report = div [ style [ ( "background-color", "#eee" ) , ( "padding", "3px" ) , ( "margin", "3rem" ) ] ] [ b [] [ text "Library Report" ] , table [] [ renderRow "Number of Books" report.numBooks , renderRow "Oldest Copyright" report.oldestCopyright , renderRow "Newest Copyright" report.newestCopyright , renderRow "Number of Unique Authors " report.uniqueAuthors ] ]

Note the way we composed renderRow within viewReport . The rendered result:

Working in Elm is a series of pleasant surprises: an elegant command-line interface, a helpful compiler, an intuitive debugger.

These surprises include (spoilers):

An elegant command-line interface in the form of elm-package and elm-make. Automatic code formatting with elm-format (similar to gofmt). Automatically enforced package semver. Elm-Reactor, a great way to playground new projects. Elm’s debug mode, with built-in time travel.

The compiler message that appears when you delete a field from a struct without updating the corresponding functions.

The biggest downsides of the Elm language are that...

It’s a functional language and therefore the learning curve is steeper for programmers from imperative backgrounds. You probably can’t use it on the server side (not as versatile as Javascript). It lacks some of the higher-level capabilities of other functional languages like Haskell. The strict dependency semantics put pressure on Elm library maintainers to upgrade immediately when their dependencies change, so as not to block downstream upgrades.

Notice anything missing from this list? Even though Elm is a small language with a small community, that doesn’t affect the Elm experience in a noticeable way.

Now I strongly prefer Elm for any new client-facing web app. Elm has other surface benefits, like great performance, decent library support, and thoughtfully-designed JS interop, but for me, the upside of Elm is that it gives me confidence in my code.

Real Kinetic is committed to helping clients develop great engineering organizations. Learn more about working with us.