Hacker News new | past | comments | ask | show | jobs | submit login

I’m curious. What exactly is Elixir good for? What do you find in it more appealing than other languages?



I have listed a few points here in the other comment: https://news.ycombinator.com/item?id=37596258

as a more general/subjective/emotional note:

The very pragmatic take on FP makes code in most cases so much easier to reason about. Immutability, no magic coming from some dark corner of the codebase unexpectedly, thinking in steps of data transformation from the get go, piping operator to make this visually pleasant, handling edge cases with pattern matches right at the function head level explicitly, ... and to have stateful stuff, there is OTP right on your fingertips with GenServer/Actor/ETS/... .

And a genserver basically is only just another module that implements certain callbacks and then gets spawned. Modelling some scenarios are are breeze with that. And when I feel like I need some better syntax for some exotic problem I am dealing with, I can bend and stretch my language constructs via hygienic macros right away.

And the whole experience is _extremely_ polished in terms of DX. docs, tests, doctests, interactive shell, a AOT-compiler, a JIT-runtime, and some very high-quality ecosystem bangers, like phoenix/livebook/membrane/boradway/Nx/Axon/... . Its like best of all worlds, and definitively the _very best_ choice for orchestrating stuff, which is a boon when you run into a problem you'd rather not use elixir for. Like building a native extension in rust/zig for some raw CPU task is a breeze.

Its not so much the individual feature (no matter which one) but the overall package.


It's a functional, concurrent-first language. People have talked about the upsides of functional programming to death, either you prefer it or you don't. The concurrency story, though, is a significant advantage exclusive to the BEAM languages. They utilize green threads with message passing queues that we call "processes" (not to be confused with OS-level processes).

A good almost-toy level project I built in Elixir was a Minecraft server proxy. At the top level, you've got a listener process, that waits for an incoming TCP connection, and a Supervisor process, who's responsible for the existing connections. When the listener receives a new TCP connection, it spawns a process under the Supervisor to handle that incoming connection, and goes back to listening. That's about 10 lines for code: 4 in the initiation (function declaration, start Supervisor, start Listener, end), and 6 in the Listener (function declaration, get new TCP connection, spawn process as a child of the Supervisor, assign TCP connection, recurse, end)

The child processes are running a little state machine to do some parsing of the TCP stream. They receive TCP packets as a series of messages, and based on those messages they do various things. Unimportant for the purposes of explanation, except to say that the built-in gen-statem state machine handler simplifies it down to a series of functions that match a message, and produce a new state.

But what if I receive a packet that I don't have a defined response to? Well, I don't need to write any extra code for that. When it fails to pattern match any of my states, the process is going to crash. Then it's children will crash (including the tcp connection, which will be closed by this operation), then it'll tell it's Supervisor "Alas, I am slain". By default, the Supervisor would replace it with a clone, passed the same initial arguments. But that's not the behavior we want here, we want it to just accept that the process has died, and maybe pass an alert to some process for logging or monitoring purposes. That's a built-in option, so it was just an argument in our declaration of the Supervisor in the first place.

The key thing to notice there is that anything that causes that child to crash will resolve the same, safe, way, from unpredicted packets, to the processor core it's running in being struck by a meteor. The other, perfectly fine child processes will keep chugging along, doing their own thing. Likewise, the Supervisor and Listener are protected from each other's failings. If the Listener fails, the top-level application supervisor will replace it, and likewise for the Supervisor for all the connection handlers.

This synnergy of fault tolerance and concurrency is the key idea of Erlang, and by extension Elixir. It lets you code the pretty path, and accept that there are failure modes you can't or don't want to bother anticipating.


Thanks for the detailed response. There’s no other mainstream language which has this fault tolerance built-in, right?


Definitely nothing in the mainstream. The closest you get is a couple of Actor-model libraries for Java and C#, but since languages weren't built from the ground up around it, you end up with a lot of jagged edges that simply don't exist in Erlang or Elixir. Outside of the functional paradigm, there are a lot of things you can express that simply cannot be translated into a distributed environment.

The other "in the same ballpark" thing that is much more mainstream is microservice architecture. They aim for the same goal, of achieving concurrency-modeled parallelism and fault tolerance by translating a system into a distributed one, but since microservices tend to be hacked together with a variety of languages with no intrinsic support for the paradigm, you end up with an "ad hoc informally-specified bug-ridden slow implementation of half of Erlang."

There's a few projects even further outside the mainstream than Erlang and Elixir that are taking a swing at it, but they're all extremely obscure, and IMO haven't yet demonstrated any novelty worth sacrificing the vast libraries available on the BEAM.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: