Problem solving as a developer can sometimes feel limited because you’re often required to think within the box that the language you are using has set. What’s even worse is when you accept the limitations of your paradigm and start to think there aren’t any other solutions. When you’re working within these constraints, you aren’t always able to follow your natural, unbounded human intuition, which can set you back and make the problem seem harder.
I’ve found that Erlang operates in a way that mimics the physical world better than other languages do because it is intuitive to humans. This allows me to code with greater clarity because it's easier to understand what’s happening. It puts me in the driver’s seat(s) of my program.
Many people understand human language as a bridge: It’s the bridge that your idea needs to cross in order to get into the brain of another person. Computer languages are a bit like spoken languages, but they’re a bridge between two very different thinking devices. An extremely broad assertion would be that biological brains are more lateral or associative, whereas computers are linear by nature.
No one programs in prose or binary, so we recognise that neither extreme is happening at present. This means that this spectrum is quite well understood and well recognised. Something that is not well recognised, on the other hand, is that there are other spectra to be aware of in your language choice – one of them being how well it suits your problem domain.
I am here to argue that the best language to handle our principal problem domain of ‘reality’ is Erlang, or any other language running on the BEAM, Erlang’s virtual machine. This includes Elixir, among others. Here are my thoughts.
*All opinions are my own. No ants, cars, kitchens, phone lines or spacecraft were injured in the making of these analogies.
How Erlang models reality to help solve problems more naturally
Erlang is a highly performant language, which is great. For me, though, the biggest benefit is how it makes it easier to think about problems.
In object-orientated languages, you are looking at objects from the perspective of the main executing thread, which can pick up an object, use it for a while, and then put it down. You act upon an object, meaning it’s more abstracted and requires more brainpower to work through.
In Erlang, you are the object. You get to think at the level of the ‘object’, particle, actor, agent... whatever. Each object operates within a tiny self-contained unit – it doesn’t know about anything else until it needs to interact or is interacted with – which mirrors reality.
Modern physics tells us reality happens from the perspective of the observer. This is what makes Erlang so powerful: It gives you as many processes as you need to represent all of the observers in your scenario.
You get to program from the perspectives of the things that make up your system.
To demonstrate this in more concrete terms, here are five features of Erlang that help it better model reality, so that human programmers can solve problems in a more natural way:
- The primary unit is a process
- No shared state
- Loose coupling
- Decentralised error handling
- Interaction by default
The primary unit is a process
Unlike an object-oriented paradigm, Erlang processes are not linked to a main system thread. Instead, each little unit has its own thread or process attached to it, which means that problems can be solved autonomously without being linked to other, unrelated problems.
Erlang processes are super light. They are handled by Erlang’s virtual machine (BEAM). It costs almost nothing to create, and costs nothing to idle them. In other words, you pay no penalty for making a thread wait, because it’s only waiting for itself – it is not a ‘busy wait’, like in some languages. It just gets parked when it is inactive. When finished, they die on their own.
To better illustrate the benefit of this, let’s compare Node, Java and Erlang:
Node is inherently single threaded. Still, it’s similar to Erlang in that when you are thinking about what the code is doing, you’re thinking about it in isolation. Where Node falters and becomes difficult to think about is that it’s entirely reliant on callbacks for asynchronous behaviour. So, you’re always having to think around a corner.
Erlang, on the other hand, has no penalty for stalling a thread, so you get asynchronous behaviour for free. You still get to think about it as if it was synchronous, because from the perspective of that thread it is synchronous. You get to think at the level of the thread — and not the system — so it completely irons out the problem for you, making it easier to solve.
There’s no shared state
Another reason why Erlang is superior at modelling reality and solving problems more naturally is because there is no shared state.
Many functional languages emphasise immutable variables and, while Erlang is similar here, it takes it as an additional step whereby each process has its own distinct memory allocation. This means there is never any shared state between processes. All memory is passed by message, which means it’s always copied, never shared.
Imagine you occasionally lend your car to your friend. You just ask that they put all the settings back when they’re done with it, and normally they do. Then, one day, you see your car parked downtown. You get in, and the seat is in the wrong spot and the radio is playing music from the 90s. You angrily text your friend, saying that they didn’t put the things back to the way they were. Your friend complains that they weren’t exactly finished with the car so it’s a little strange you’re angry about that.
This is what can happen when you share state: Two things want to use it at the same time and you may get some undesirable results. One way of preventing those bugs is just to give a copy to both — this is what Erlang does. This is akin to reality: Nature has no shared state in the way computers do – state is normally transferred, or copied, but not shared.
Loose coupling
Another way that Erlang streamlines problem solving is through loose coupling, whereby different parts of a system don’t even have to know about each other. One process can send another a message if it knows its process ID. The other process has to be prepared to the extent that it needs to be aware of what type of message could arrive.
The sending and receiving functions exist just fine in isolation. It’s the same as if you viewed a website you didn’t even know existed two minutes ago, and the website owners didn’t know you were coming either. All you need is a common protocol that you both subscribe to. But one is not dependent on the other.
Imagine you have an ant colony: Half of the colony is out foraging, and then the ant hill gets attacked by an anteater. The ants out foraging have no idea that their colony is being annihilated, and they won’t find out until they get back. Let’s pretend that there are some really industrious ants that manage to mostly restore the colony before the last foragers get back. These may not even know that their colony was nearly destroyed earlier and just continue on with life. This is what we want out of a resilient system.
The closest technical analogy to Erlang’s loose coupling would be something like HTTP – not another programming language, but a network protocol. One process only needs to know the process ID of another process in order to send it a message. This other process may or may not exist. The equivalent for HTTP is it needs to know which IP address and port it’s trying to talk to, not whether or not it actually exists. HTTP and Erlang could both be used for interplanetary communications without further modifications because of this loose coupling.
Decentralised error handling
As a consequence of loose coupling, a different approach to error handling is required. The motto in Erlang for handling errors is ‘let it crash’. This might sound horrifying for those coming from other languages, where crashes are by definition catastrophic. What’s ‘crashing’ in this scenario is just one small process. A crashing process is not catastrophic as it can be supervised to automatically restart. When your process is crashing repeatedly, it’s a sign of a problem – probably a bug.
Erlang acknowledges that human beings are not perfect and will inevitably introduce some bugs into the system, so it has been designed to be flexible and resilient in the face of error. This is also a better way to model reality: When failure happens in reality, it affects systems in isolation. Something like your car may be brought down by a cascading failure, but your whole kitchen doesn’t fail when the sink is clogged.
Because of this resilience, you can do things in Erlang which cannot be done practically with other languages, such as migrating your code and/or hardware without shutting down your system. The parts that are relying on the bits you are replacing will blissfully fail and continue to restart if they are supervised. When the new parts of code or hardware come online, the system heals itself and just goes back to normal much like the resilient ant colony.
See more on ‘Let it crash’ here.
Interaction by default
Erlang assumes an interactive environment, and you can interact with your own system if you want to in a terminal. You can send your own running processes messages and receive messages from them. In this context you become a part of your system. It is built for interaction.
Ruby and Node (among others) can run interactive terminals as well but they aren’t designed to interact with other running processes. With either of these you can pick up the phone but there’s no one else to call. In Erlang, you can dial into an already running instance. If this sounds like voodoo to you and/or you think I’m lying, check out this live bug recovery demo.
You may think pain is a bad thing. If you broke your arm and you didn’t have pain you would continue to cause yourself damage. Similarly, a system needs to know when things are going badly. This is an area that is difficult for many computer languages. JavaScript, because of its original use case, was designed to fail without giving feedback. This is a really big problem with systems. You need to know when things are failing.
This is something that Erlang does quite well. You can become one of the nodes of your system. There are also several great tools for introspection on top of the usual tools such as logs and system monitoring.
This is useful for other areas apart from failure. Maybe you just want to know what is happening. Is your server busy or not? How are people interacting with your product? The more you know, the more well informed decisions you can make.
For more on building a system that is useable for its operators, aka: you, check this out.
But Erlang is weird...
Some people are put off Erlang because of its alien syntax and its unconventional order of execution. The former is because it inherited this syntax from Prolog, which is another wonderful, albeit mostly forgotten, language. If you prefer a prettier syntax, Elixir is waiting with open arms.
The latter — the unconventional order of execution — is for, some people, the more difficult pill to swallow. Guess what? That’s the secret sauce.
The order of execution is the way it is, because it solves the hard problem you can’t solve in another way. It’s mirroring how your brain works more closely than a conventional programming language would, so it will take some getting used to. But, once you do get used to it, you won’t be able to go back to the way you used to think was the right way.
More on Erlang’s weird syntax.
Still hungry? Prefer video? Don’t believe me? Watch this mind blowing demo which displays the external benefits of Erlang.
The missing bridge
To wrap up, I’d say that Erlang is the missing bridge that you’ve been looking for: The bridge between you, the computer, and your problem. It gives you a superior model for thinking about your problem. Bret Victor has done some great work in this area.
Reality is messy and happening everywhere at once, and I believe that our programming models need to be able to deal with the beautiful mess in such a way that makes it possible to more easily reason about it. This is accomplished by thinking from the perspective of the unit or process, just as you already do for everything else.
Kevin E was born at the beginning of time at the stroke of midnight on the 1st of January, 1970. The war on bugs drew first blood as he lost the remaining portion of his surname in a string truncation accident. Was he formerly Kevin Everest or Kevin Ernest? It’s lost to the sands of time.
He doesn’t enjoy hunting or fishing or trapping bugs, which is why he is a proponent of functional programming in general and Erlang specifically.
He is married with three children but it’s also entirely possible he made them up as a polymorphism analogy and inadvertently retconned it. He has trouble distinguishing metaphor from reality.
He is a recovering practitioner of OOP and now refers to that time in his life as ‘Oops’. He’s a greedy knowledge gleaner. He wants to know everything. Unfortunately he is mortal so he’s decided to limit himself to things with a useful half-life of greater than six months so JavaScript is out.