Introduction to Elixir
Elixir is a functional, dynamically typed language that runs on the Erlang VM (BEAM). It is a language that uses the battle tested features of Erlang, while providing the comfort of a modern programming language. Elixir’s language design is similar to that of Ruby, but it is also inspired by e.g. Clojure and Erlang.
The Erlang VM is known for its capabilities of running fault-tolerant, distributed and low-latency applications. By running on the Erlang VM, these capabilities are available when writing Elixir applications.
History and background
While Elixir is a relatively new language, its foundation was created decades ago, with Erlang/OTP. Erlang is a functional programming language created for developing fault-tolerant, distributed and low-latency applications, specifically for the telecom industry. It was developed at Ericsson in the late 1980s and was used for their telephone switching systems. Erlang is often used as a general term of the language Erlang and Erlang/OTP. OTP, or the Open Telecom Platform consists of the Erland runtime, including the Erlang VM, libraries and middleware you can use in your applications, and a set of design principles on how to develop Erlang applications.
Elixir was developed by Josè Valim, and first appeared in 2012. Inspired by languages such as Ruby, Clojure and Erlang, Jose created a new language that had the capabilities of Erlang and its ecosystem, with a developer friendly syntax, lowering the threshold for developers to take advantage of the Open Telecom Platform. Since the appearance of Elixir, many companies has taken use of the language and its underlying platform. For instance, Discord uses Elixir and OTP in its chat infrastructure, where they have used Elixir services to serve more than 12 million concurrent users, sending more that 26 million WebSocket events to clients every second. Check out the blog post here.
Structuring code using functions and modules
Named functions are defined using the
def keyword, followed by the function name and the function parameters. The function scope is surrounded by a
Named functions must live inside a module. We use modules to group functions together, similar to a namespace in other programming languages, such as Java and C#. Wrapping our function inside a module, leaves us with the following result:
To call a function from outside our defined module, we simply have to specify the module and function names.
Greeter.greet(“John Doe”). Structuring code in Elixir is very simple. We create contexts and boundaries using modules which contain functions.
The return value of a function is always the last expression evaluated in the function scope. Given the input
"John Doe", the return value of the
Greeter.greet function is
"Hello John Doe", even though we did not explicitly specify that the value would be returned, which is common on other languages.
What is usually the assignment operator,
= in most languages, is called the match operator in Elixir. The match operator will attempt to match the right and left and side of the
E.q., matching a list of unbound values in a list with a list of values on the right hand side, will assign the unbound values.
In the example about,
a will be assigned
If we wanted to use the match operator for destructuring, we would use the following syntax.
We initially set the fields
age in the
person map. Afterwards, we match the left and right hand side by saying some field age should be matched to
While we are not able to mutate existing data, we can rebind variables with new completely new values.
If we want to disallow variables being assigned when pattern matching, we can use the pin operator. Using the pin operator, the patterns on the left and right hand side will use the variables value to pattern match instead of trying to bind a new value to the variable.
The pipe operator is used to chain output from one expression as the input to another function, as the first argument.
If we wanted to nest functions and use the output from one function as the input of another, we would have to write something like this in many other languages.
The equivalent using the pipe operator could be something like this:
The pipe operator allows us to easily compose simple expressions into complex ones, while maintaining a readable code structure.
Working with enumerables
Enumerables in Elixir are collections that can enumerate over, such as lists, maps and ranges. Elixir has the abilities to work with enumerables that you would expect to exist in a functional programming language. Three commonly used functions are
reduce, which all can be found in the
In order to map over a collection of elements in Elixir, we would use the
map/2 function. It accepts two arguments, a enumerable and a function. The function is applied to each item, producing a new collection based on the functions return value.
If we wanted to double every number in a collection, we could simply map over the collection with a function that doubles each number.
If we want to filter out some elements from our enumerable, we can use the
filter/2 function. Filter accepts to arguments, an enumerable and a function which is evaluated for every item in the collection. If the function returns
true, the element is included in a new collection, otherwise it is omitted.
If we wanted to exclude all non even numbers from a list, we could do the following:
If we wanted to transform our enumerable to a single value, we could apply the
reduce/3 function. It accepts 3 arguments; a collection, an initial value for the accumulator and a function, which is called for each element in the collection with the value and the current accumulator for each step of iteration.
If we wanted to multiply all the values in an enumerable, we could do the following:
Chaining transformations using the pipe operator
Elixir is excellent at transforming data due to its abilities in composability by chaining expressions together. If we had different operations that needs to be applied to a collection in sequence, we could achieve this using the pipe operator.
If we wanted to perform all the operations described above using the
reduce functions, we could compose a larger expression by doing the following:
If you would like to have a further look at the available functions in the
Enum module, have a look at the official documentation.
We have different ways of working with control flow in our Elixir code. One that will be familiar to most programmers is the if-else operator, which does exactly what it says; it branches the flow into a if, and if present, an else block.
Similarly, we have the case operator, which compares values against patterns until a matching pattern is found, using the pattern matching capabilities of Elixir.
The case operator will try to match the input with a pattern, and return the expression on the right hand side of the arrow. It is common to have a "catch-all" clause at the end
_ -> , which catches all the cases that cannot be matches. This allows us to create case clauses that can remain pure, always returning a value or executing some computation for all input passed.
In the example above, the following output would be produced from the input passed. The three first lines are able to match the right and left hand sides respectively, with the last call evaluated using the catch-all clause.
The cond operator will try to match a set of conditions until it finds the first one that returns true. It will do so from top to bottom. It is also common to use a catch-all clause with the cond operator, to ensure that every input is handled.
In the example above, the cond block will try to evaluate each block from the top to bottom until it returns true. If no blocks return true, we have a catch-all by having a
true -> block at the end, which will always be evaluated if none of the blocks evaluates to
All the control flow operators described above can be used for assignment, even using if-else.
The content of odd_even_message will be assigned either
"This is even" or
"This is odd" based on the value of x. The same pattern can be used for
cond, assigning a value based on the evaluation of the control flow operator.
To solve the challenges of concurrency, Erlang has adopted the actor model pattern. The actor model is a conceptual model of computation design to help solve the challenges of computational concurrency. The actor is the computational entity in our software. A single actor can send messages, receive messages, create new child actors or terminate any existing child actors. At the root of our application, we have our root actor, which is responsible for creating more actors which eventually creates a process tree which represents the desired state of our application.
The actor model is implemented in Elixir and Erlang through BEAM processes. Processes in the Erlang VM are lightweight, and must not be confused with operating system processes. They are low-cost processes managed by the virtual machine, which are fast to create, terminate and have little memory overhead. This model allows for applications running on the Erlang VM to scale very well.
Other language feature resources
It would be challenging to write about all the features of a programming language in a single blog post. If you are courious about more of Elixirs language features, elixirschool.com is a great resource for learning, along with the official documentation at elixir-lang.org.
There are many frameworks available for different problem domains, ranging from web development and numerical computing to IoT programming. Phoenix is probably one of the more known frameworks available in the Elixir ecosystem. Phoenix is a web framework, which offers high developer productivity, performance and concurrency. It has become a go-to web framework for Elixir, similar to what Ruby on Rails is for Ruby.
Just like Ruby on Rails, Phoenix allows rapid development with a clear convention on how to architecture our applications. It ships with scaffolding tools, which allows us to quickly create the our application resources.
Phoenix is a battery included web framework, which allows developers to work very quickly in creating applications while maintaining clean code due to its clear convention and great documentation.
Elixir with its ecosystem provides a way to create reliable, fault-tolerant and distributed applications, with a low entry barrier for developers. It gives us a programming model with great solutions to concurrency challenges out of the box, with little overhead.
Elixir is a great entry to functional programming. Learning functional programming and the coding style it encourages, may help developers increase their skills in other imperative and object oriented programming languages, e.g. by writing small, pure and testable functions and keeping a clear division between your business logic and side effects.
One of the challenges with using Elixir is adoption and finding developers. While the technology may be battle tested used for decades, without existing adoption, it may often be difficult for stakeholders to take the risk of adopting new technologies. While the risk may be high, developers and stakeholders have a opportunity to define tomorrows mainstream technologies, by not only exploring established options.