axelerator.de

If Kent Beck is your doctor, Elm is your pharmacy

8 minutes
#elm
24 August 2024

Kent Beck is known for his pragmatic approach to software design and his focus on improving the quality of code and the efficiency of development processes. In one of his articles he gives a list of “prescriptions” to write less bug prone code. And now I have a concrete list of reasons why I like writing code in Elm so much.



Elm is a functional language that compiles to JavaScript. It helps you make websites and web apps. It has a strong emphasis on simplicity and quality tooling.

The following Kent Beck quote came up on the Elm Slack the other day:

“You don’t have bugs because you don’t have tests. You have bugs because you write code that makes it easy to have bugs.”

It’s from Kent Beck’s article Scrape Then Paint, and I recommend everyone to read it. Jeremy, who posted it, also said, “This made me think of how nice it is to code in Elm.” I instinctively agree with that but wanted to dig a bit deeper as to why we feel this way about Elm but not, or less, about other programming languages.

When you pitch an alternative programming language to someone, the response is often that they can “already do everything” in the language they’re familiar with. Ultimately, you can solve nearly any problem that you can solve in one language in any other language as well. So the conversation is about how efficiently (with how much effort) can I solve the problem?

If you add the effort of “learning a new programming language” to any programming task, the new language will, of course, always lose against solving the problem in the familiar environment.

Unless you’re right about to retire, I would argue that this is a short-sighted view that suffers gravely from the sunk cost fallacy.

Different languages, frameworks, and libraries make different tradeoffs. Knowing only one way to solve all programming problems makes you a one-trick pony. And even if you don’t use all the tools in your tool belt all the time, being exposed to different approaches in one language will actually widen your horizon in other languages as well.

How Elm Delivers What Kent Prescribes

This is why I continue to pitch the Elm programming language to people. It addresses most of the “prescriptions” that Kent Beck gives in his article at the language level. Let’s take a look at them:

  • Improve cohesion by reducing the size of elements
  • Unify duplicated logic
  • Replace duplicated conditionals with polymorphism
  • Use immutable data structures where feasible to eliminate aliasing errors
  • Simplify control flows where possible
  • Eliminate dead code
  • Rationalize naming so the right names are likelier to be guessed

Improve Cohesion by Reducing the Size of Elements

The Elm guide’s chapter on modules contains a link to this very enlightening talk on how to organize your modules around data types.

Elm has a simple yet powerful module system. It allows pairing data structures with the functions that operate on them without the complicated semantics of many object-oriented languages.

Coupling can be limited easily by explicitly selecting which parts of a module are exposed.

Unify Duplicated Logic

There is nothing inherent to Elm that keeps you from duplicating logic. However, with Elm being a pure functional language, it’s easier to spot and unify duplication.

In imperative languages, you often mix “calculating” and “doing things”—for example, accessing some members in an object and then writing it to the database. In these cases, I have to do the mental pattern matching on “partial” methods to spot and extract some of the logic to be reused.

In Elm, it is a lot more straightforward to extract functions because of the strict types the extracted functions have to follow.

Replace Duplicated Conditionals with Polymorphism

I initially wasn’t sure what he meant by that, so I googled the “principle” and found an article that wonderfully proves my point. Disclaimer: I’m not throwing shade on that particular solution, but the verbosity of the object-oriented solution here looks overwhelmingly complicated to probably any Elm developer.

In Elm, it’s very natural to think in types first—not in conditionals. So a likely solution would be to create a data type type Role = Admin | Subscriber | Other.

Now we can use a case (switch or match in other languages) to decide what to surface in our views.

case user.role of
	Admin -> ..show admin stuff..
	Subscriber -> ..show subscriber stuff..
	Other -> ..

Elm has built-in exhaustiveness checking. That means the program will not compile until you handle all cases. So every time we add or remove a “role,” the compiler will point us to all places we have to touch.

Use immutable data structures where feasible to eliminate aliasing errors

This one is the most straightforward implementation of a “prescription”: Everything is immutable in Elm. This removes the decision on where to use immutable data structures entirely.

The only thing that needs to be mutable in a web application is the state of the user interface aka the DOM. The state of the DOM is completely managed by the Elm runtime. In an Elm app we have one view function that takes the current application state and tells Elm what we want the DOM to look like.

“But how do we change the state?” you ask. We’re going to touch on this in how Elm implements the next prescription.

Simplify Control Flows Where Possible

All Elm apps follow “The Elm Architecture”. Each app has a View function that takes the current state (Model) and generates HTML along with potential user interactions, represented as Msg (messages). When a Msg is triggered, the Update function processes it to produce a new state, which then generates an updated view, maintaining the cycle of unidirectional data flow.

This is where Elm blends between language and framework. The best comparison I can make here is React. Elm has a strict unidirectional information flow. You can’t change the state of anything in Elm yourself. Instead, you emit Messages from your view. And then the Elm runtime calls the update function that you provide as well. It receives the Message, and you can return a new state of your application. The most similar to this pattern in a React world is the use of Redux. The big difference is that it’s part of the language, and you cannot (accidentally) circumvent it.

Read: Avoid This Common React Hook Antipattern about why using useState and useEffect to compute synchronous values will lead to a sub-optimal user experience and subtle bugs that can be hard to debug.

Due to the simplicity of the language and the strictness of the compiler, there are fewer ways to do the same thing. Since React uses JavaScript (or TypeScript), it has less control over the environment it runs in and consequently has a lot more “foot guns,” like, for example, using useEffect and useState together.

In short: Because there are fewer ways to do “the wrong thing,” it’s easier to do the right thing. And because there are fewer ways to control flow, they will most of the time also end up being simpler.

Eliminate Dead Code

elm-review is an Elm packages that analyzes your Elm project, to help find mistakes before your users find them.

The Elm compiler itself does not assist you in detecting unused code. But there is an excellent tool called elm-review that, thanks to the strict type system, can very efficiently detect and remove unused code.

Read more about how people removed thousands of lines of code effortlessly and how Elm’s type system enables great static analysis to make all kinds of optimizations.

Rationalize Naming So the Right Names Are Likelier to Be Guessed

Here again, the Elm compiler only gives a little help in the form of rules about how identifiers of the different syntactic elements (types, functions, variables) can be formatted, but I can’t see how the actual naming can be supported by the language itself. Maybe LLMs will solve this once and for all for us 🙈.

Edit: Jeroen just pointed out that renaming is a very safe operation in Elm since the compiler knows all the places where a function or variable is used. If you use the Elm language server it’s basically free, which of course helps improving naming across your application.

Conclusion

Elm is not suited to solve every programming problem, but it’s the most delightful development experience I’ve had for frontend work. This article only scrapes the list of features that make it nice to use. I’ve taken many of the techniques I was forced to learn for Elm into projects in other languages to benefit from the “prescriptions” Kent Beck proposes.

Here is a list of resources to check out if you want to give it a try: