Part 2: Functional Programming
Thanks to the efforts of those behind React, Elm, Redux, CycleJS and others, functional programming has made a significant resurgence on the front-end, and as a dynamic, functional language Elixir is well poised to take advantage of that trend.
In fact, any front-end engineer familiar with Redux or Elm should feel right at home when learning about GenServers, an integral part of OTP and the Erlang ecosystem. Elixir also brings a pleasant, Elm-like dev experience to the back-end that is much cleaner and more idiomatic than what you'll find in NodeJS. In general Elixir just feels well thought out when compared to the hacky Frankenstien that is the modern NodeJS ecosystem.
This section will go into the merits of functional programming, which should explain why it is making a comeback and how it all relates to Elixir.
A Brief History
Before getting into it, the Functional vs Imperative/OOP debate has been going strong since practically the start of modern programming. It's like the debate between Nikola Tesla and Thomas Edison regarding AC and DC. Ultimately, just like we ended up with AC in wide use while DC has its specific use cases, Object-oriented programming became the standard, while the functional languages served specific use cases.
Until we needed massive concurrency for webservers and the ability to create distributed, fault-tolerant systems to keep up with the technology demands of companies today in 2020, Object-oriented programming made sense. However, now that needs have shifted from what software needed to do in the 80s and 90s, during the heyday of OOP, eyes have turned back to functional languages that have faded into obscurity. Over 30 years later, Erlang is getting a second look, thanks to Elixir and the already brilliant design the Ericsson team came up with back in the 80's.
Features of Functional Programming
There are countless articles out there explaining the benefits of functional programming, so I'll just touch on a few of the main points here:
Immutability
If you've ever worked with Javascript, especially with Redux, you know how careful you have to be when messing with arrays. Oftentimes you actually need to clone an array before changing it because it can have unintended consequences when you modify the original. In Elixir and other functional programming languages data is immutable, meaning it cannot be changed. The only way immutable data can be changed is if it is actually cloned before executing the proper algorithm (like inserting an item into an array, for example).
Referential Transparency
OOP has a nasty little secret. It's the internal, local state of objects, the 'this' if you will, the secret information only the object knows that prevents us from getting the predictable results we want from our code. When we have a function that doesn't have any 'internal' state and gets all of its necessary information from its argument, we can reliably predict the results. However, if the result is a combination of the arguments and secretive internal state, there is no way to know for certain that we'll get the expected results back.
When we compute results only from inputs, we have referential transparency, meaning there are no secrets and what we put in is what we get out. However, if we combine inputs with secret state only accessible within the function body, (in OOP terms a method instead of static method) our results are referentially opaque, meaning we don't have all the details so can't reliably predict the output. OOP is heavily based around this internal, secretive state while functional programming identifies this is a major source of problems in programs and eliminates this capability altogether.
Side-effects & Complexity space
I'm as far from a doctor as you can get, but if there's one thing I have in common with them it's that we both know about managing side-effects. In programming, however, side-effects are the disease, they are really the main source of how things go wrong. Side-effects are the unpredictable real world, I/O that leads to unpredictable and unaccounted for behavior, disrupting the otherwise perfect vacuum of your program.
Managing side effects in a safe manner and reducing the complexity space, i.e. the total number of possible outcomes, in a way that won't crash your program is one of the biggest challenges in programming. In both Elm and CycleJS the goal is to move side-effects to the edge of your application rather than inline with synchronous, pure code, in order to keep the application logic 'pure' and without side-effects.
Composition over Inheritance
I did a coding test once for a company that told me I didn't follow idiomatic Javascript in my code. It kind of made me laugh because that's one of the main reasons I don't like Javascript; what even is idiomatic in Javascript? If anything, prototypal inheritance is idiomatic, after all it was the original design. The ES2015 introduction of the Javascript 'class' syntactic sugar seemed like a superficial departure from prototypal inheritance, giving a convincing and practical illusion that Javascript is indeed class-based. If you add 'new' into the mix and the functional paradigms being introduced more and more, including reactive programming techniques such as observables and streams and you've got yourself a smorgasboard of paradigms, none of which are technically 'idiomatic' except perhaps prototypal inheritance.
The point of this rant is to point out one of the better features of Javascript, its capacity for composition over inheritance. You could say prototypal inheritance is a crude form of composition, although probably not the textbook definition of it, but conceptually more similar than the class-based inheritance that was introduced to the language.
It's a little difficult to articulate, but the benefits of composition over inheritance are well-documented, with associativity and mathematical principles on the side of composition, and a fragile deck of cards on the other. Think pipes and function chaining over Java class hierarchies.
Concurrency
Although functional programming does not directly impact concurrency, it makes it easier to create concurrent applications if there is no shared state, because there is no need for locks or other multi-threading techniques for preventing data corruption, which can typically lead to performance bottlenecks and complicate distributed computing.