IMO the most important reason for using Elixir is to take advantage of the underlying Erlang VM (BEAM). The VM gives simple tools with very strong guarantees that make it possible to build highly available systems. IMO HA is a crucial property for web servers, and many other kinds of systems that need to run continuously and perform many simultaneous tasks.
With Erlang, it’s easier to reduce an impact of individual failures (and thus keep providing most of the service with no interruption), as well as detect failures and react to them (thus making the system self heal). I’ve seen examples of both in my production systems, and I think it’s a huge win when a system is able to provide as much service as possible, and resume complete service as soon as possible.
I’m not aware of any other platform which gives guarantees as strong as Erlang. It’s certainly not CLR, nor is it JVM. Some languages and frameworks (e.g. Scala/Akka) might resemble Erlang, but they will not give same guarantees. There will always be some hidden gotcha, or an inferior implementation of an Erlang concept. It’s simply due to the fact that the underlying runtime (e.g. JVM) doesn’t provide the same guarantees. IMO, these days only languages which target BEAM (i.e. Erlang, Elixir, LFE) can get such strong guarantees.
I talked a bit about this in my interview for InfoQ, and also in the first chapter of my book (it’s free, so you don’t need to buy the book to read it ).
I’d say this is in fact a weakness of OO. And I should note I’ve been doing OO since around '95, professionally since '01 (including a lot of C#), while I’ve been working with Erlang since '10. So most of my background is OO and I’ve in fact been a happy practicant of OO, until discovering Erlang.
One problem is with mutable variables: it’s easier to introduce a bug due to unexpected mutation. Just a few weeks ago I spent a few hours debugging why something in my JS code doesn’t work as expected, only to discover that I was mutating a var where I shouldn’t have. That can’t happen if data is guaranteed to be immutable.
In OO you can never be sure about that. You can try to use only immutable vars, but in a large code base it’s hard to be certain it’s always true. Moreover, even if your code is immutable, your dependencies might not be, so there are no strong guarantees.
This might seem like a trivial thing, but it is quite important in a complex code base. Knowing that something can’t change when I call a function (since data mutation is not possible) is a big gain. It makes it easier to reason about the code and gives me bigger confidence.
Finally, FP is more explicit (so less magical) than OO. You call a function, pass some inputs, and get the result. In OO, you call a method, and it’s not clear what does it do to the state of the object. You need to read the implementation to understand what has been changed. In a dynamic language it becomes even worse, since when calling
some_object.some_method(...) I can’t know what’s the type of
some_object. That frequently makes the reading experience harder than it should be.
So while OO can simulate FP, it’s never completely there. After spending a couple of years with FP, my impression is that it’s at least as easy (if not much easier) to manage complex codebase, so I don’t see the need for OO features anymore.