I think that it isn't a well kept secret that I'm a fanboy of Elixir. Almost half of my blog posts (at point of writing) are related to Elixir and its ecosystem. But, how exactly did I get to this point? And why? In this post, let's deep dive into what makes Erlang/Elixir so special and why the latest innovations out of this ecosystem keeps me coming back for more.
The Whatโ
What is Erlang?โ
Erlang is functional programming language that made its debut in 1986 in the telecoms industry, due to the need for distributed, fault-tolerant, and highly available telecoms systems at Ericsson. As a result of this initial use case, the base suite of libraries that make up the core of Erlang was named the Open Telecom Platform (OTP), and the name persists till this date, even though Erlang and sibling languages are used beyond the telecoms arena.
The BEAMโ
When we run Erlang code, we need to run it on the BEAM, a virtual machine that compiles Erlang code into bytecode and then executes it. At runtime, the interpreter maps each instruction to executable C-code, making instruction dispatching fast.
Functional Programmingโ
Erlang code is functional, meaning that there is no shared state, mutability, or side effects. Instead of creating classes to encapsulate our logic and state, we compose pure functions to do the exact same.
The design choice to prevent shared state is to allow for Erlang processes to fail without affecting other processes. No shared memory means that one failure cannot cause another.
As Joe Armstrong, one of the creators of Erlang states:
Object Oriented Programming, this is the art of hiding side effects. If you can do it successfully you can write this in program. If you do it unsuccessfully, you get in a sucking mess.
from 26 Years with Erlang, September 22, 2014, Joe Armstrong
It is safe to say that he definitely wasn't a fan of Object Oriented Programming. By preventing side-effects, we essentially isolate our state and the way we can update it to a new state
Stateful Processesโ
Given that Erlang is functional, how exactly do we hold state in our programs? Through processes. Processes holds a programs state to memory, and can only be updated by itself. The immutability feature of Erlang means that each time we update the state, the entire state is copied, meaning that it is always predictable.
Side note: Processes is the way to encapsulate the updating of state in Erlang, but the parallel form in Haskell is called monads.
Concurrent Programming and the Actor Modelโ
Each lightweight process holds its own state, running its own code instructions. This intuitively means that we are able to write concurrent programs, with processes working in isolation of each other.
However, this gives rise to the problem of how these isolated processes will communicate. Obviously, sharing some sort of state between the two processes is not feasible and would go against the idea of state immutability and no side effects. The answer to this problem is the Actor Model, which states that "everything is an actor". In this case, each processs is considered an actor entity.
When actors receive a message, it can concurrently do either of the following:
- send messages to other actors
- create more actors
- designate message handling behaviour
This means that in order to communicate between different processes, we send messages between them. Processes then receive these messages in an inbox, and will handle them before sending more messages, either to itself or to others.
The Whyโ
Now that we know the mechanics of Erlang and what it offers, this section deals with why exactly we will reach for Erlang for building applications.
Fault Toleranceโ
The cornerstones of fault tolerant programming are to isolate errors to make sure if one thing crash it does not crash anything else. That's what the processes in operating system do.
from Faults, Scaling and Erlang concurrency, September 24, 2014, Joe Armstrong
When writing our program, we want to ensure that if something fails, it does not bring down the entire system. Not only that, but we want it to be able to gracefully recover from unexpected failures automatically, by recovering to a known working state.
However, in order to properly recover from a failure, there must be something that watches out for that failure, and can then perform error recovery for that crash. In Erlang, any process that crashes from unhandled errors will notify any monitoring processes, allowing us to restart the crashed processes from a known working state if needed. This way, our applications can run forever due to the way we can recover from processes that crash.
This error recovery mechanism gave birth to Erlang's "let it crash" philosophy, where we would rather allow the process to crash than to program defensively. Note that whether processes crash does not actually affect the stability of the BEAM. Processes that crash will not affect the underlying Erlang virtual machine. The BEAM will simply trudge along and continue running and scheduling all remaining processes for computation.
Scalabilityโ
Process Scaling with a Single Nodeโ
At a micro level, we are able to scale computations easily by spawning multiple processes to concurrently run code instructions. As there is no limit to how many processes that can be spawned, we can shift our mindset from a sequential mindset (where incoming requests/messages are handled sequentially, one after the other), to a concurrent mindset (where a new process is spawned for each new incoming request).
This means that to handle an increase in volume, all we need to do is to create more processes to match that volume. The Erlang runtime will handle the tricky part of scheduling tasks for computation in the most efficient manner possible.