The Case for Elm

July 01, 2024 - javascript coding elm web-app

One of the most significant drawbacks of using the HTML/CSS/JS stack for application development is the default “fail silently at runtime” error behaviour. This was fine in the DHTML days when we were just adding decorative or cosmetic functionality to a static page, but I think it makes less sense for modern applications. Authoritative stats are difficult to find, but some put JS error rates at over 6% of page views.

Another personal bugbear of mine is the ‘batteries not included’ approach to modern frontend development. Smatterers like myself can’t simply install a development environment & SDK and start coding, it requires selecting/installing/configuring/learning a JavaScript runtime, package manager, compiler (probably?), module system, frontend framework, CSS framework (maybe?), bundler etc, all of which have multiple competing open source implementations, with loud online supporters/detractors of each, along with rapidly changing versions & recommendations. It’s an incredibly powerful and flexible ecosystem for full-time frontend teams, but is an enormous burden on beginners & casual web developers.

Elm is a mature techology that solves both of these issues, however it’s never really seen widespread adoption. I’ve been doing all of my browser app development in Elm for about 8 years - the context here is that I’m only a part-time web developer, so I’m less enthusastic about flavour-of-the-month or bells & whistles frameworks/toolchains with a large learning curve and constant API churn. Elm has been a reliable and low-risk option for me, and I’ve long been a (mostly unsuccessful) proponent, but I’m nothing if not stubborn, so following is my attempt to explain why Elm is a good option for many teams & why you should consider it.

Isn’t Elm dead?

No. This is a criticism that shows no signs of abating. I think where it comes from is we tend to develop an aversion to abandoned open source projects, usually after some forced rewrites/migrations when a showstopper incompatibility pops up in an abandoned library or framework. This doesn’t really apply in the same way to Elm - Elm operates within its own ecosystem, it works and it will continue to work for the forseeable future. If it does what you need today, you can be fairly confident it will still be doing it in 10 years. A good comparison might be the C standard - typically there are 5-10 years between releases (but a lot of code still targets the older standards).

Often when managers and tech leaders make this argument they’re referring to developer and library availability rather than the technology itself. There are certainly fewer packages available for Elm than the 2 million on the npm registry, but the packages are all of a fairly high standard, and SemVer compatibility is enforced by Elm. There’s a fairly active (but comparatively small) community of developers, but because Elm is an easy language to pick up, it’s more feasible to train/develop internally. (I have more thoughts on choosing a tech stack based on cheapest and most plentiful developers, but I think this is a topic for a different blog post).

I think it’s also worth reading The hundred year programming language (if you haven’t already) for an in-depth discussion on what it means for a language to be ‘dead’. Elm is certainly past the point where it’s the new hotness, but from my perspective it’s still the best option for its particular niche.

Benefits of Elm

  1. No runtime errors. This has always been the headline feature of Elm, and in the context of the statistic above, is a compelling one. As a pure functional language Elm is able to make guarantees about runtime behaviour that very few other browser technologies can. In practice this means the developer can focus more attention on logic and interaction than trying to spot potential errors or invalid assumptions.
  2. Incredibly stable. The most recent major (breaking) Elm release was 0.19 in 2018. Having a frontend codebase that might have been untouched for half a decade, yet is up to date and doesn’t need days of updates before starting new work, is probably unthinkable to a modern React or Vue developer.
  3. Predictable behaviour. A pure functional language is deterministic - if it produces a specific output on your developer workstation, it will produce exactly the same output in any browser on any operating system in any locale. Functional programmers often talk about code being "easy to reason about"; this is a difficult concept to communicate & really needs experience to understand, but in short it’s harder to make mistakes and requires less brainpower and expertise to write reliable code.
  4. Easy to learn. This is obviously subjective, but the Elm language & standard library are small and a motivated developer can cover it all inside a week. In most cases the hardest part is unlearning techniques you may have carried over from other languages. A significant amount of effort has been put into making compiler errors very descriptive & easy to action. In general, one of the main goals has been to make Elm easy for beginners to pick up - this has actually led to language features being dropped, which is unheard of in most programming languages!

Drawbacks of Elm

  1. Can be difficult to integrate with third party JS. Generally anything that manipulates the DOM is going to trip over Elm or vice versa. Using ports to integrate JS is more effort & ceremony than the FFI call approach of most other compile-to-JS languages (although it is safer).
  2. There are some gaps in the official tooling and libraries. The elm cli has no native ability to upgrade or uninstall a package - a community tool was developed to work around this. The Elm regex library can match on a regex but doesn’t have good support for capture groups, because Evan doesn’t like regexes. You can’t easily develop private packages or host internal package repositories (and you certainly can’t just point at a github repo).
  3. The ML syntax can put off migrants from C syntax languages, but more fundamentally Elm won’t let you use the imperative programming techniques you’ve picked up in mainstream languages. You can probably expect a substantial productivity hit for the first few weeks while you adjust, which will be frustrating for experienced developers. An async callback to access the current date, or current locale, or a random number is going to seem over the top to developers used to a single method call. For complex dynamic UIs, things like animations, progress, and accessing rendered element position/metrics are more difficult than in a regular JS-like environment.
  4. Good samples & best practices for larger-scale Elm applications can be hard to come by. The “nested-TEA” model is the most well-known scalable Elm app architecture, but requires a lot of top-level boilerplate (this can be generated with elm-spa or elm-land). There’s some weight behind an alternative approach that uses global app state instead of page-level state, but it’s a lot less obvious to beginners how to go about building this.
  5. Beginner (and sometimes even experienced) developers often base their technology choices on ‘employability’ - how many software engineering positions specifically mention it as a requirement? Elm is not going to measure up on this yardstick. However, I would argue that learning Elm will make you a better developer than just learning another JS framework.

Should you adopt Elm?

There are a few success factors which may make Elm a more likely match for you:

Alternatives

There are a couple of Elm forks around - Gren and Zokka being the most well known. The community hasn’t really coalesced behind either; the confused branding, purpose and adoption hurdles are probably a little too high, given core Elm is not fundamentally broken.

I’ve flirted with Rescript a couple of times - it has active development and solid corporate backing, supports React & JSX out of the box, has a slightly more approachable version of ML syntax, and a simpler FFI approach to JS interop. The downsides are it’s an impure language - it can throw errors (mostly via foreign JS calls) and be non-deterministic, although good conventions & code review would minimise the chances of this. It’s also had a fairly painful journey from a server-targeted OCaml variant to a compile-to-JS language via forks & rebrands (perhaps illustrating some of the pitfalls of Elm attempting to follow that approach).

A newcomer is Gleam - this started life as a compile-to-Erlang language for the BEAM, but now also supports a JS target and has some Elm-inspired browser libraries. While it’s a C syntax language with FFI JS interop, it feels a lot like Elm - it’s a very small, beginner-friendly functional language that’s ended up attracting several developers from the Elm community, and a Erlang/Elixir compatible backend is a nice bonus. It’s still early days but the language is seeing considerable growth.

Conclusion

While it’s seen some corporate adoption, Elm is never going to become the mainstream technology that supporters had hoped for in the early days. However in my view it’s still a standout in its category of high-assurance browser software & a stable, beginner- & part-time-friendly web app framework, and it deserves serious consideration from teams that are prepared to experiment with their tech stack.

Older posts

Is readability always better?

September 30, 2022 - 421 words - 3 mins
At the recent YOW Perth conference, Andy Marks & Pam Rucinque of Thoughtworks presented a thought-provoking talk on "Reviving the art of software design". As part of the talk they had a selection of different-but-functionally-identical implementations of a pangram-checking function, and attendee… read more

Blog Necromancy

September 26, 2022 - 585 words - 3 mins
I realise this blog has been quiescent for over half a decade, but I've resolved to put a little bit more time into blogging, professional development, and other not-directly-billable pursuits. There are a number of factors behind this; chief is probably that I'm not really using Twitter anymore (th… read more

RxSwift DelegateProxy with required methods

May 12, 2016 - 350 words - 2 mins
Once you’ve sipped from the Reactive Kool-Aid it can become emotionally painful to have to implement callback methods the old-fashioned way when you hit a delegate-style API that doesn’t have a prebuilt Observable wrapper. Max Alexander did a great tutorial post on how to implement your own Delegate… read more

Swift ErrorTypes - A Modest Proposal

October 02, 2015 - 790 words - 4 mins
After the WWDC announcement, when I got over my initial knee-jerk ‘Exceptions - ick!’ reaction, I came around to being a supporter of the Swift 2 error handling model. While it would be nice to be able to desugar the implicit result type, and there’s a glaringly obvious hole with async error handlin… read more