I'd be curious to hear some criticism, negative experiences, downsides from people with deeper experience. This thread is 100% positivity and praise, which is highly unusual for HN (New Year's afterglow??)
To be clear, my occasional dabbling in Elixir has yet to reveal any major shortcomings so this isn't an elephant in the room kind of situation, just a genuine request from people whose thoughtful opinions I generally appreciate.
Elixir usually receives this much praise, so it's not unusual. Elixir (and Phoenix) are really great. Phoenix is probably my favorite backend framework I've ever used - and I've used a lot. And the community is wonderful.
Now, before I sound too much like Trump, there are some downsides, as always. It is a dynamic language, which can be a downside for some. (Type-checking is possible though.) It's not that fast with pure number-crunching, so it's best for distributed, networked applications. The syntax isn't always quite as nice as Ruby. And the language is relatively complex (more than Go, much less than Ruby/PHP/Scala).
From my experience the positivity is not unwarranted, Elixir is a great, young language with an awesome community (as most new languages have). The creator, Jose Valim, comes across as a very nice person and I feel that a lot of the positive attitude in the community stems from this. As to the language itself, it feels well designed and fun to use (the pipe operator is really cool). Plus it runs on the Erlang VM which is an incredible piece of engineering. Overall a great next language to pick up and experiment with, plus it has reached a good level of maturity so you won't see your code break from one release to another.
We are rewriting an internal Rails application in Elixir as a test project. I like what I see so far. Elixir/Phoenix promise it to be like Rails but 10x faster with 1000x less memory use and better concurrency. Our preliminary data is that it does deliver that.
But there are definitely problems.
The only one that really concerns me is Ecto and its integration in Phoenix. It makes simple things hard and hard things impossible.
More generally, I don't get the feeling that Phoenix was "extracted from a production web app" like Rails was. With Rails you knew there was at least one app, Basecamp, that worked on top of it. With Phoenix I am not so sure. This is a very preliminary opinion, but first impressions matter.
This specifically applies to Ecto and it's Phoenix integration. The rest of Phoenix seems perfectly nice, and fixes a lot of Rails' warts.
The rest of downsides are not a big deal, and time will fix them:
There is no installed base to speak of. You'll be the first one to run into many problems. 3rd part libraries are not there/not mature. It's missing a lot of basic scripting language functionality (e.g. wrappers over libc functions). Some code comes out verbose and hides the intent (though most of it is surprisingly nice, often as good or better than Ruby).
I would love to hear more about the Ecto/Phoenix integration and what feels hard and what feels impossible. Feel free to shot me an e-mail or ping me on IRC.
I don't think the point of Phoenix is to be like Rails. It always struck me as being aligned more toward something like Sinatra. ?
One thing Elixir definitely needs is a killer app or framework that really plays to the strengths of its Erlang underpinnings. A web framework is probably not that app/framework.
Those already exist with extreme maturity in other ecosystems. Scaling the web framework part is pretty easy since, unless you royally screwed up your design, you should be able to just add more stateless web heads to service more requests and you almost always should end up ultimately bottlenecked at the persistence layer, not in the request router/processor.
I just don't know what that killer app/framework will be.
Phoenix is much closer to Rails in terms of its goals than it is to Sinatra. It's a full-featured framework with asset handling, a recommended file layout, template discovery and compilation, generators, etc.
As far as I can tell, most projects one might use Sinatra for are done in Elixir by just working with Plug directly (Plug being the Elixir analogue to Ruby's Rack). I'm sure there are many lightweight web frameworks for Elixir out there, but I haven't seen any with Sinatra's level of ubiquity.
I think a lot of it is Elixir's niche, too. Unlike most of the languages talked about on HN, the BEAM makes no bones about what considerations it's made, and what sorts of problems it is and is not intended for.
While there are a few just general warts to be found, they tend to either be obviously surface level things ("I don't like ~this~ bit of syntax"), or so deep you are unlikely to hit them (the issue with large binaries being stored off heap, and reference counted, and references only being collected when an individual process heap collects, leading to certain edge cases where large binaries remain uncollected even when they should be collected, leading to a memory leak that can eventually OOM you, for instance. See https://blog.heroku.com/archives/2013/11/7/logplex-down-the-... ).
But aside from that, basically everything is intentionally considered and sensible from a "fault tolerance" perspective. And that makes it hard to criticize the language/platform; for a general purpose language, anything that doesn't jibe with your use case you can complain about. For a biased language like this, intending to solve a specific kind of problem, any decisions the language makes that don't conform to your problem domain are issues with you trying to use it for your problem domain, rather than issues with the language.
Example: The BEAM isn't fast at pure number crunching; rather than complaining about how slow it is, anyone looking at a problem that requires a lot of number crunching will, rightfully, say "This is not the tool for the job".
So for those problems where it -is- the right tool for the job, the cohesion between tool and problem is excellent, because the language doesn't try to be all things to all people.
You could do a lot of fast number crunching if you wanted to. It just gets tricky to implement without screwing with the primary scheduler pool.
I've written many NIFs for Erlang that call out to high-performance C routines when "number crunching" is most important. I've also done it to side-step memory allocator churn for implementing binary protocols that support zero-copy semantics like Capnp by using the NIF as a kind of escape hatch where I can fiddle bits in a buffer directly.
In fact, somewhat lazily since it's low priority, I'm trying to create a decent Rust wrapper for the NIF interface so that I can attempt to get more compile time safety out of the dangerous stuff I do outside BEAM's safety net.
Other than being dangerous, since a segfault will bring down the whole VM, the other big issue is these calls block the scheduler (basically breaking the VM's preemptive abilities and creating the same limitations Akka on the JVM has when you block all the threads in an ActorSystem). However, now that R18 has dirty-scheduler support it's much easier to avoid this problem by quarantining that stuff on threads that don't muck with executing normal bytecode.
You're not using Erlang/Elixir for number crunching; you're using it for orchestration of your C code.
That's my point; in a niche language, you only try to use it to solve the problems it claims or you have reason to believe it to be good at, thus, its limitations (both declared and any you run into outside of the claims it makes) are dismissed with "you're using the wrong tool", a fact the developer usually realizes themselves, and does not treat as a deficiency of the language.
If you had reason to believe Erlang was good at number crunching, and wrote code to crunch numbers in it, and then found it wasn't, you'd complain about how slow it is. Because you never had that expectation, because Erlang flat out says it's not good at that, you knew to instead write NIFs. The thought "this language should be faster" never entered your mind, because that kind of performance is not the goal of the language. Instead you used it where its reliability and scalability come into play, things it is good at, and offloaded the number crunching to a language better suited for it.
That's a fair point. Though the thought, "this language should be faster" has crossed my mind many times during the pain of all that other mucking about I had to do :-)
I'm timidly hopeful that projects like ErLLVM and BEAMJIT will eventually produce enough improvement to BEAM performance for computational workloads that using FFI escape hatches only becomes necessary in the most extreme fringe of circumstances.
The fact that the NIF interface is so damned straight forward to work with compared to FFI implementations in other systems does ease the pain a bit of having to step out to consume it more often than I might its analogue in other languages.
I write both Erlang and Elixir. One negative thing I'll say about Elixir is that it sometimes feels like a Ruby-shaped DSL obfuscating the Erlang I would otherwise be writing more clearly.
I don't like optional syntax. Like dropping parens for argument lists, etc. Elixir adapted this quality of having varieties of optional syntax from Ruby-land, and I think it makes things harder to read and writing is sometimes ambiguous. So for my part I just avoid it and write out the explicit form every time.
I also think the weird variable rebinding doesn't really solve an interesting problem, but also ends up sometimes making things more confusing when reading code in projects written by people who leverage it all the time. I happen to like explicitly naming reused constructs with different names each time they're bound. 1) it makes is explicit which version of a thing you're working with (e.g. Time1, Time2, etc.), and 2) it makes writing the code a lot more similar to how one might sketch up a more formal construction (e.g. T, T', etc.). So here again I just write out the explicit form like I would have in Erlang. To me the very existence of the "pin operator" is kind of an indicator that rebinding was a weird design choice to solve what seems like a non-problem.
Lastly there's a fair bit of boilerplate in Erlang when using the OTP patterns, and in Elixir there are often metaprogramming constructs that attempt to provide some sugar or some shortcuts to mitigate that, but I find this is really hard for newcomers when trying to understand how something like say Supervisors work, because in Erlang everything that's going on is explicit and well documented, but in Elixir the connections and relationships between concepts, callbacks, etc. isn't quite as clearly spelled out and the docs don't really help much (in fact they make it more confusing because at the same time they introduce Supervisors they also introduce GenServers, and give the false impression those concepts are tightly coupled). I often point learners at the Erlang docs and give some instruction around OTP concepts in Erlang so that they have an easier time mapping them in Elixir.
That said there are a ton of things I really like about Elixir. Starting with Mix. Oh my god how I love Mix. Having the language come bundled with an easy to use build, dependency, and release management tool is unbelievably nice. I also really like the fact that a lot of effort went into organizing the standard library so it's coherent. For the most part things are pretty discoverable. Functions that take state or context to modify pretty much always have it passed in in the same location in the argument list. Whereas in Erlang it's different depending on which module you're in, and sometimes not even consistent inside that same module. Also it's nearly impossible to know where to go look for a particular piece of functionality in Erlang due to the way the organization of the standard library accreted over many many years.
I've really enjoyed adding Elixir to my stable of tools, and there are a couple projects at work will definitely be made significantly richer by the fact that they're built in Elixir.
Oh, and I also really miss that variables in Erlang all begin with a Capital letter. If Erlang had Elixir's atom syntax, or Elixir had Erlangs variable syntax, I'd be in code heaven.
Then functions, variables, and atoms would all be trivially easy to identify at a glance.
To be clear, my occasional dabbling in Elixir has yet to reveal any major shortcomings so this isn't an elephant in the room kind of situation, just a genuine request from people whose thoughtful opinions I generally appreciate.