Why Erlang?
fredrikholmqvist.com
fredrikholmqvist.com
After Erlang, I’ve enjoyed Scala a lot. Especially Akka.
I do tons of go now and when I need actors in go (not very often because channels are rather straightforward), I use proto.actor: https://github.com/AsynkronIT/protoactor-go.
Erlang is awesome. I can definitely recommend the “Designing for scalability with Erlang/OTP”: https://www.oreilly.com/library/view/designing-for-scalabili.... This is the book which finally made me “get it”.
Interesting anecdotes.
Compared to the other languages you mention there, I'm not surprised that Elm has the fastest compiler; compiling to an actual binary (or something low-level like JVM bytecode) definitely seems like it would take more work to compile to than something much higher level like JavaScript, especially if you're performing optimizations at each intermediate layer. That's not to knock Elm though; obviously having a fast compile time is a good thing regardless of the reason, and it certainly still would be possible for someone to accidentally write a slow compiler to JavaScript!
[0] I don't know Haskell that well, but my understanding is that its type system is capable of expressing more intricate types than the Go type system.
Native binaries with no dependencies was the reason to change to go.
Switching to the functional style of logic from the algorithmic style can be slightly tricky but as for immutability itself - since the introduction of for-each loops which freed us from incrementing counters I could never understand why is immutability not the default in all the popular languages. Programming algorithmically I rarely need to change a variable even in C# or Python. The fact they don't have a syntax for defining single-assign values (like val in Scala) annoyed me since I learnt Scala (which is extremely easy to start with - you can use it just as "better Java", advanced functional programming tricks are entirely optional) and realized I mostly use "val" naturally. If a variable I defined is changed - it most likely is by a mistake or some unexpected behavior taking place. Both C# and Python already have "var" to define a variable with its type inferred, why won't they add "val" to tell the compiler it can only be initialized once and never assigned again? To me it seems trivial for a compiler developer to add and also not breaking anything while bringing quite a lot of value by eliminating a whole class of bugs.
My take (could be wrong, happy to be corrected): Because most PL designers want speed to be pre-eminent in their language. One of the quickest way to never have your language adopted by large corporation is to have it be slower, without some obvious great benefit in productivity.
That's the catch though: Arguing for increase in productivity is harder, because it is qualitative, and ultimately subjective. Plus it's a crowded market these days in language productivity land, and unfortunately most of those languages are dynamically typed. Showing some benchmarks on paper is easy to do, and an easy way to sell a language.
So how does this all tie back in? Well, if you want to go fast, you need mutability (and up until most recent times: shared mutable memory, although this appears to be changing to non-shared mutation models). As memory and workloads get bigger and bigger, the cost of copying increases.
Of course retroactively introducing them to a largely mutable language may not prove to have better performance, but eg. Haskell is most definitely not slow.
At what point do you shift the blame for bad code from the language to the programmer?
If you made variables const by default bad programmers will simply mark everything mutable.
The only way you enforce proper constness tags (or mutable tags) is through code reviews and teaching people better. So the default doesn't really matter.
Then the content is enjoyable, and the length of the posts seems just about right too.
Where do I subscribe?
Also, technically speaking, having shipped Erlang a few times in 3 different products (although all of them were between 2000 and 2010), that I've had the least amount of trouble developing them the REPL experience is (was?) awesome with all the Emacs integration. The ability to basically shell into a remote node and poke around live was extremely handy.
People didn't like the binary logging but it seems systemd forced it down their throats anyway later. Also, I provided really easy tools to read the logs using the runtime, so they didn't care much in the long run.
It was really easy to write pipe drivers for programs written in Haskell, Python and C++. I even used the Java integration back then.
OTP was like a dream come true, and when you're building out an SNMP MIB, MNesia made that so stupidly easy - I couldn't believe how much faster I was going than the folks trying to plug into NetSNMP.
I wish I was still working on that project sometimes. It was my favorite.
I worked on an Erlang system for nearly eight years, and don't remember any binary logs. Maybe disk_log? but afaik, we only used that for mnesia transaction logs and I don't recall ever needing to look in there.
https://erlang.org/doc/apps/sasl/error_logging.html#report-b...
It looks like they have an RSS feed: https://www.fredrikholmqvist.com/posts/index.xml
Side note: That's a really cool graphic that looks to be Joe Armstrong, Robert Virding, Mike Williams with telephones? Where'd that come from?
I noticed it's 3 separate svg files.
He has another post with similar line style drawings[1], no credits there either, so I assume the author did them?
Yesterday was amazing, today continues in the same fashion.
To everyone who read and commented, thank you. This means a lot to me.
I think you're doing many things right! This crowd loves discussing fundamental technologies, including languages and your posts provide a really nice basis. I like the rational, tech focused, "no BS" perspective.
I especially like that you bring in some history and the persons behind tech (in Go post), I personally fancy that and think it could even be more. I like posts/books with a ton of footnotes and references. It makes me feel like I'm exploring/discovering something for myself rather than "just" reading some persons opinion (although that is fine sometimes, just not always).
Also your blog and posts just look nice! (Mobile could be a bit better. You should maybe scale down the SVGs responsively). The monospace font is immediately familiar and screams "Hi fellow nerd!". The colors and illustrations are warm and friendly.
Beautiful. I do more frontend these days, but if I ever get back to the backend, I hope it's erlang. I want to use tools designed for the problem.
Ocaml on the Erlang VM!
https://gist.github.com/macintux/6349828#alternative-languag...
1. Type of intelligence. This piece drove by the functional versus procedural roadblock. Most developers don't like and don't grok functional programming.
2. Level of intelligence. Systems programming and concurrency is not for the faint of heart. When I was a TA in college the threading topics were the ones that challenged the students the most.
The functional versus procedural mental capacity is very real and most developers have to fight with functional programming mentally. One cannot just ignore this decades long struggle of functional people bullying us in the procedural camp. One just has to search HK news here for functional programming and most comment sections will have this debate.
Amdahl's law can be stated as make the common case fast. Functional programming is not the common case for the human mind.
And since processes are so key to Erlang, the green thread mechanism that BEAM provides is very fast and message passing is about the same overhead as a function call (which is to say, not much). This model of concurrency should be teachable to any 3rd year CS major in college (and possibly earlier) without breaking their brains.
Most developers I work with day-to-day are not designers. Fred Silverman's "The Mythical Man Month" from 1972 has a good take on this. The way I see it, languages like Erlang are amber that locks in dead code because no one except the original author can maintain it. Once code goes into maintenance mode good luck finding Erlang maintainers.
> One just has to search HK news here for functional programming and most comment sections will have this debate.
Yet this thread entirely avoided it until your comment.
> One cannot just ignore this decades long struggle of functional people bullying us in the procedural camp. [emphasis added]
"Bullying" is quite a strong word. What kind of bullying happens?
Besides the silliness of the term, the other thing I want to comment on: Stop being in a camp. The best way to stop learning is to assign yourself to a camp and to only do what that camp does and only believe what that camp believes. Expand your horizons, learn about other camps and why they do what they do and believe what they believe. You'll eventually learn that no paradigm is "right" (in an absolute sense) and that, instead, they all have right approaches to large portions of the problem of programming but wrong approaches for some (potentially substantial, but usually not) other portion of programming.
That said, I generally prefer Erlang, for the syntax, for Dialyzer, and for the one less level of abstraction. But recognize the future in the space is probably Elixir, and being fluent with both is probably helpful.
Registry I will admit to never really looking at; I assumed it was the same as Erlang's process registry. Looking at it, yeah, having that as part of the default library seems nice, though in Erlang the few times I needed more than what the process registry gave me I just used a library. Given the evolution in that space just from the time I was in Erlang, I'm actually curious how Elixir's is implemented, but regardless I agree it's nice to include a default one.
So, yeah, solid additions, but I don't know if I'd phrase it that way, that they "don't exist in Erlang".
That's the prevailing mindset in Erlang, and it's holding Erlang back. "Why build X, when you can just build it yourself". And so you have everyone building a variety of Xs in each project, all of them subtly different, all of them slightly inconsistent.
Why not have these useful abstractions in the standard library? "Little more than syntactic sugar" goes a long way towards improving developer experience and frees you to do other things than reimplementing stuff that should've been in the standard library in the first place.
I'm not at all against having it there, I even call it out as a positive addition. Just not one, I, personally, found as transformative as the things I called out. There are other things that are -nice- in Elixir, the pipe symbol, revamped standard libraries, etc, but they didn't change the experience for me the same way.
Strings in Erlang are a pain point. Lack of a standard build tool (and thus using things like rebar3 and the like) is a pain point. Lack of macros wasn't a pain point, but it did enable new approaches, so I would consider it transformative.
Task? Task is nice syntax sugar, but it's not solving a pain point or enabling anything new coming to Elixir from Erlang (which, as you may note, was the point of this thread). I'm not dismissing it, it -is- a nice addition, and just like Registry having a standard approach is a benefit, but it just isn't nearly as impactful.
The protocols, structs and macros are game changers in comparison. All the Phoenix, LiveView, Ecto stuff is possible because of those.
For example, if I make a database checkout in testing, using ecto, the checkout lifetime is tied to the test process, as is typical in BEAM for sane resource cleanup. If you execute code which spawns a new process it does not know about the database checkout because it's a new PID. Task stashes it's caller PID (this is not the same as ancestor because you might want to supervise a Task) in the process dictionary, so libraries that need that association can find it.
So yeah, you may have a point about Registry, but Task really "doesn't exist in erlang" because no 3rd party erlang libraries know about your ad-hoc Task clone, so even if you built caller knowledge into it, it would only be useful for your own code. At best you could correctly emulate Task's api, but then you would only be able to get that functionality with your code + Elixir libraries that use Elixir Task, and no erlang libraries.
Setting standards is an important role for languages.
(edit) There you go:
https://www.futurelearn.com/courses/functional-programming-e...
https://www.futurelearn.com/courses/concurrent-programming-e...
There are older videos from them on YT:
https://www.youtube.com/playlist?list=PLlML6SMLMRgAooeL26mW5...
https://www.youtube.com/playlist?list=PLR812eVbehlwEArT3Bv3U...
Found this in my Erlang bookmarks:
This post doesn't have any code at all.
What's a good place to look at idiomatic Erlang that will force me to say "yes, this is much cleaner and clearer than my current language of choice"?
https://github.com/orgs/rabbitmq/repositories?q=&type=&langu...
I'm skeptical of this claim. How does easy restarting prevent bugs?
By crashing and having it start into a good state, the system will at least be operational again.
If you running a thousands of programs on hundreds of thousands of machines 24-7, you're bound to run into weird edge cases at the system level.
Instead of worrying about and optimizing all of these edge cases, SOMETIMES it is better to just have a system that is tolerant of these edge cases by design.
In Java you would explicitly handle this case with a catch and take action on a "division by zero exception". In Erlang you would just let the process crash (and log the reason so bug can be fixed) and restart the state to what it was before the erroneous input, no matter what bug/case the wrong input triggered. By having this generic handling you will make a really resilient system since you don't have to handle every possible bug on a case-by-case basis.
What would be the advantage of handling this case in the Java defensive programming approach? Maybe someone will catch the error and return/introduce null to the state? Or BigDecimal.ZERO? Then you might end up in an unexpected state for all subsequent requests.
(edit) formatting
handle_zero(data, 0) -> {:error, "division by zero"},
handle_zero(data, _) -> {:ok, transform(data)}.
handle_input(data) -> handle_zero(data, data.divider).A key point of Erlang systems, however, is that they are really good at reporting the state of the system when it crashes (due to functional programming, you have the state from before the crash happened, and what event lead to the crash).
The restart is a stop-gap measure that gives you service for the system as a whole. You can then look at the logged bug report and fix the problem. But you are in control of how quickly you want to fix the problem. There is a cost to fixing a bug as well.
Bugs in production will happen in every language, and crashing one of many running processes and restarting it to get it back into a known good state sure beats having to scramble to find a fix, code it, build and deploy a new version of the code.
The way the “let it crash” philosophy is done in Erlang is that you crash individual processes first (e.g. the current HTTP request from one browser). If that keeps happening there’s a counter that crashes the subsystem (e.g. the web file listing component). If that too keeps crashing the whole web frontend might crash, but the node might still be up (e.g. serving FTP or whatnot). And lastly the node will shut down completely if the web frontend keeps crashing.
This way, the rest of the system keeps performing and serving requests even if some parts are not working intermittently.
On top of this you of course have (built-in) logging of all these errors so you can investigate them.
Many other languages now have both language features and libraries/frameworks for doing similar things to Erlang's main selling points.
Languages that are easier to work with for most, e.g. syntax wise, better tooling, etc.
I see little reason to pick Erlang for any greenfield project.
"Erlang enables you to write scalable, concurrent, distributed and fault tolerant systems with soft-realtime latency guarantees"
Not unique to Erlang.
Not even the author realizes he does not actually answer his why.
Scala+Akka might come close, but from what I’ve heard it’s not there yet.
It boils down to the complexity that it saves you from. Webapps eventually grow to need caches and queues. That means external infrastructure dependencies on things like RabbitMQ, Kafka, Redis, Elasticsearch, etc. That also means managing the lifecycle of this infrastructure in your application and building robust systems to do maintenance, etc and not fail.
Erlang provides enough of the basic building blocks to trivially implement these features on BEAM in code, as I'm sure you already know. Then all you're doing is message passing with Erlang.
That's the secret sauce. The language features itself don't make it any better at solving the webapp problem than say Rails, etc...it's all of the ancillary problems of scaling webapps that are solved by everything just being code running on BEAM.
[0] https://elixirschool.com/en/lessons/advanced/gen-stage/#
Here's a neat demo. No JavaScript required.
Building a web service backend serving JSON over REST, or similar? Works well.
Wanting a more "batteries included" Rails like experience, with server side rendering and such? Elixir has a solid framework in Phoenix, and community around it.
Elixir and Erlang interoperate well so if you want to stick with Erlang you can do the Web bits in Elixir and then do all the lower pieces in Erlang. What happened to me though is that I discovered that Elixir is great, and so is it's tooling.
Definitely not 2.
FTFY :)
Recent versions are plenty fast enough for the majority of web purposes, it does tons of stuff out of the box, it’s share-nothing nature is a good fit for web services, and for a dynamic language it has a surprisingly strong type system.
You’re about to create a basic CRUD server for storing configuration files (JSON, YAML), with a web frontend. This will be used internally, with a few hundred daily users. No single file is greater than one megabyte, total storage is less than one gigabyte. You have today to complete this task.
You’re given these language options:
Ruby on Rails
PHP
Erlang
C++
x86 Assembly
Which one do you choose?
Answer: Ruby on Rails. Not Erlang.
Every. Single. Time.
I'd also like to learn Erlang when I find the time and use it for projects in the future, whereas Ruby was never appealing to me. Each time I had to deal with it, it created a mess in my system.
That's fine, that's your subjective experience which I'm not gonna argue with. But if you're trying to say here that Erlang is somehow more popular than Ruby for web development (or in general) that's quite ridiculous, not sure I wanna waste both our time engaging that.
That's like saying PHP came and went. Just because something is no longer the new hotness and doesn't get discussed a lot on HN doesn't mean nobody uses it. Both Ruby and Rails continue to be updated just like PHP and it's popular frameworks. If you compared the number of websites developed by either compared to Erlang, it's not even close. Elixir & Phoenix would be a better comparison, but Ruby is still top 20 in the Tiobe Index (PHP is top 10), and Elixir is somewhere hovering around the top 50.
It was kinda the de facto way to make websites at some point, together with Perl. it was definitely cool.
What I'm trying to say is in this case, I'd rather use Elixir and Phoenix as I know that best at the moment.
I like the question, though. I've seen small projects that see negligible load, but are mission critical, written in a niche language no one else in the org knows. The language handled the problem good enough, but maintenance costs were massive for what the project was.
Elixir/Phoenix is dead simple. Theres no "Rails Magic." It's easy to understand what's happening at all times. You don't need to have a grasp of the internals of OTP because it's not that important for the problem described above, but it's there should you need it, wand why you do need it you just need to understand it instead of of a plethora of extra technologies/DSLs that you would with a regular Rails Application.
with Rails you need to know/consider technologies like Foremen, Webpacker, Redis, Capistrano(or what ever), AnyCable or ActionCable, Sidekiq or whatever for ActiveJob, then how Puma(Unicorn, Thin) Webservers work, you probably need NgineX or something like it for reverse proxying. For more Rich Frontend experiences, something like Stimulus Reflex or a JS Spa(angular, react, svelte) Cron for periodic task, even stuff like periodically restarting your application. These are things you don't have to necessarily worry about in the context of Elixir & Phoenix w/ Liveview.
The beautiful thing about Elixir/OTP in general, is because you can approximate all those things that I listed above with OTP and core Elixir/Erlang libraries. You can't do that with rails, you need those other libraries/technologies/DSLs etc.
"While you may reach for Sidekiq / Resque / Celery by default when writing apps in other languages, in Elixir there are some good options to consider that are already provided by the language and platform. So before adding Exq or any Redis backed queueing library to your application, make sure to get familiar with OTP and see if that is enough for your needs. Redis backed queueing libraries do add additional infrastructure complexity and also overhead due to serialization / marshalling, so make sure to evaluate whether it is an actual need or not.
Some OTP related documentation to look at:
GenServer: http://elixir-lang.org/getting-started/mix-otp/genserver.htm... Task: https://hexdocs.pm/elixir/Task.html GenStage: https://hexdocs.pm/gen_stage/GenStage.html Supervisor: http://elixir-lang.org/getting-started/mix-otp/supervisor-an... OTP: http://erlang.org/doc/ If you need a durable jobs, retries with exponential backoffs, dynamically scheduled jobs in the future - that are all able to survive application restarts, then an externally backed queueing library such as Exq could be a good fit.
If you are starting a brand new project, I would also take a look at Faktory. It provides language independent queueing system, which means this logic doesn't have to be implemented across different languages and can use a thin client such as faktory_worker_ex."
- Formen: thats just a process manager. you need somethign for otp as well. in my startup, its booted up from kubernetes. the nice thing is that the vm will use all available cpus.
- Webpacker: phoenix comes with webpack out of the box though I think they are replacing that in the next version with esbuild.
- Redis: Elxir borrows from the beam and part of that includes both an in memory ets (key balue storage system) and mnasia (a slightly more sophisticated queriable database. ie: I don't need to setup redis when the virtual machine has something already avaialble. redis is there if you really want it though
- Capistrano: We use kubernetes but theres also mix release which comes out of the box
- AnyCable or ActionCable: have you heard of phoenix's channels system? its WAY more powerful. action cable is a toy compared to what the channels system can do and its already setup when you start a phoenix app.
- Sidekiq or whatever for ActiveJob: Oban works great and I was able to set it up and use it from scratch within an hour
https://www.phoenixframework.org/blog/the-road-to-2-million-...
Can you justify this?
If you knew them all equally well I would suggest that Elixir and Phoenix would be an excellent choice, although I am definitely stretching the definition of "Erlang" to include Elixir.
That said, I'm an elixir developer and I use elixir because I need to handle heavy loads and thousands of daily users with unforgiving uptime guarantees. Elixir lets me do that while keeping my hair.
If you don't know it well enough, choosing it over something else is a big RISK.
The investment required to learning enough to make a decision is often not considered. That's why consultants exist.
Also, was the Erlang VM written in Erlang? Probably not. So I'm skeptical about the universality claims of the language.
The article states the opposite opinion: that you should use the right tool for the right job.
Change the requirements... and the spectrum-of-ridiculousness for any option changes drastically. The language matters, deeply.
As for universality, not sure where you’re getting that. No one’s saying you should use Erlang for writing device drivers or OS kernels, or the lowest-level parts of the VM. Erlang is a medium- to high-level language, and should be evaluated as such.
https://erlang.org/doc/man/erl_eval.html
https://github.com/erlang/otp/blob/master/lib/stdlib/src/erl...
It's not lisp levels of concise, but the language was not designed for that.
the trade off is that every thread has its own gc arena that can be freely and quickly cleaned, and even better, often simply dropped entirely when the thread ends
there's never a stop the world moment, only the specific greenthread that's used up all its space.
OTP setup for managing processes in a standard way is also a big boon to developing in the language.
if you need to mangle some giant datastructure concurrently, sure, erlang is likely to not be what you need. but if you're just trying to toss out a cluster of interconnected nodes that can handle tons of concurrency and your interprocess messages are largely queries to threads dedicated to managing some resource or lump of data, it's a great setup.
This particular optimization can backfire if you're not careful. If you generate short binaries that trigger this optimization and then store them in ets, it references the existing binary which has a lot of extra space. If you hit that binary:copy/1 will return a perfectly sized binary you should store in ets instead. This same kind of thing can happen if you make a sub binary from some large binary (like a service response) and store it.
[1] http://erlang.org/doc/efficiency_guide/binaryhandling.html#c...
To the programmer, messages are immutable. There are, however, optimizations to reduce the performance impact, particularly for binary data. When you send a binary message that is longer than 64 bytes, it is actually stored in a shared heap and effectively only a reference is passed as a message. Similarly, when you extract a small piece out of a larger binary, you might actually get a reference to the larger data stored on the binary heap. The Erlang VM has had more than 30 years of development, and there are a lot of subtle optimizations like this which were needed to solve real world performance and scaling issues.
Lack of serialization is one thing that makes e.g. the ETS key/value store built into the VM nice compared to Redis. You can just use raw data structures as keys and values. Reads and writes are under 1 microsecond.
This copying isn't memory efficient and may not be CPU efficient, but along with immutability, it makes garbage collection simple, fast, and independent per process.
I suppose crashing is a kind of GC