Hacker News new | ask | show | jobs
by 29athrowaway 248 days ago
Elixir was not a happy experience.

The language itself is maybe OK but the overall experience is not.

On a production build, stack traces look like Erlang code, which is the weird syntax that Elixir tried to improve upon.

Then you have macros, which make code unmaintainable at the 10k SLOC mark, and increasingly harder to maintain as projects get larger.

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

The toolchain has much room for improvement. Editing, debugging, profiling, unit testing, or pretty much any basic routine development task, involves a tool that's decades behind the state of the art. Even Borland tools from the 80s have a better toolchain.

Building a team around Elixir is hard. You have to train people on the job and they will probably not write idiomatic code that takes advantage of the language. Or deal with people that won't stop selling you how great the language is.

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.

Support for massive concurrency is nice but you are realistically not going to need it. If you do need it then yes, Elixir can be a good tool for the job.

4 comments

I have the opposite experience. Even in production builds, I get Elixir stacktraces, and they're exceptionally helpful. Macros only save me time and LoC, I think maybe once I've struggled with a macro-caused issue (and it was immediately apparent that macros were the issue).

ExUnit has been hands down the most impressive testing library I've ever worked with, and the debugging, profiling, analytics, introspection, observability, etc of the BEAM is unbeatable.

Documentation of elixir, elixir deps, and elixir code is also far and above any language I've ever seen.

And the struggles I had supporting minimal concurrency in python were completely alleviated - so even if you don't need massive concurrency, elixir has a good chance of massively simplifying anything that needs minimal concurrency (which is probably most web related projects).

When you say "above any language I've ever seen", can you be more explicit? Do you have an example?

I have a lot of respect for the community behind it but the experience is still not there.

I think it would be easier for all of us to understand if you elaborated on what you think the issues are. What concrete things are you missing from ExUnit and documentation, what do you wish was different?
You can ask ChatGPT or any major model: from the perspective of an expert technical writer/information architect, how would it evaluate the Elixir documentation, and it will surface key issues.
I don't care what a sycophantic machine "thinks" because I can get any answer I want out of it by subtly changing the phrasing of the question. I thought you had legitimate points to discuss in good faith, not this nonsense.
Take the documentation of GenStage, for example. It is a dry transition between what a stage is into advanced concepts with diagrams are in ASCII. https://hexdocs.pm/gen_stage/GenStage.html

Not only this article dumps significant cognitive load on the reader. It's not well digested and not a soft landing into the subject. Worst of all, many Elixir articles assume familiarity with GenStage.

Compare it to this, which is not the best example but is a much more soft landing. https://doc.akka.io/libraries/akka-core/current/typed/actors...

Even though I do not write Elixir on the job, I often cite their docs as a gold standard. They're succinct, easy to read, and most often show an example of how a method is used. What's better is that the language has documentation as a first-class citizen (it's even testable!) meaning your auto-generated docs will look as nice as the language's.

I complain about OCaml docs all the time. But Elixir? no way.

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

Do you have an example? There are some cases that I can think of where the application dumps some foreign-looking data structures if the release fails to start, but that's very rare and usually the actual error is somewhere near the beginning like "eaddrinuse" here:

    [notice] Application my_app exited: MyApp.Application.start(:normal, []) returned an error: shutdown: failed to start child: MyAppWeb.Endpoint
        ** (EXIT) shutdown: failed to start child: {MyAppWeb.Endpoint, :http}
            ** (EXIT) shutdown: failed to start child: :listener
                ** (EXIT) :eaddrinuse
    Kernel pid terminated (application_controller) ("{application_start_failure,my_app,{{shutdown,{failed_to_start_child,'Elixir.MyAppWeb.Endpoint',{shutdown,{failed_to_start_child,{'Elixir.MyAppWeb.Endpoint',http},{shutdown,{failed_to_start_child,listener,eaddrinuse}}}}}},{'Elixir.MyApp.Application',start,[normal,[]]}}}")
Here's how runtime errors are normally reported (in `MIX_ENV=prod mix release` build):

    10:47:17.229 [error] GenServer {MyApp.Registry, "some-long-running-thing:4196f8ae-c971-439b-854e-5057e45076b9", %{}} terminating
    ** (RuntimeError) attempted to call GenServer #PID<0.2892.0> but no handle_call/3 clause was provided
        (my_app 1.1.0) /home/runner/work/elixir/elixir/lib/elixir/lib/gen_server.ex:895: MyApp.Monitoring.SomeServerMonitor.handle_call/3
        (stdlib 5.2.3.5) gen_server.erl:1131: :gen_server.try_handle_call/4
        (stdlib 5.2.3.5) gen_server.erl:1160: :gen_server.handle_msg/6
        (stdlib 5.2.3.5) proc_lib.erl:241: :proc_lib.init_p_do_apply/3
    Last message (from #PID<0.2891.0>): {:some_unknown_request, %MyApp.Monitoring.Stats{ts: ~U[2025-10-17 08:47:17.229542Z], some_data: 2}}
> Then you have macros, which make code unmaintainable at the 10k SLOC mark, and increasingly harder to maintain as projects get larger.

Absolutely, so don't write macro-heavy code. This is mentioned in the first paragraph of official Macro documentation and documented as an anti-pattern in the official documentation.

> The toolchain has much room for improvement.

I agree that editing experience (due to lacklustre language server support which is now being worked on officially), and debugging tools are lagging behind.

> 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.

I don't agree with this at all.

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.