Lessons learned with Elm
🙝I’ve started to work with Elm in the last couple of months. I’m new to the language and staticly typed functional programming too. I come from a PHP, Lisp, Python, JavaScript background. Hereby I’m collecting the main lessons I’ve learned along the way.
Language
I have to admit that it took some time until I became friends with the syntax of the language. Eventually I find Elm’s syntax very clear and explicit, and now I love it. With help of elm-format, there’s even one preferrable way to write Elm code.
What came to me as a suprise was that partial application is not only a feature you may use like in other functional programming languages. Partial application is baked in how you define any functions in Elm. In fact, all functions in Elm take only argument.
It’s liberating how safe and fun is to do refactoring an Elm code base. Static typing combined with Maybe
, pattern matching and Elm’s friendly compiler messages are incredibly helpful. Change the name of a constant or a type in a function signature, and the compiler will tell you exactly what you need to correct. Introduce a new value for a custom type and the compiler will let you know of unhandled cases. No more guard conditions to make sure you access the value you assumed. No more unit tests to make sure you handle invalid input cases. When refactoring becomes this easy, improving code design becomes habitual.
Although first I struggled a bit with the Maybe
type, it’s a powerful tool for explicitly stating if a certain value can be missing. If there’s a chance of absent data, the compiler forces you to handle that case. No more Cannot read property ‘context’ of undefined errors in production. Interestingly, when it comes to handling lists, the type Maybe List
offers two blank states: Nothing
and []
. In this case, it’s recommended to use custom union types to be more expressive.
Type aliases provide an elegant way to reuse existing types and give them a more meaningful name in the given context. However, when it comes to record IDs, they are often best described as custom types. This way the compiler can make sure that only the right type of value can be passed around. Furthermore, opaque types also advocate an API that doesn’t expose implementation details.
Architecture
Elm is not only a language, but also an architecture that was designed to build webapps with. Think of it as TypeScript, React and Redux bundled together. Yet, The Elm Architecture is simple, straight-forward with no hidden magic. Regardless of the complexity of the web app, it all boils down to a Model
data record, an update
and a view
function. Therefore there’s only one way to build a web app with Elm.
The articles If you’re using React, Redux and TypeScript, you would be so happy with Elm! and React Redux Thunk vs Elm are two wonderful comparisions of Elm and an equivalent JavaScript stack.
Code organization
Everyone carries code organization and refactoring best practices they picked up in the past. However, they aren’t necessarily meaningful in Elm. The official guide suggests to focus on finding the right types and data structure first to describe the problem, and worry about file size later. Organize your code around types, not components or the MVC pattern. As I mentioned before, in the end of the day what you need to provide is a single Model
record and the update
and view
functions.
In his talk The life of a file, Evan Czaplicki shows his approach of figuring out when to split code. He illustrates the problem with two checklists that would give the idea for many of abstracting away a checkbox list component. While implementing the business logic, it turns out that eventually there’s very little in common between the two solutions and early abstraction would have lead to poorer code design.
Alex Korban, author of the book Practical Elm, also shares his take on code organization. He points out that “you are still ultimately passing a single update function and a single view function to Html.program” when working with the Elm architecture.
Finally, Richard Feldman, author of elm-spa-example, demonstrates different refactoring techniques that come handy as your code base grows. He discusses narrowing responsibilities on various levels.
Batteries not yet included
Elm is a young language with a young ecosystem. There aren’t many standardized packages available so far. Yet, there are many abandoned packages already that makes it harder for a newbie to evaluate what is helpful. In my experience so far, pulling up a multilingual SPA with lots of forms requires much more developer resources upfront than doing so in a JavaScript framework.
Learning resources
There are many great resources available to study Elm.
- Beginning Elm: a gentle introduction to Elm programming language, a book by Pawan Poudel, is freely available online
- Programming Elm: Build Safe and Maintainable Front-End Applications, a book by Jeremy Fairbank, for those who prefer the classic format
- Elm Weekly, a newsletter by Alex Korban, a source for the news from the Elm ecosystem and further study materials as well.
The Zen of Elm
Inspired by The Zen of Python, I compiled a verse of best practices in Elm. I borrowed a few sentences from the Python one where it was applicable.
- Narrow is better than broad.
- Modules should be built around a central type.
- Explicit is better than implicit.
- Simple is better than complex.
- Opaque is better than transparent.
- Maybe is better than blank.
- Although a custom type might be better than nothing.
- Make impossible states impossible.
- Unhandled cases should never pass silently.
- Unless explicitly silenced.
- There should be one – and preferably only one – obvious way to write it.
- Always prefer qualified names.
- Use custom types for record IDs.
- Data structure should be the last argument of a function.