by innocentoldguy | 2025-10-17 07:20:34+00:00
I'm sorry you had a bad experience, but this doesn't reflect the experience of most Elixir programmers. I'll share my experience as a counterpoint.

> On a production build, stack traces look like Erlang code...

Elixir has the most readable stacktraces of any language I've used. Here's an example (which is color-coded in the terminal for even more clarity and, as you can see, doesn't contain any Erlang code):

== Compilation error in file lib/app_web/live/authentication/settings.ex == ** (MismatchedDelimiterError) mismatched delimiter found on lib/app_web/live/authentication/settings.ex:96:1: error: unexpected reserved word: end │ 4 │ on_mount {AppWeb.UserAuth, :require_sudo_mode │ └ unclosed delimiter ... 96 │ end │ └ mismatched closing delimiter (expected "}") │ └─ lib/app_web/live/authentication/settings.ex:96:1

It's easy to see that the issue is on line 4, and that it is a missing curly brace.

> Then you have macros, which make code unmaintainable...

Elixir gives you full access to the AST, making macros extremely easy to read and reason through. The point of the Lisp-style macros Elixir uses is to simplify your code. If your code becomes unmaintainable due to your use of macros, you're probably misusing them. I'd have to see a sample to make that determination, though.

> Running "mix xref graph" on most Elixir projects shows a spaghetti mess.

Spaghetti? It's a simple two-level tree that is in alphabetical order. Here's an example, and it is like this all the way down:

Compiling 26 files (.ex) Generated stow app lib/app.ex lib/app/accounts.ex ├── lib/app/accounts/user.ex (export) ├── lib/app/accounts/user_notifier.ex ├── lib/app/accounts/user_token.ex (export) └── lib/app/repo.ex ...

To me, this output seems extremely accessible.

> The toolchain has much room for improvement...

Having developed many Windows apps using Borland's tools in the 80s and 90s, I disagree with this statement for these reasons:

• Mix is one of the best and most integrated build tools/task runners I've used. For example, you can create, migrate, and reset databases, execute tests, lint code, generate projects, compile, build assets, install packages, pull dependencies, etc.

• ExUnit is a great testing framework that handles all kinds of tests in an easy-to-read DSL similar to Ruby's RSpec.

• IEx is a fantastic REPL.

• Elixir's debugging tools are excellent. For example, IEx.pry lets you stop all processes and interact with your system in that frozen state in the REPL. You can watch variables, run functions, and even create new functions on the fly to interact with your data to see how it behaves in different scenarios.

> Building a team around Elixir is hard.

Why is it hard? I've worked exclusively on Elixir projects for both start-ups and large companies with hundreds of engineers for over ten years now, and never had a problem with hiring teams.

> And the documentation for most of the projects you will use is full of noise, with few workable examples, grandiose claims of performance, and fantastic treasures, and the articles are a great read if you want to waste your entire evening.

All technical documentation could be improved, but Elixir's is already quite good. See for yourself: https://hexdocs.pm/elixir/1.19.0/Kernel.html.

Furthermore, from within IEx, you can type:

function |> h

for any function and see an explanation of what the function does and examples of how to use it.

> Support for massive concurrency is nice, but you are realistically not going to need it...

Elixir supports minute concurrency as well, and yes, I do need it. For example, in Ruby on Rails, which has a GIL, I'd have to use a gem like Sidekiq to push long-running processes into Redis so they can be processed in the background. In Elixir, I can just run them in a separate, concurrent process, which is simple.

Here's an example that takes a collection of users and a function and then runs each user through that function, each in a separate thread:

defmodule ParallelProcessing do def map(collection_of_users, func) do collection_of_users |> Enum.map(&(Task.async(fn -> func.(&1) end))) |> Enum.map(&Task.await/1) end end

Here's the same Elixir code running in a single thread.

defmodule SequentialProcessing do def map(collection_of_users, func) do collection_of_users |> Enum.map(func) end end

In the first example, I could have 1 user, 1,000 users, or 1,000,000 users, and this code would run as optimally as possible on all the cores in my CPU or across all the cores in all the CPUs in my multi-server BEAM cluster. There are no extra programs or libraries needed. In the second example, users are processed one at a time, similar to languages like Python, Ruby, JavaScript, PHP, and Perl.

Given the simplicity of writing parallel code in Elixir, why would I limit myself to one CPU core to perform a task when I can use all cores simultaneously?

> Or deal with people that won't stop selling you how great the language is.

The reason they won't stop selling you on Elixir is that Elixir is a fantastic language. I hope you take the time to revisit it in the future. It really is much better than most things out there.