Index
- Introduction to Elixir
- Functional programming
- Recursion
- Pattern Matching
- Immutability in Elixir
- Actor model in Elixir
- Installing Elixir
The software development world has seen the emergence of many innovative technologies in recent years, but few have caught the attention like Elixir. This programming language, built on the solid foundation of Erlang and designed to take full advantage of modern multicore systems, is revolutionising the way we think about the development of scalable, fault-tolerant applications.
Even we at DevInterface were not unimpressed with Elixir. Just this year, after carefully evaluating its benefits and potential, we decided to integrate this technology into our development stack. Our choice was not only dictated by curiosity, but by a specific desire to improve the performance, reliability and maintainability of the solutions we offer our customers.
In this series of articles, we will take you through a journey that will enable you to understand the key features of Elixir, the reasons why we adopted it and how you can start using it in your projects. Let us start, therefore, with an overview of what makes Elixir so special.
Introduction to Elixir
When was the last time you used Whatsapp or Discord? Both applications run on the same virtual machine. To be a little more precise, Discord is powered by Elixir and Whatsapp by Erlang.
Elixir is a functional programming language created by José Valim in 2011. It is built on top of Erlang, a language developed by Ericsson in the 1980s to support distributed, fault-tolerant and real-time applications. Both Elixir and Erlang are compiled and run on the Erlang virtual machine, called ‘BEAM’.
What's so great about Elixir?
- Functional
- Immutability
- Scalability
- Fault tolerance
First of all, it is a functional programming language and thus facilitates the writing of more concise, readable and easy-to-maintain code. Secondly, it supports immutability by default. This last factor is important because it allows us to have immense scalability. Since all data types are immutable, there is no possibility of threads, other objects or functions accidentally changing values. This results in state preservation, which makes the system much more scalable. The final, most powerful aspect of Elixir is its fault tolerance. Suppose any one of the actors or processors fails: it can be automatically revived with its previous state, thanks to the robust supervision system inherited from Erlang.
Why did we adopt Elixir at DevInterface?
We chose to adopt Elixir for several key reasons:
- Reliability: the fault tolerance and robustness inherited from Erlang make Elixir ideal for building critical applications that must always be available.
- Scalability: Elixir's ability to handle millions of lightweight processes simultaneously makes it possible to build highly scalable applications, ready to grow with business needs.
- Productivity: Elixir's modern syntax and advanced features increase developer productivity, making the development process more efficient and enjoyable.
These features make Elixir an excellent choice for building modern, scalable and resilient applications. Let us now take a detailed look at its characteristics.
Functional programming
Functional programming means that the entire programme consists of several functions. Imagine that f(x) is a function. If it receives an input, for example an X, the function will transform data from X. This data will be transformed into something called Y.
This is what your function will do. The function will always accept some sort of argument, transform the data and always return some sort of data. Although this may sound very simple, it really isn't. In fact, there is a lot that goes into creating pure functions.
So what makes functional programming so unique?
- Absence of classes
- Immutable data
- Absence of for loops
Firstly, we have no classes or objects within functional programming. Furthermore, all data types are immutable, so it is like working with constants in the programme. Now you are probably wondering: why do we need immutable data types or constants? Because this way, the state remains the same and data can be copied and distributed much more easily, allowing us to create immensely scalable systems using function programming. The presence of immutable data brings us to a third aspect: there are no for loops. Let us take an example. Suppose we have the variable I equal to 0, I less than length, and I plus. With each iteration, the variable I increases, i.e. the data changes continuously, but this is not possible for us because we have immutable data types. Functional programming and Elixir use recursion as a means of executing loops.
Recursion
Whenever we repeatedly perform the same task, we can speak of recursion. In the picture, we see a task that recurses continuously: this is a classic example of recursion.
Let's take an example. You organise a party and want to invite all your friends. However, your friends know other people who might come to the party. You can think of this process as recursion. You invite your friend Paolo and ask him to invite his friends. Paolo invites Marica and Giacomo. You ask Marica to invite her friends and Marica invites Camilla and Martina. Ask Giacomo to invite his friends and Giacomo invites Elia and Nicola. Each person continues to invite their friends until there are no more people to invite. In fact, the recursion in this example only continues and ends when everyone has been invited and there is no one left to add to the invitation list.
In simple terms, recursion is a way of iterating operations by looping them. The basic idea is to create a function that calls itself until it reaches an end condition, at which point the function stops calling and recursion stops. It is essential to think from the outset about what the desired end state will be.
Pattern Matching
If we say a = 1, you will most likely say that 'a' is a variable and that the value 1 is assigned to the variable 'a'. This is correct, but it is not what happens in functional programming.
If we say that a = 1, the following statement should also be true: 1 = a. "=" is not the assignment operator, but the matching operator, and what it does is simply match the right-hand side to the left-hand side. This pattern will often be seen in Elixir and is also called 'pattern matching'. Whenever you see an instruction such as a = 1, remember that we are simply trying to match the right side with the left side.
Let's take another example where we focus on the left and right sides: [a, a] = [1, 1] (the curly bracket indicates a list). The pattern on the right (two values) is the same as that on the left (two variables). The value 1 is now bound to the variable a, just as for the second variable 'a' the contained value is 1.
Let us now see what happens if we now type [a, a] = [1, 2]. In this case we receive an error message "no match on the right hand side". We have no match. The value 1 is bound to the variable 'a' and again the second variable is 'a', but this time the value inside is 1 and on the right hand side the value is 2, so 2 is not equal to 1 and that is why the two sides do not match and we get an error.
We take another step by typing [a, b] = [1, 2]. In this case we get a perfect match because value 1 is bound to variable 'a' and value 2 is bound to variable 'b'.
As we said earlier, if we want to think in terms of pattern matching, we always want the right side to be equal to the left side.
Immutability in Elixir
As we saw in the previous section, if we type [a, a] = [1, 2] we receive a matching error. This happens because all data types in Elixir are immutable: we cannot randomly assign new values to an existing variable in Elixir. If you are wondering why data is immutable, the main cause is scalability.
a = 1 in this example, we have a match operator and the value 1 is bound to the variable 'a'. Yet, if I say a = 2 we will not have an error. How is this possible if our data types are immutable in Elixir? Whenever we have a variable on the left-hand side of our matching operator, Elixir thinks we want to bind the new value to the variable on the left. That is why we have the new value instead of a which is 2, but if we don't want this behaviour we can use a pin operator, so we can say that the pin operator and a equals 2: ^a = 3. This time we get a matching error saying "no match of the right value: 3". This is because inside "a" we have the value 2 and on the left hand side we are trying to match the value 3. Now if we try to do the opposite and say that 3 = a, we again get an error saying "no match of right hand side value: 2".
These examples make you realise that since our data is immutable, it can easily be copied between processes and we do not have to worry if another resource, thread or process is modifying or trying to change our data.
Actor model in Elixir
One of the strengths of Elixir is its use of the Actor Model for concurrency management. The Actor Model is a programming paradigm that treats 'actors' as the fundamental units of concurrent computation. In a nutshell, an actor is an isolated concurrent unit: it receives something, processes something and returns something. Each actor operates in isolation, without sharing state with other actors, communicating only by passing messages.
In Elixir, the actors are implemented via the lightweight processes of the Erlang runtime, which are not those of the operating system. They can be imagined as virtual threads. At any given time, there may be millions of processes running. This is where data immutability comes into play. We can have millions of copies of our data and distribute them among various actors not only on our local machine, but also on a global cluster of different servers and distribute the load horizontally across the globe.
Installing Elixir
To install Elixir, go to the dedicated section of the site and click 'Install'. If you use Windows, go to the Windows section and download the appropriate installer following the instructions. If you are using a Mac, you can use Brew to install Elixir. Once you have installed Elixir, open the terminal. We can enter by typing IEX and clear the terminal by typing clear.
Now that you have installed Elixir and are familiar with its main features, you are ready to explore this powerful programming language. In the next article, we will go into detail about Elixir and look at some practical examples.