What is Elixir and why you should try it
If you've been a part of the Ruby/Node/Python/Erlang community chances are that you've heard about Elixir - A language built by José Valim to make the benefits of the Erlang VM more accessible to the masses.
This article will give you a short introduction to Elixir and hopefully inspire you to dig deeper into its fantastic ecosystem.
What defines Elixir?
Elixir is a dynamically typed functional language based on the actor model that is running on the BEAM (Erlang virtual machine). The actor model is a mathematical model for concurrent/parallel computation through isolated primitives (called processes) that communicates through message passing, much like servers in a micro-service architecture. This model allows Elixir apps to scale both vertically (number of cores per machine) and horizontally (multiple nodes) if utilised correctly.
There is a lot of things that makes Elixir interesting from a developers point of view:
Immutability: Immutable structures means that you can never reassign or edit data. Instead you’d have to return new values whenever a change has to happen in a data structure. This means that we will have full transparency in our code as the only things that matter in a function is the input, and the output - making unit testing and debugging so much easier.
Pattern matching: One of the things newcomers get the most excited about in Elixir is its ability to pattern match on almost everything, almost everywhere (including function headers). This is something everyone, especially coming from the Ecmascript world will love:
def find_person do
{:ok, %{name: "Johan", age: 8}}
end
# You can pattern match on the second element
# in the tuple
{:ok, person} = find_person()
> {:ok, %{name: "Johan", age: 8}}
person
> %{name: "Johan", age: 8}
# You can use pattern matching to only get the
# values you care about from the person
{:ok, %{name: persons_name}} = find_person()
> {:ok, %{name: "Johan", age: 8}}
persons_name
> "Johan"
# If the pattern doesn't match an error will be raised
{:error, person} = find_person()
** (MatchError) no match of right hand side value: {:ok, %{name: "Johan", age: 8}}
Pipeline operator (|>
): One of the reasons some people frown upon functional
programming is that it can get confusing when chaining functions. Consider the case
where you just want to chain a few different mutations:
multiply_by(integer_from_string(remove_prefix("number-2", "number-")), 2)
The pipe operator in elixir will help you write chained calls in a much more elegant way where it will take the result from the previous call and use it as the first argument:
"number-2"
|> remove_prefix("number-")
|> integer_from_string()
|> multiply_by(2)
Tail recursion: Elixir can optimise recursive functions to reuse the current call stack, which makes memory management much more efficient. This is important in our next item on the list.
Multiple functions with same signatures: Defining multiple function headers with the same signature isn't new to anyone with a history in Java, but there is a clear improvement whenever pattern matching and tail recursion is added to the mix. With pattern matching we can use function headers as an replacement for conditionals:
defmodule Healer do
@doc "heals a character with the passed amount of health points"
def heal(%{health_points: 100}, _amount), do: raise "Cannot heal a character with full health"
def heal(%{health_points: 0}, _amount), do: raise "Cannot heal a dead character"
def heal(character, 0), do: character
def heal(%{health_points: current_health_points} = character, amount) do
health_points = Math.max(current_health_points + amount, 100)
character
|> Map.put(:health_points, health_points)
end
end
Superb tooling: While most languages has decent tooling now a days Elixir code includes a great set of tools such as:
mix
- A modern build tool with great extensibility.hex
- An immutable package manager (looking with disappointment atnpm
),ExUnit
- A test framework that runs your test suite in parallel.
The Elixir core team is also involved building a lot of high profile libraries with the same level of quality:
Phoenix
- The most common web framework (often compared to Ruby on Rails)Ecto
- A database wrapper built on top of the repository pattern with a language integrated query builderBroadway
- Concurrent and multi-stage data ingestion and processing
Powerful macro system: While the general consensus in the community is that explicit is better than implicit, code generation through macros can prove to be a great tool in certain cases. What’s good in Elixir is that all macros are expanded at compile time so it won’t harm your runtime performance.
Interoperability with the Erlang ecosystem: Because of how Elixir compiles down to the same byte-code as Erlang they can be used seamlessly together. This brings in a huge ecosystem with more than 20 years of battle testing and bug fixes.
What makes Elixir a good choice?
While language features are something you can reliably lean back on and be happy about using they seldom provide good enough arguments to use in a business context or in front of developers that have heavy experience in another language. This is why it is important to stress that Elixir can provide other values to business and in some cases even be seen as an competitive advantage.
Computational performance vs developer productivity
When it comes to building software the business always dictates the requirements. When building software for autonomous vehicles the requirements on the software probably means C or another low level language is the only option. For most other projects the speed of building features might be more important.
Ruby (on Rails) is a perfect example of where productivity is the main goal and that is why it is so popular in the startup scene where ability to evolve could be the difference between success and failure.
Elixir really shines in the sense that it can offer the same kind of productivity as you’d get in a Ruby on Rails project but at the same time take you a lot closer to C in terms of performance (it’s actually quite common to see respond times in μs). In reality it means that a startup could have the same productivity but doesn't have to put a lot of work/money into improving performance once customers start to line up.
Focused around maintainability and scalability
It’s a common joke that all software becomes technical debt as soon as it has been written and the sad part is that a big portion of it is true. Codebases that have seen a few different developers and have been under active development for more than a year tends to be a bit messy in terms of quality, documentation and ease of understanding.
The Elixir core team puts a lot of effort into making Elixir as a language as easy to maintain as possible but also to teach the community about how to write software that is easy to understand. This comes in forms of books, guides and blog posts and documentation on how to be transparent, use domain driven design, prevent confusion and much more. In Elixir documentation is also a first class citizen with support for doc tests where you can let your test suite verify that the provided examples in your documentation is correct.
The functional paradigm of the language also helps with this as functional code tends to be more focused around data transformation in terms of input -> output. This will in most cases reduce the experienced complexity and make your life easier when it’s time to refactor.
Low Latency over high throughput
One of the big selling points for choosing Elixir is not really Elixir specific at all but rather something all languages running on the BEAM has. The thing is called pre-emptive scheduling and is what makes Elixir and its siblings so unique. Preemptive scheduling means that the virtual machine will decide how many CPU cycles a task may take and pause it for a while in favour for another task in case it doesn't finish, just like a modern operating system doesn't slow down because one program is taking a lot of resources.
This gives a guarantee that no task, no matter how much time it needs to finish can't block another one from executing. In reality it means that if you have a page that is fairly simple and quick to load, will continue to load fast even though your servers are under a lot of stress. Isn't that exactly what we want from our web/API-services?
Diving deeper
If this caught your attention, why don’t you head over to elixir-lang.org and look at their getting started guide. Happy exploring!
Comments