As someone with primarily a Ruby / Rails background, I’m choosing a language for a new API project and considering Elixir. I’m interested in hearing some counterpoints to Elixir, especially in how a smaller ecosystem Of 3rd party libraries slowed down development.
Not Elixir, but a cautionary tale from our Erlang project. ~8 years ago a our IoT backend was written in Erlang, this was the early days of IoT, so sure it made sense as a technology, could scale well, handle all the symmetric sessions, etc. It was a good tool for the job and in theory could scale well.
But, there's always a but. We're a Python shop on the backend and C on embedded devices. Engineers move on, there some lean times, and after a few years there was no one left who could support an Erlang system. So we hired for the position, and it's key infrastructure, but not really a full time project, so we hired a Python+Erland person.
But! Now they're on call 24/7 since the regular on call roster are python people and when the Erlang service goes wrong, it's call the one engineer. So, do you hire 2 or 3 people to support the service? No, you design it out. At the time is was 2018, and IoT services were a plenty, so we could use an existing service and python.
Another way of looking at it, let's say it's a critical service and you need at least 3 people to support it/be on call. If each engineer costs $200k/year, that's $600k/year. Does this new language save you that much over using a more generic and widly known language in the org?
Erlang / Elixir are languages that are easy to begin learning, but difficult to master. In our case, the team was able to get the basics up and running quickly, but the service became increasingly difficult to manage as our product requirements expanded beyond the strengths of the Erlang ecosystem's core competencies.
In our case, the original team of developers was excited to write a greenfield service in Erlang, but much less excited to maintain and scale it long-term. Some of them used their experience to speak at Erlang conferences, which led to them being recruited away by companies who were desperate for Erlang developers to maintain their legacy code after the core team left. Ironic.
At first we tried to train existing engineers on Erlang. Doesn't work. Not because Erlang is bad, but because people don't want their companies changing the direction of their career development just to support someone else's legacy project that requires an on-call team to support.
So your two options are: Hire at least 2, preferably 3 Erlang engineers for the reasons discussed above. Harder than it sounds, because AFAICT there are a lot of companies with 2010-era Erlang backends that made sense at the time but now require dedicated maintenance engineers.
Or you can rewrite it in a more common language, which is the route we chose. Rewrites are a difficult decision for well-known reasons, but ultimately it was the correct choice. Being able to drop 10+ engineers into a project as needed is a huge accelerator compared to funneling 100% of your work through the 2 Erlang gurus in the corner who maintain a codebase that no one else wants to touch.
Hiring is nearly impossible, come support our legacy system, and what ever you do, don't introduce Erlang into any other part of the company. Why? We feel burnt on this one project, and god forbid we get any deeper than we are now. What this isn't appealing?
If you have $200k "python engineers" on the payroll who wouldn't jump at the opportunity to do some additional Erlang, maybe it's time to reconsider your hiring practices and that is the real cautionary tale.
Back in the day people would balk at hiring Python programmers saying, "there are so many more Java programmers", and I used to say, "Why would you hire a Java programmer who was unwilling or unable to learn Python?"
Same logic applies here: Why would you hire a Python programmer who was unwilling or unable to learn Erlang? (Especially if you're going to pay them to do it!)
If you can't switch languages you're a kind of technician not a programmer.
FWIW, having done it professionally for ~15 years I'll never write another large project in Python again. Erlang's runtime is so much better than Python's it's just ridiculous. I feel stupid for not realizing this sooner.
Ah, sure they might be willing, but as a lead I'm not willing to invest in it.
1. I'm not sure I want someone who's got 4 weeks of experience in a language working on system critical infrastructure. Pick your time, does it take 6mo?
2. They have existing work, learning and working on an entire new language _AND_ new project is a huge task. Especially for non-trivial projects.
We could, but in our case it was a better engineering decision was to retire the old system and move on.
I'm just beginning to get interested in the Erlang ecosystem. Could you please add more color to this and give some examples where Erlang shines? IIRC, Whatsapp backend used to be on Erlang.
Key phrase right there. Because it doesn’t matter what language a system is written in: if management doesn’t bother to hire replacements until after everyone who understands that system and why it works the way it does has already left, of course it’s going to crater shortly thereafter.
That’s not a language problem, that’s a business continuity problem. And it’s lamentably common as dirt.
Any monkey can learn to churn code. Learning the business domain, what its problems are, and how to solve them; that’s the bit that actually matters.
PEBKAC, at every level.
If you're an up-and-coming Golang developer, how happy are you going to be when your company takes you off of a Golang project and makes you the new Erlang person?
Now how happy are you going to be when you're informed that you're learning Erlang to maintain an old, flakey codebase written by 2 guys who left the company and are now unreachable?
Oh, and you're going to need to be on call for this because no one else knows how to fix it.
You don't need someone "learning" Erlang. You need someone who works with it.
For example good static typing is not just about adding type declarations but about taking advantage of the type system to the right degree. (Like don't ever overdo it, it's killing productivity most times).
But what degree falls into the productivity boosting spot and from which point on it will hinder productivity depends a lot on the team.
The problem often starts when the team changes and e.g. a pure scala/rust/C++/Ocml team get people cross entering from languages without any usefull static typing.
What had been reasonable and useful might now be a hindrance to the team!
I ran into a situation where this caused massive delay. (Well through the actual root cause was miscommunications, people not complaining about problems and instead trying to find ways to fix them behind the back of other people force pushing nice looking simple solutions which just happen to fail many edge cases, killed the write, refactor, test cycle and increase maintenance cost longtime :( )
So well 1st never overuse type systems. 2nd communicate problems, especially if you have problems understanding anything even if it just takes longer then you think it should.
This is a great question to ask. It’s worth extending beyond language to frameworks and libraries as well.
It’s important to optimize for the development experience across all the apps in a team. Especially once the excitement of using a new language is gone and there are bugs to fix.
I‘ve rewritten a few projects in a common language for the org. Each time the biggest benefit was how straightforward it became to work on. Problems you’ve found somewhere else become easy to spot (and search for). Writing another test is the same as the last project you had open. Anyone in the org can contribute with a minimal learning curve. This can be the difference in getting bugs fixed in a day or a month.
Then consider how hard it would be to get more people that have used such language/ecosystem in anger.
Side note: Pagerduty comes with many hilarious sounds, including a very realistic meow. Thus we practice meow driven development: I don't want my phone to meow desperately.
On the flip side, I prototyped out a drone flight controller in Elixir thinking that the “fail and restart” portion would be awesome for reliability (if the RTK receiver craps out and restarts, no big deal, the EKF can just fall back to using the IMU until the RTK is back online). That part of it all worked great, but doing real-time numerical computing in Elixir is painful. As was generating the signals for the ESCs. I was making C modules to try to talk to the ESCs, and errors in those modules had the ability to corrupt the BEAM. I started looking at ports too (where the C code runs as a separate process), but gave up when it felt like everything was feeling way too complex and fragile. Let it crash is great, but “it crashes all the time” is not. I could have probably gotten it working, but... not a great domain for it.
let it crash != robustness not required
Let it crash is an engineering design for systems large enough that statistically unlikely failures occur regularly. Like a data center or the original use case, telephone switch centers.Even if let it crash worked, it seems philosophically inappropriate for a drone controller.
In embedded systems, certainly safety-critical ones, you have to consider the possibility of a reset path at all points in your flow of control. Your code resetting is a valid flow of control.
This is absolutely necessary to get a properly robust system.
Your drone system needs to be able to handle a reset at any point and still maintain adequate control of the aircraft. This is "Let it crash".
"Let it crash" is essentially the same thing. Take account of the necessity of reset, because you can't stop it happening.
What is completely incompatible with disposable workers is complex numeric processing. Unless you can segment it with small non-communicating unities, of course. That's the kind of application that asks for a low level language.
Erlang is great at soft real-time but does not work at all for hard real-time, there simply are no guarantees, just very good average latency.
If you want hard real time (such as for in-flight control of a drone) then you should look elsewhere.
Any codebase beyond fairly small will be harder and harder to work with to an unreasonable degree, in my experience, and any perceived "velocity" gained from the dynamic nature of it is paid for doubly so by the lack of safety you get beyond toy projects.
I'm only slightly more for Erlang as a choice mostly because it's simpler than Elixir and doesn't have as much obfuscation of logic added to it that Elixir does, but in reality it's also a bad choice for any bigger system. The runtime is absolutely great, but the languages on it are generally not good enough.
[1] This looks fine to me? https://elixir-lang.org/getting-started/typespecs-and-behavi...
[2] I have limited experience with Haskell, but my impression is that the type capabilities do actually allow you to forego testing in a lot of cases. The next best static type system I've used is C#'s, which I used professionally for years, and in C# I think automated testing is still very necessary.
EDIT: I regret even talking about type checking here--that's not my point. My point is that for modeling domains, Elixir's type system is totally adequate. I'll agree that Elixir's type checking could be better, but that's not what the comment I was responding to said.
Specs/contracts are cool but ultimately don't afford the same kind of descriptive and expressive power that a static type system does.
> static types in most languages[2] don't reduce this burden much: there isn't an alternative to thorough testing.
However, there definitely is a burden about how much testing you have to write. I generally don't want to have to test every branch of my program to make sure a string doesn't slip through where an int should be or that variables are initialized and not null, etc.
Most of this is incorrect, both for and against your argument. Elixir has a very incapable and incomplete type system. It's optional but can be checked via `dialyzer` but never confidently and exhaustively checked because `dialyzer` will sometimes plain not work, sometimes be foiled by bad library specs and sometimes fall back to "Everything must be OK because I can't prove this incorrect".
All in all it's not a useful substitute for a real type system used to model things.
Type specs aren't useful for domain modelling but serve mostly as extra documentation more than anything.
Testing is important no matter what language you use (except maybe dependently typed ones, I don't know?). It's not a substitute for a good type system. Even disregarding type-directed property tests with automatically generated test cases, there's a lot of value to be had in tests that actually just test logic, not basic types.
Maybe ruby is different than python in this regard, but with python I see a lot of “assert isinstance(arg, dict)” in functions which would not be necessary with type checking.
Feeding an unexpected type into a function, and having it carry on as normal, is really scary.
Static languages fell out of favour last time round the dynamic/static merry-go-round because the typing started to get in the way. You ended up writing more type boilerplate than actual code - and not breaking your methods/functions down into smaller functions to avoid having to write out even more type boilerplate.
I often wonder whether type annotations that allow a processor to generate tests that can integrate into your test suite would be a useful thing to have. Sort of like a C preprocessor.
Then you could add ML type headers to Elixir functions and get a set of ExUnit tests back.
Static type checking does have some advantages, it can ease some of the low level testing burden. For instance, using a tool like Dialyxir (dialyzer) is helpful in elixir to give you that extra security of static type checking.
For me it’s always ended up being that I’m more productive with dynamically typed languages and the flexibility they offer.
How does pagerduty deal with it, I wonder.
The first project was an API that was intended to serve as a middleman between a few legacy services. Basically the company that hired us was building a new JSON API but didn't want to rewrite all their old code and our job was to consume the output from their ugly legacy APIs and produce nice JSON.
Elixir/Phoenix worked for the first service, but after that we ran into problem after problem. One of the legacy services used HMAC authentication and Phoenix (at the time?) didn't really support that. We were eventually able to hack around it without too much pain. Another service used XML and we had a lot of trouble finding XML libraries. There were some but nothing remotely as nice as Nokogiri. While all of this was happening, adding to our frustrations, a new version of Phoenix was released with a lot of changes. The only documentation for how to migrate an existing app was a typo laden GitHub Gist by the author of Phoenix. The final straw was when we got a call from the client about another service we had to integrate. This one used JSON but the order of the keys was important (please for the love of whatever god you believe in never do this). When we had to decide whether to write a custom JSON parser or move to Ruby, we rewrote the thing in Rails.
The second project was already a few thousand lines of code when we started working on it. It was a dumpster fire of a project but the devs who built it praised Elixir to the skies and said it was way better than their old Rails version. So take that for what it's worth.
We've decided not to do any new projects in Elixir for now but we're leaving it open for the future. It is a nice language to work with and, as you can see from the other comments, there are some projects where it shines.
I have two suggestions for you:
1. Talk to people in real life about Elixir/Phoenix. Most other devs I talk to in person at other agencies that tried Elixir have (surprisingly) neutral or slightly negative feelings about Elixir/Phoenix. Some percentage of people on Hacker News absolutely love Elixir (and often hate Ruby/Rails) and they usually flood the comment section of any Elixir article.
2. If you don't fully know the problem domain, use something with good library support. I don't know what your project is, but if you control the front and back ends or if you know exactly what features you'll need, there will probably be no problems choosing Elixir. Plus, it's always fun to learn something new. If your project is "some kind of API that needs to do X and maybe some other things we don't know yet" I would stick with Rails (or Python/Java/something with a lot of libraries). The worst place to be is not being able to do something simple because of a lack of library support and having to explain to your client/manager/coworker that it's because you read on Hacker News that writing "a |> b" instead of "a.b" is the future.
I think this is completely fair. As my friend says, "I don't know what library I'll need for every project, but I know where will be one for Python."
> Most other devs I talk to in person at other agencies that tried Elixir have (surprisingly) neutral or slightly negative feelings about Elixir/Phoenix. Some percentage of people on Hacker News absolutely love Elixir (and often hate Ruby/Rails) and they usually flood the comment section of any Elixir article.
This mirrors our experience, albeit with Erlang.
Every time I try to talk about negative experiences with Erlang online, people come out of the woodwork to pick apart my stories and assume we were just incompetent. Erlang/Elixir have an almost mythical reputation in online communities. A lot of people have completed some basic Erlang or Elixir tutorials and assume they're just a few steps away from running a massive WhatsApp-style backend, but it's not that simple.
If a company wants to go all-in on Erlang/Elixir and maintain a staff of multiple Erlang/Elixir devs for the lifetime of the service, that's one thing. However, decisions to write core features in these less popular languages should not be taken lightly.
My boss really wanted to use Elixir. Our CTO said he could only if he could convince 3 senior engineers and one ops person to agree to work on and support it. So he flew a whole team of us to ElixirConf. He organized a hackathon team around it. We rewrote some non-critical some services in it. All on company time and money.
At the end, none of the engineers could sign off on using it professionally at the company. Elixir is a cool language and it was a fun learning experience, but it's such a shoddy abstraction over Erlang and that's not a pleasant language. The operational complexity is really high, and it didn't solve our problems better than our current core languages did. The ecosystem was also really lacking in unavoidable ways for us, which meant we spent more time developing libraries that would've existed in any other ecosystem than actually writing application logic.
My big problem is with companies that don't do any exploratory work like that. Write a prototype in new language/framework with the explicit idea enforced in project management that it's going to be a throwaway. Do a study through hackathon etc like your team did.
Don't just go on meme-driven development, which covers "CTO read a fancy shmancy content marketing piece in colourful magazine while on the flight"
These days there's a multitude of both XML and JSON libs for Elixir.
The language itself is pretty small and I felt like the first stage was pretty easy.
Learning the OTP is a much bigger challenge.
At the same time, I think you can gain a lot of the benefits of Elixir and the BEAM without needing to be an OTP expert, because frameworks like Phoenix leverage it for you.
In my experience, things like pattern matching and immutable data really contribute to a more maintainable code base, whether you find you need the actor model or not.
Part 2 learning Phoenix and OTP has been a long, ongoing thing. If I did it full time it would be done, but doing it off and on part time it is a long slog.
Elixir and its ilk tends to make it easy to do what you want to do.
In Go you have to re-write things over and over because the abstractions are low, but they're easy to do. In Elixir you can use the built in libs/abstractions, but you have have no idea how they work.
This is a gross simplification of both sides of course, but I often see Go-people arguing a different point than their counterpoint in <other language> is trying to make.
Debugging it it a nightmare especially when you have to roll up your sleeves and go into Erlang.
Probably the biggest advantage of Elixir over Ruby is the runtime. The Erlang VM has proper concurrency, and it provides really nice primitives for working with it.
The ways in which Elixir sucks tend to be the same as Ruby: dynamic types, somewhat slow runtime (compared to C, Java, Rust, Go, etc). I'd also add that library support is still a bit weak with Elixir, but it's always getting better.
It's only slow if you are comparing a single-threaded operations, Erlang VM is designed to scale horizontally, not to be fast with one thread.
Where Phoenix really shines is its support for fancy stuff like Websockets and distributed messaging in your backend. Trying to do these things in Rails leaves much to be desired.
+ Phoenix _better_ than Rails, which is very surprising
- less packages available, though almost everything covered by at least one package. ( hex.pm vs rubygems.org )
- Debugging is hard. You can’t throw a REPL wherever your want in your code and pipe states are hard to inspect.
- Serving static files in Phoenix is oddly super hacky.
- Deploy is hard. Mostly by the lack of support out there in term of documentation and services.
abc
|> foo()
|> bar()
to abc |> IO.inspect(label: "a")
|> foo() |> IO.inspect(label: "b")
|> bar() |> IO.inspect(label: "c")
If you use vscode, this user snippet will inject this (with line numbers as labels) when you type "ins <tab>", which can be used to great effect with multiline cursors. Since line numbers are usually the same length in any given block of code, it's equally easy to ninja those IO.inspects out with the delete key. https://gist.github.com/ityonemo/00875891748bed3ee68e5f1b75c...2- agreed
3- Mix releases have solved 80% of this problem, for me. Since it's trivially extensible, I do a series of compile-time checks, include verifying that the current branch is on master and correctly git-tagged; and then uploading to Amazon S3. I haven't done this yet, but it's going to eventually trigger an automated blue-green deploy.
I clearly hadn't bothered to read the 'Getting Started - Debugging' page on the Elixir website at any point.
In my experience, I found I could throw a `require IEx; IEx.pry` pretty much anywhere in the code to get a REPL. This seemed like a unique superpower of Elixir to me. Where have you found that this isn't possible?
I agree with the pipe states point.
My main complaint with Elixir is that I'd discover really stupid mistakes with run time errors. The sort of things a bunch of other languages I've used would preclude at compile time. I never dug into more advanced debugging or Dialyzer though.
What stopped us was not so much the lack of libraries, but programming ergonomics. E.g. when using keyword lists for function options, callers need to pass the keywords in the exact same order. Also mocking was difficult in tests.
Elixir is a great project with a friendly community, it just didn't work for us. YMMV so by all means try it out if you're interested!
That's not generally true in the language. Were you trying to pattern match options in your own functions? Newbies to the language can get excited about the pattern matching feature and try to apply it in places where it's not right (well I may be projecting there).
> Also mocking was difficult in tests.
This is true. Were you using Mox? Mox makes things considerably easier at the cost of some boilerplate, with the added benefit that you can run concurrent mocks. You also have to not think of mocks in elixir like Ruby, they are very different.
I'm sorry I wish someone more experienced with Elixir could have helped onboard you.
This would've been fixed by creating a map from the passed in list, which would remove the order dependency but still keep the ergonomics of a keyword list, or just querying the keyword-list with `Keyword.*` functions. Not that big of an issue, to be honest, and not a showstopper. There are much bigger issues with the language, but Ruby doesn't offer any upsides in those cases either.
Proplists should be replaced with maps pretty much everywhere.
>Also mocking was difficult in tests
Interesting, what exactly was difficult? You just replace one function with another.
I'm not the person you're asking, but iirc, that replacement is global - so once you replace function/module X with MockX, you can't test X itself, nor anything else that relies on X.
I came from a Rails background too and Elixir is so much better than even Ruby (which I'm a huge fan of already) simply because it forces you to think in terms of functions rather than OOP based. Most people who try and give up early on Elixir - including myself (I gave up initially after a few weeks actually, before going back to it again) was simply because I was trying to code things like I would normally do in an OOP setting.
For example, you don't use FOR loops in Elixir, you don't nest switch cases. If you try to be in an OOP mindset with this language, you WILL fail. And that's a good thing. The language constantly challenges you to rethink a lot about your code. And it always results in better code when you do it functionally. Eg. using pattern matching instead of multiple if else clauses.
The downside of Elixir is the ecosystem isn't as mature as Ruby, so, for some of the things you're trying to accomplish, you won't find ready made libraries, or they won't be maintained anymore. Sometimes, you will be forced to write something from scratch and that can cost valuable developer time. Integration of stuff like GraphQL can be too verbose compared to other programming environments as well. But, the language itself? It's hard to complain. My entire consulting career has been built around Elixir since I made the switch ~3+ years ago. That's how good it is.
Hard disagree here. Functional code is often better, but certainly not always.
In order to tell the type of various method signatures, it seems like you have to go looking at other areas of your program. I’m aware that it’s possible to annotate method signatures, but this still didn’t lead me to feel secure. I think I was hoping for/expecting something more Haskell-like.
It’s easier to recognize that a feature (dynamic typing) is an issue in some applications after trying it for a while rather than just taking a theoretical view. I’ve sometimes misunderstood how a feature works in practice by relying too much on my theoretical understanding.
Also, because Elixir allows type annotations, I was thinking that I might not need static typing. It turned out (at least for me) that I would prefer to have a language that insists on type annotations by default rather than allowing them as options.
1. Elixir is still a pretty leaky abstraction over Erlang. In my experience it's not enough to learn just Elixir, I regularly had to dive into Erlang library source code to debug or answer questions. This somewhat negates the benefit of a small, stripped down syntax when you often have to learn another one in conjunction.
2. Maturity of ecosystem. This should improve over time but I've had challenges finding high quality libraries, especially for things like database connection drivers or making network requests. It's often hard to tell how well-supported or complete a library is and regressions were a regular occurrence.
3. Documentation. In practice I rarely found official documentation complete or even helpful (outside of big projects like Phoenix / Ecto). Even core Erlang libraries had surprising chunks missing. It's been awhile but I remember it being very hard to figure out what options were supported in Erlang's TLS wrapper. I ended up stitching together pieces from the OpenSSL documentation, Erlang source code, and lots of trial and error.
4. OTP overlap with other scheduling systems. This isn't a design flaw as much as a potential footgun depending on how you deploy Elixir code, but there is a lot of overlap between the cluster management support in Erlang/OTP and, for example, container orchestration in Kubernetes. Both support scheduling some concept of tasks, configuring redundant copies of tasks, detecting problems, and restarting tasks according to policy. Deploying an OTP application on top of Kubernetes (on top of Linux) results in 3 layers of OS-like task scheduling to learn, teach, maintain, and debug through, all with distinct tooling, terminology, and limitations.
All in all, I found Erlang/OTP to be a pretty interesting and compelling platform, especially for certain special purpose applications. If I ever use it again I'll probably skip the extra layer of Elixir syntax and write straight Erlang.
I've certainly found it to be much more specialist than I think many cheerleaders would have it. It can do most things out of the box (and that I guess is the point of OTP), but to make it do those things requires quite a deep understanding of the platform (and that it is a platform, that the language is just a way to interact with that platform). Elixir is my favourite of any language I've worked with, so I'm not down on it, but it's not general purpose
I've gotten to the point I can adapt to missing libraries but what it does at runtime is very different from anything else.
I don't do a lot of server side node.js but I have a lot of confidence if someone called me at midnight saying their node service was down I could jump in and start making progress fast. I think it would take some time running Erlang in production to get there.
1. Dialyzer is just a huge piece of shit. It's an incredibly impressive piece of shit, and makes it possible to pretend that Elixir supports typing, but it has so many edge cases that we've run into them writing even trivial code - and it misses a lot of things that, e.g., Java or Typescript would have no problems with.
2. Testing is kind of fiddly and annoying. It's very difficult to consistently mock things out. And yes, yes, it's a functional language so in theory every function should be small and trivially testable, but unless you're writing toy code, eventually you will have dependencies you would like to mock out in tests, and you will have complicated business logic living _somewhere_.
I do regret choosing Elixir at times for the following reasons:
1. I am new to the language (I've been using it for "years" but only very part time (not part of my day job)). While the functional approach does lead to much more elegant code, it can also be super esoteric to me as I'm new to and somewhat uncomfortable with the idea of recursion for everything. There are pre-written macros to help of course, but it's just training wheels. This will improve with time and experience. I also don't fully grok how to write my own macros. What I really need to do is spend a couple of days deep diving until I get it.
2. Phoenix is changing quickly (tho slowing down). I wrote some stuff if Phoenix 1.2, 1.3, and the upgrade path is a bit involved (no more so than a rails upgrade was tho). I suspect it's stabilizing, but when I first created one project it used brunch for assets, now it uses webpack (which is a good choice, but as a non-frontend person I'm not well equipped to make the change myself to upgrade my project). 1.3 (IIRC) also dropped models, so that required some refactor to work as a 1.3 project.
3. Deployment changes quite a bit. The old rules for Ruby go out the window. You can still treat your Phoenix app like a rails app if you want, but it's like buying a Ferrari to drive around the neighborhood at 25 mph. This takes some learning, and while much of the old rules about scalability, etc are still applicable, they do change a bit and it requires making new mistakes and learning from them.
4. I get irritated now when working with non-functional code in other languages. My tolerance for side-effects is down to nearly zero, and I get irritated when there's a dozen lines to do something that in Elixir is a couple of pipes.
That said, overall I much prefer Phoenix/Elixir. None of the downsides are the fault of Elixir, so I believe with time It'll be a no-brainer. I am already at the point where I don't start any green field projects in Rails (tho I do use Ruby extensively for scripting as it's by far the best scripting language IMHO).
I can't tolerate OOP stuff that `obj1.calls(obj2)` and changes `obj2` anymore. I didn't care before, now it makes me want to refactor everything or not write it at all.
Using Elixir kind of made me not want to use anything else.
I really don't know what I'd prefer to it though, every language has its pros and cons and static typing systems while nice also can introduce their own problems. I've spent days tinkering with type abstractions in Haskell rather than getting work done - not because I had to but because I knew more expressive solutions were possible and the allure of them was too hard to resist. Elixir is a fantastic language for getting work done.
If anything holds me back its my experience with Scala. I used Scala for awhile (actually it was my introduction to functional programming) and I like the language but everything about it that seemed to make me unhappy was related to the tooling and JVM ecosystem in general. I should give Kotlin a real chance on a small project though as I haven't done that yet, I've just read the language guides.
The main complaints are valid: hiring issues, lack of a good typesystem, small ecosystem. Yet I have been the most productive with Elixir and would rather move to something better than regress to the mean just because Elixir isn't a mainstream language yet (which would solve the hiring and small ecosystem issues).
Most Clojurists tout "libraries over frameworks", accordingly perhaps with the flexibility of the language itself. Personally, I think acceptance of frameworks would bring much needed standardization to the Clojure experience. On the frontend, I enjoy the lightweight Re-frame.
Of you want to build dynamic web applications, phoenix+LiveView is imo _the_ best Option currently. It also keeps the promises: reliable (zero-maintenance apps running for years on heroku), productive (prototyping things is really fast: like rails 2/3 days), fast (templates render fast even when complex). It is also great to have actor processes and ETS at your fingertips.
Having said that: dynamic typing truly sucks for bigger projects/large teams. It is awful how much you have to write tests for, and these tests will slow down further velocity when things will change... stellar opposite to Rust.
Never attempt to do any serious number crunching in Elixir/Erlang, the BEAM truly is not made for this stuff. Plan to use/write NIFs or Ports for computational tasks. Even compiling larger Phoenix codebases will take minutes if you aren’t cautious of using „import“ in modules.
Finally: deployment is really the major pain point. I love the single file deployment story of rust and go.
In a large company with lots of services in many languages, avoid putting business logic in your Elixir code. Business logic adds coupling to related code in other systems that often all need to change together, or get changed in a hurry. Either way, the engineer trying to do the change probably won’t know Elixir. Likely they were appointed to make the change by a PM who doesn’t know or care much about which systems were built in which languages and who knows those languages. And that engineer won’t know Elixir, and they’ll complain about being unable to work on your Elixir system, and it will give Elixir a bad reputation at your company in the eyes of managers and PMs. It won’t matter much if your system has high uptime or low tail latency or whatever.
The solution to this is to only use Elixir for building infrastructure. Nobody cares much what language PostgreSQL or RabbitMQ or Kafka are written in. You don’t modify the source code for those things typically, you just use them through some kind of API. Similarly if your company needs a notification delivery system, you can build that in Elixir. You just have to do it in such a way that nobody will ever be blocked trying to launch a new kind of notification because they need to learn Elixir first. Make it fully accessible and extensible through its API so they never need to know what language you used.
Lots of great ideas in Elixir, Phoenix, and Ecto, but it just didn't hit the critical mass or completeness that makes the Rails ecosystem impossible to walk away from.
And because abandoning Rails is not a practical option, maintaining brainspace for an additional thing that is not widely used and nearly identical in function is not worth the cost.
Sadly.
- If your engineers are used to the Ruby, JS, Python etc. ecosystems of finding a library for everything, they may become frustrated with the smaller Elixir ecosystem. In my experience, this is actually fine because it means you have fewer external dependencies to wrangle.
- Differences between compile-time and run-time configuration is a foot gun. We ended up moving almost everything to run-time configuration, which is not what you see recommended by many libraries and examples. This is slowly changing however.
- Deployment is a bit unique, and working with building releases can be confusing for those used to deploying dynamic languages that are not compiled.
- Functional programming requires unlearning many OOP best practices. This can slow down engineers new to the language.
A lot of these issues IMO comes from how Elixir was initially marketed as a "fast Ruby" and Phoneix "fast Rails". Once you get past the syntactical similarities you realize the semantics are very, very different. Elixir is much closer to Erlang and Clojure than it is to Ruby.
Elixir is an excellent language for motivated engineers solving novel problems. If you are building a standard CRUD app, I think you will find more success with Rails or Django in 2020.
For that reason, I was pretty comfortable coming to Elixir and implementing Somewhat esoteric, games-specific networking stuff myself. It would have been nice if the library had already existed, but I am so glad I went with Elixir rather than Node or PHP (which both do have the game networking library I needed).
My experience has basically been that the “fat head” of open source libraries exists in Elixir, but the long tail of more esoteric stuff does not. By its nature, though, you won’t need 99% of the esoteric stuff available in other languages.
Although, actually, I'm converting it all to C#, since we don't really have a lot of elixir people in the shop (I had to learn Elixir just to do the maintenance when I came onboard.)
The lack of APIs can be painful at times. What is there sometimes isn't the most fun to deal with outside of the ecosystem (the driving force of the language conversion was issues using Phoenix Channels reliably with non-elixir consumers, and during the conversion we are dealing with the minor pain of pulling certain data out of an mnesia data store.)
But, I mostly enjoyed my time playing with it.
Biggest observation was that the IDE Tooling didn't feel as nice as, say, F#, which makes a bit of a difference when you're not dealing with the language day-to-day and need helpful reminders for how you're getting certain things wrong.
My biggest annoyance with Elixir is that the ecosystem seems to impose a lot of opinion on how things should be done, and sometimes actively makes it hard to do things differently. In some edge cases I couldn't do the easy thing because it's "wrong", and couldn't do the right thing because it was not yet implemented. I believe this is with good intention and is mainly a side effect of the relatively small and young community, as in many areas the de facto libraries are made by the same few people. I suppose this problem will get better as there are more libraries to choose from and/or more contributors to those libraries.
Another, milder source of annoyance for me is the lack of functional programming features in a functional programming language. To invoke a function that has been passed as a parameter you need a dot. No currying etc. Still, you can get things done and with these limitations it's harder to get yourself into a mess of functions being passed around (but not too hard if you try).
All in all, to me what Elixir brings to the table is more than enough to offset its annoyances. However when it comes to choosing Elixir, I agree with other comments suggesting to choose something the team is comfortable with.
In our particular case,
1, we did not have good libraries for file uploads to Azure and to handle the signing for securely displaying them.
This was solved by 2 very simple modules. In the end, both things are fairy simple unless you want them complicated (ActiveStorage can sure do "more" out-of-the box).
This also means the code it's super straight-forward and understandable.
2, we did not have a good library for SOAP.
We started to build a simple SOAP library. This I would consider a pain, but there is always option to avoid it (see no. 3).
3, we did not have good PDF libraries.
I happened to package my Ruby library (using Prawn) as a Docker container and simply call its API. This could also simply solve no. 2.
On the other hand hand,
1, we got one of the best GraphQL libraries out there (Absinthe)
2, we got a really fast test suite (once everything compiles, that is)
3, we are getting great tooling like Phoenix LiveDashboard for free
Also I want to note that deployment is now quite easy with "mix releases" (as compare to what it was). You even get a remote console that connects to your running cluster. No problem.
[0] https://nts.strzibny.name/phoenix-livedashboard-request-logg...
- Elixir and the Phoenix framework is perfect when building concurrency systems. Our company had made a ticketing system, solving the tickets for "Cirque du Soleil - Hongkong 2018". During two months of the event, our system (one Elixir/Phoenix server - scale up to two - one load balancer) can handle more than 5000 tickets per second. We were so excited. Unfortunately, the client's company closed after that. So the project was closed too.
- The second project was a product of a finance startup. Not so much thing I can share in this project, but the main feature is chatting. We used the Phoenix Channel and Graphql Subscription for this feature.
- In the side project, one member of the team used Phoenix Liveview to build a Coup game (board game).
So, in general, when I work with Elixir, I see a few pros and cons:
Pros:
- Fast. Functional programming is really fun. I love pattern matching.
- Not so hard to write tests. It's easy to mock external APIs. (Check out this blog: http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-...)
- Easy to deploy. After dockerize the application, I can run it anywhere. The ticketing system was run on AliCloud, and the second project was run on Kubernetes (AWS).
Cons:
- Lack of supported libraries for external services. Sometimes, our team has to write a wrapper library to call directly to external services' APIs.
- Not easy to hire an Elixir developer in Singapore. Our team tried to hire Ruby on Rails developers and train them. But they were not feeling interesting in learning Elixir, so they left the team.
1. It actually takes a while to get your head around the fact that the biggest advantage of elixir is having access to the battle tested features in OTP. It takes longer to realize that these benefits don't come for free and elixir might not be the best solution to your problem.
2. The learning curve for writing functional code is different for different people. As a result, it's impossible to figure out before hand how long it will take a team used to writing pythonic code, for example, to start writing functional, elixir code.
3. You're able to emulate something like type checking between pattern matching and ecto schemas but it's not type checking. Be prepared to spend time coming up with best practices for writing tests.
Coupled with the relative slowness of the compiler, this means your average development session is a loop of
1. trying to get something that pass syntax check
2. starting your application
3. making a request
4. `no match of right hand side...` because you had a function that expected `{:error {:ok {:foo %{bar: 0}}}` down a five-level-deep stack, and you typed `%{"bar" => 0}`, you fool ! Or maybe you typed "baar". Or maybe you did not type it, and some of your colleague did. How would she know ? `dialixir` was probably disabled a few years ago because the error messages were not helping anyone, and you're not going to write `@spec` anymore at this point.
I heard some people managed to overcome this by using the REPL, but in my experience it just means making the spelling mistakes in a terminal rather than in an editor.
Elixir is great for code that mostly does "routing" of messages from sources to sources.
Phoenix channels are a blasts.
Pattern matching would be great if the compiler could help you with the obvious mistakes.
Libraries are exactly how you would expect for a few-years-old language. Name one topic, and there are three competing libraries : one of them does not work, the other one is unmaintained, the third one has completely changed its API in latest alpha. You're going to pick the wrong one anyway.
If I could go back in time, I'd avoid writing any kind of business logic in it.
(Opinions mine.)
Elixir is also dynamic but with annotations that gives the illusion of types. It's really the worst of both worlds.
* Not enough engineers can write/understand elixir which kept leading to people being blocked
* Library ecosystem for scientific applications is lacking
* The language itself is confusing and the macro capability causes code to be uniquely incomprehensible in each different module
It was a worthwhile experiment but you just can't beat the advantages of having extensive documentation and expertise in the more mainstream languages unless you have very specific performance requirements.
The backends were swagger which allowed me to autogenerate the Elixir source code for the GraphQL resolvers from the swagger.json file. It was a project for a telecom company which also employed Erlang programmers.
My experience: stability was outstanding, performance reasonable. Got a lot of praise for it. Only got one big report in two years.
Would I recommend it? Only to experienced software engineers.
Would I recommend it to companies? Only if you understand that every new tool or stack is a strategic decision.
The one thing that we struggled with Elixir with is that it's quite hard to find people that know Elixir. For a long time I was the only one doing Elixir in the company, which introduced a bottleneck. That said, when you find someone who knows Elixir they tend to know Elixir (and a bit of infra) quite well.
However, I wouldn't use it for a real project:
1. The ecosystem doesn't compare to ruby/python/javascript. There's a lot of missing packages and abandoned libraries. I ended up having patch a bunch of libraries I used.
2. The tooling that exists is nice and 'just works' but there's a lot that's missing from ruby land: https://elixirforum.com/t/help-an-elixir-beginner-find-missi...
3. The deployment story is horrible. The heroku/dokku buildpacks didn't work by default and it's not obvious how you can easily deploy a mix/distillery release without writing your own deploy scripts. https://elixirforum.com/t/official-heroku-dokku-buildpack/28...
4. Phoenix != Rails. There's many many obvious things that are missing. `mail_to` for example.
However, I dislike the GenServer syntax, it feels too complicated with random tuples all over the place. Also, I feel like the job market is too small. I wasn't able to find a job using it (although maybe COVID is to blame here).
The first one, I can't complaint. Every programming language have it's weakness, to solve that I'm offload processing stuff into Rust. Luckily, Elixir have NIF integration with Rust.
* cant find people for it and if you do they come because of the tech not because of the real world problems we are trying to solve.
* the no types thing makes people write tests that test for typos and make refactoring incredibly difficult because you have to fix gazillion pattern matching tests
* the tooling is just horrible, cant jump, because of pattern matching that makes it much more annoying
* phoenix was complete misfit for us as well, so i ended up hating it for no reason
so because of no-tooling and on-refactoring, accidental complexity only grows, and we ended up with much more fragile system with infinite failure domain.
i wrote briefly about why it was chosen at https://eng.rekki.com/kitchen/kitchen.txt and why we moved to go.
that being said, i am sad that this was my first real world experience with elixir, in a project where it was not a good fit, so now i am totally biased against it. when i tried it briefly for few hobby projects it seemed like a quite cool language.
-b
Elixir is an okay language at best, if you look at it in comparison with let's say Python:
1. It is not any safer than python 2. Not any faster than python 3. Abstracts away a lot of things away from you "magically" just like python 4. It is harder to read elixir cause you will have people writing all sorts of random macros.
Where Elixir is good: 1. Concurrency (Processes!) 2. Based on BEAM but you won't care for your simple API project and you shouldn't have to. 3. Other erlang goodness supervisors, otp etc.
Personally I detest Python, but you would be better off writing your app in Python cause using Elixir you have gained nothing much substantially but lost on all ecosystem and community support Python has gained in its long history.
As a sidenote, I hope you use a strongly typed language.
1. Hiring. Generally speaking, finding experienced Elixir engineers is not cheap nor easy. Also, chances of finding someone willing to work in an office is even more slim (very remote heavy talent pool)...
2. Deployment/monitoring. Shipping your first Elixir application to production will likely be one giant act of frustration if you're used to Ruby or other scripting languages. Compile time vs runtime settings and environment variables are a HUGE gotcha. Also, as fate would have it, BEAM is more like an operating system than a simple runtime and thinking you can toss it into container and be off to the races is a recipe for disaster. Hope you didn't want to use distributed Erlang... (though this has gotten better recently, tbh). A lot of common monitoring tools and methodologies have to be thrown out the window because of how BEAM works internally (there are great ones to accomplish most anything, but its just more to learn).
3. Distributed systems, make for distributed problems. Yes, Erlang/Elixir and BEAM give you some fucking amazing tools to build distributed systems... that doesn't make building distributed systems themselves "easy" or "quick."
4. Performance. Erlang/Elixir will probably take a hot-fast shit on most scripting languages when it comes to network requests; however, if you need to do any long-running number crunching or complex string manipulation, you'd be better off almost anywhere else. This could also be categorized as "use the right tool for the job, dummy."
5. Learning curve is real and vastly underestimated. You aren't just learning a language, you're sort of embarking on a quest to learn a new paradigm. Erlang/Elixir, BEAM, and OTP (I'm going to refer to them as the "Triforce") were designed together to solve a specific set of problems. The Triforce has been in-use, 24/7, 365 days a year, for 30 years, in real, critical, production systems. If you ever wanted to learn the "Actor" pattern, BEAM is the most metal implementation I know of... Also, really sit down and make sure you understand it's all "non-blocking" thanks to the scheduler being able to pre-empt processes.
----
With regards to 3rd party libraries, I find in Elixir and Erlang, I need and use way less of them... BEAM and OTP give you soooooooo much it's kind of absurd how little you end up missing or wanting.
----
Lastly, while a lot of the success stories here are great to read, a lot of them have bad code, or things that are just silly to the trained eye. And that's totally okay, the authors are learning, we all have do it, but I'd probably not recommend the code as study material. In general, try and find examples from people who have seem to have been using it for a few years.
----
Addendum (via edit):
FWIW, I've been writing Elixir for about six years now (just checked some commits), and have been employed full-time writing Elixir for five of those. I wrote Ruby before that, and have never looked back... If you want my number one selling point to any Rubyist :trollface: (but also very real), is in Elixir a "horrible, slow, wretched monolithic test suite" still will take less than 90 seconds to run.
The main question is always the same, “how much effort it will take me to find Febe if I need them”.
And my answer is always the same, “make them come and work with you, and focus people excited and open to learn a new technology”.
My only real complaint with Elixir (and this covers Phoenix and Live View too) is that things tend to get announced WAY before they're anywhere near being production ready, so you may find yourself postponing building anything because you're waiting for the announced thing to be ready to use.
For example Live View was talked about in the later months of 2018 but there's still some things missing today that make it pretty sketchy to really use it in a non-trivial production app, and the docs leave much to be desired. Things like LV based file uploads as being part of the library was in the "near future" or "on the horizon" a year ago, etc..
I only spent a weekend using it for a project to play with about 6 weeks ago and ended up uncovering some bugs and even some feature requests that quickly ended up making its way into the master branch of LV after reporting them. But these are for things that seemed very basic to me in the sense that if the folks who were developing it were using it in larger production apps, there's no way those things wouldn't have made it into the library sooner.
In other words, I don't feel comfortable or confident at all to use it in production because I'm afraid if I spend some real time working with it, I'm going to end up being patient zero for so many bugs and edge cases. I want to focus on building my apps and trust the libraries I use, not be a pioneer (software is already difficult enough without bugs!).
On the flip side, Rails is a champion here and IMO it's very much why it's so popular and will remain popular 5 or even 10 years from now. It's because most of the things in Rails come from tech focused businesses using it to drive their services and the core team also use it to drive their business. Everything feels like it's really battle hardened, production ready when features are talked about and was carefully designed based on months of real world usage.
With Elixir, Phoenix and LV that feeling isn't there -- at least not to me. It sort of kind of feels like the libraries are being built from a theoretical point of view. I mean, I know the creators of those libraries do consulting work, but it's super behind closed doors. There's never any talk about what drives the development of practical features and it's kind of a bummer to get glimpses of things on the horizon, but then years later they aren't ready for production.
That's the thing that worries me about the future of the ecosystem. I also think it's partly why it hasn't exploded in popularity. It's a nice ecosystem for sure, but it's missing critical components for it to be adopted by the masses.
That and I think generally speaking the language is very hard to pick up. I know I struggle hard with Elixir with ~20 years of general dev experience across half a dozen non-functional languages before touching Elixir. I still feel like I have trouble reading Elixir code I've written 2 months ago when it uses some "real" functional bits that I had to ask for help on.
I often feel like I hit 100% dead ends and have to ask another human for help. I don't think I would be using the language if it wasn't for the IRC / Slack channels. There's some folks going above and beyond to help people out there, including Jose himself (the creator of Elixir).
With Python and Ruby I rarely encounter situations where I had to ask for help like this. Google always had the answer for everything I couldn't figure out based on tinkering and reading the docs.
It's not just due to less blog posts existing for Elixir / Phoenix too. It's the language overall. I want to love it, but for whatever reason my brain won't accept it naturally. I get hung up so frequently. I know this is just my brain being an idiot, but I do wonder if anyone else feels the same.
My gut tells me yes because otherwise the language would already be one of the most popular choices for building web apps. There's a lot of great things surrounding the language.
1. Expensive to find talent. 2. Deploy story was kind of shit until recently with mix release. 3. In Rails or .NET you can find a package for literally anything you can think of. In Elixir-land, it's not 100%. It's more like 80%. One example, there's a Twilio elixir lib, but bad luck one of the calls we need isn't supported by the package. In rails it's all supported.
---
But on the other end of this, it's all rainbows and happiness. It's fast, predictable, boring. I love that about the language.
I still use Nim for hobby projects, but anything professional I will write in Elixir.