Elixir is well handled by LLMs at this point, and we keep the “easy to scale and maintain” aspect (operational characteristics of the runtime are very good).
I can safely say that total cost of ownership is excellent.
Roughly 14M per day, but growing 2.5 to 3 on a YoY basis.
My point stands though: much more serenity in ops with that growth, and LLMs do work nicely, especially when most of the code is stateless by language design.
Everyone else just moved towards the same kind of cooperative thread runtime semantics (even if the lingo differs, async in C#, callbacks first, then async in NodeJS, virtual threads in Java, async in C++ and so on).
I think an interesting historical tidbit is that Java actually had M:N threading in early versions (1.1, 1.2, 1.3?) that got ripped out because it was deemed complex and error-prone since people back then often did native bindings naively (And perhaps file/socket API's were less complete?) only for them to reintroduce it again in Java 20 I think it was under the virtual threads name?
Yeah, they called that “green threads”. https://en.wikipedia.org/wiki/Green_thread
BEAM is actually cooperative. In userspace, function calls and IO (receive and !) are yield points, and reduction count is simply the number of function calls you're allowed. Something something Computer Sciencey reduction basically means function call.
It just feels preemptive because as a functional language, you have to call functions all the time. Your loop becomes
loop(I) -> loop(I + 1).
And that will yield once it calls itself enough times.Native code (NIFs, BIFs, and drivers) has to count reductions and/or yield explicitly.
You can easily hog scheduler threads in native code that does significant computation or does i/o without using the runtime facilities; most people who write NIFs will mess it up at least once, I certainly have. It's much harder to do in userspace code, but if you fully unroll a very large loop that does not call functions or anything else that can yield, you can do it; I don't see how this would happen without specific intent, I've never run into it, but it's possible.
A blocked scheduler can be vaguely fine or terrible depending on how long it stays blocked and what the system is up to. Often it won't be terrible until you do something that needs all schedulers to synchronize, such as operations around code loading, but you can probably wedge the whole BEAM if you try.
"Project Loom" spent much of its time (AFAICT) figuring out how to make every Java system-level API non-blocking without breaking backward compatibility. The only one they didn't get to for the initial release was Java's "interesting" built-in `synchronized` object monitor construct.
I also wouldn't say OTP was built to overcome the C10k problem. It is a very good solution for lots of concurrent connections (with a suitable OS), but OTP was built to manage concurrency needs in the context of telephone switches. The constraints chosen to meet those needs does work (very) well to meet the challenges of large numbers of concurrent IP connections, but I wouldn't say it was built to overcome those challenges.
I have done several experiments where, the only difference are a couple of paragraphs describing the technology stack for the AI tools.
Hardly any different than changing the target CPU architecture on a compiler output.
Quite sad, as now the fun of why language X instead of Y is gone.
The differences emerge as the project goes on. Comparing feature velocity and defect count is difficult unless your experiment is to build the same project over a long timeframe across several languages. Then winners would indeed emerge, but then that's only in the context of whatever domain said project is tackling. For instance, if concurrency or infrastructure isn't a large part of the complexity, BEAM languages might not confer much benefit.
Immutable value languages like BEAM langs are probably useful too, the LLM doesn't have to guess whether a value passed into a function will get mangled by the callee