An Elixir Review: Building on a Solid Foundation

To start, let’s take a step back from Elixir and talk about the OTP. The Open Telecom Platform is a collection of tooling built with Erlang, including the BEAM virtual machine that drives OTP based languages such as Elixir.

The more I learn about OTP and the BEAM, the more excited I become about finding uses for them. There’s a single, focused nature to them that creates something special. Languages seem to be headed in a generalistic direction—everyone wants a language or piece of technology to be used for anything and everything. Consider Golang’s direction toward generics and iterators, or the trend of using JavaScript for every possible task. I think this approach takes more away from technology than it adds.

The Power of Purpose Built Solutions

Returning to the subject at hand: Erlang was built by Ericsson for telecom. They had clear problems they were trying to solve and evolved the language and tooling to better address those problems over time. The core problem they were dealing with—highly available and fault tolerant telephone switching—spawned the need for their concurrency model, which makes me wonder why people speak so highly of Golang’s approach in comparison.

They built a language and tooling for their specific use case and evolved it to continue matching their needs. If you’re trying to create a product for a theoretical consumer, you’ll always miss the mark and be forced into slow iteration cycles as the consumer uses it and attempts to translate changes. But if you are the consumer, you know exactly what the use case is, and you can iterate with accuracy.

The former approach seems to be where much of the industry sits: we use technology that isn’t built for our use case and fight to mold it to our needs, or we go for the low hanging fruit even though it may not be the best long term decision. I can understand why—we don’t have infinite time to learn all possibilities, and we have to play the game of “good enough.” This is where the history of a language becomes so important when evaluating it. The people behind it who have built and used it are crucial, as this dictates what the language will be best suited for without too much molding.

Why This Context Matters

This is supposed to be a review of Elixir—why the rant? I think it’s important to build context around Elixir’s history and bring to light what’s behind it. This isn’t a language built from scratch; it’s an iteration. It’s built on the idea that telecom problems are very similar to web problems. I agree with this premise—both are providing communication services from one place to another. It doesn’t seem far fetched to think that similar issues around high availability, fault tolerance, scaling, and debugging would exist. This is why I think Elixir makes for an incredible foundation for web based software. The portfolio of Elixir’s creator, José Valim, reinforces this stance.

The Ecosystem

Let’s direct our attention to the ecosystem. For the most part, I’ve found libraries for any functionality I’ve been looking for: JSON encoding/decoding, databases, HTTP clients, emailing, caching, etc. The most popular libraries seem to be built around Phoenix (web framework) and Ecto (database management).

To dive into those two for a moment: Phoenix is excellent. The templating system ranks among the best I’ve used, the plug (middleware) system is quite intuitive, and LiveView makes for a nice replacement for JavaScript for most of the simple reactivity you’d want on a web page. For Ecto, I think the game changing feature is the changeset abstraction—it creates a simple and powerful translation layer between user interaction and the database for your data.

When reaching for any non standard library, you’ll quickly see the drop off in adoption. This really makes you hesitant to use other libraries within your projects.

Documentation: A Double Edged Sword

Documentation is often spoken about as a strong suit of Elixir with their HexDocs. To be completely honest, I think it actually becomes a weakness. Different levels of developers rely on different forms of documentation—some want just specifications, while others need more assistance. For me, great documentation gets you up and running quickly and is packed with searchable examples.

I haven’t been able to put my finger on it exactly, but the documentation in the Elixir ecosystem has been a definite pain point that could be a show stopper for newer developers. It feels like most documentation was created by developers and hasn’t been touched by technical writers or instructors. There’s a level of hand holding that I think is missing. Golang suffered from the same problem for a while, where developers really leaned on auto generated documentation without thinking about the developer experience.

Development Tooling

The LSP works, but there’s a weird divide. There are two or three different LSPs: ElixirLS, NextLS, and maybe another. This creates a confusing experience for someone just starting out. It still confuses me when I find myself in my Neovim config. It would be nice to be able to rename symbols (function names), but beyond that, I don’t have too many problems with it—it mostly does what I want. There’s room for improvement.

Type System and Debugging

The type system is really lacking compared to other modern languages. To an extent, I think this is fine—you don’t need a Rust level type system when building web applications. The real pain comes from having to continuously move up and down the chain of commands to remember what’s being passed down, and having to log variables to understand what they are and how they’re structured. The pattern matching system can help with this somewhat, but it doesn’t solve the problem completely. At the time of writing, type inference has been announced, and I’m hoping it will solve most of the issues I have with it.

Debugging is something I might have been spoiled with from using Rust and Golang. The debugging experience is slower for me with Elixir—I find it difficult to pull useful information from error messages. I don’t know if it’s the ordering of the text that’s shown or the noise included, but I’ve found it less concise than the other languages I’ve mentioned.

Error Handling: Too Many Choices

I couldn’t seem to find clear conventions around error handling. Do you pattern match and let it raise an error if it fails? Use a case statement and handle the outcomes? Use a with statement and raise on failed matches? There are many different ways to handle errors, each with trade offs.

Golang’s error handling is often complained about, but what I really love about it is that there’s one definitive way to do it. Yes, there might be better ways or approaches that allow for more flexibility, but it’s concise—you know exactly what’s going to happen, and you’re forced into handling it.

With Elixir, there are conventions like using ! to signify errors being raised, and you can return a tuple with :ok or :error. These work, but it’s still ambiguous when and how something will fail. I understand that Erlang has a “fail fast” philosophy, but this leaves a less than desirable experience for a user who gets a disconnected alert on their LiveView screen.

Concurrency: A Model That Grows on You

The GenServer model makes for an excellent concurrency approach. I haven’t gone too deep into the different supervisor modes yet, but the model of initializing your process, then reacting to messages sent to it, is a pleasure to use. Coming from goroutines and channels, there was—and still is—a bit of a learning curve. It’s really growing on me and changing my general approach to concurrency.

Resources: Quality Learning Materials Available

There are a number of great resources for learning Elixir, both paid and free. I found Elixir School to be an excellent free resource. The Pragmatic Studio’s courses were well worth the money. The “Programming Elixir” and “Programming Phoenix” books are also outstanding resources.

The Bottom Line

From my list of dislikes, you might get the idea that I’m not a fan of this language. In reality, I find this to be a great language that I’ve been using more and more for projects. The language’s shortcomings are more than made up for by its strengths.

LiveView has saved me from having to reach for a JavaScript library and manage state on both ends of the stack. The template engine has made it quite easy to organize layouts and components. Ecto changesets have saved me a ton of time with validation. The functional paradigm as a whole has been a reinvigorating programming experience. Even though I have a lot more to learn about the language, I feel like I’m able to get things done quicker and cleaner than I have previously with other stacks I’ve used (Go/React, Go/HTMX, Rust/HTMX).