Here's a personal favourite. Elixir is a dynamic, functional programming language built thinking about scalability and parallelism. It is built on top of Erlang, so performance wise it's really powerful.
It is not quite as popular as other programming languages out there and it has a relatively small community due to being still young, but its capabilities are still great.
I think developers tend to favour GoLang for exploiting distributed tasks or Clojure and Scala on the functional aspect, yet Elixir seems to me like a nice place in the middle.
Some of the issues around Elixir revolve around lack of popularity for functional languages, reduced compatibility in some hostings and sometimes poor error readability when things go wrong.
This article is going to be divided into 3 sections: Installation, Basics and Capabilities.
Installation
Elixir can be installed in many different ways, you can find the way that best works for you on their installation page. My preferred way (on MacOS) is to use a package manager like homebrew. That way I can stay on top of updates and releases fairly easily.
The installation using homebrew is fairly simple. You can use
$> brew install elixir
and brew will do all the heavy lifting for you. You can test your installation using elixir --version
.
Using a version manager
As with other programming languages, you may find yourself needing to use multiple versions of the same language on different apps, specially on microservice environments. To resolve this problem, you can either use dockerized setups or use a version manager.
There are multiple version managers available for Elixir. I particularly like and use kiex. It's really simple to use and to be fair, it's the one I've had less trouble with.
To install kiex, they provide an installation script you can run from your shell
$> curl -sSL https://raw.githubusercontent.com/taylor/kiex/master/install | bash -s
From there you can follow the instructions if necessary. This script will install kiex in your $HOME/.kiex
Usage is fairly simple as well, you can check the available versions installed on your system with
$> kiex list
=* elixir-1.8.2
# => - current
# =* - current && default
# * - default
or generate a list of available releases for you to download with
$> kiex list known
Getting the available releases from https://github.com/elixir-lang/elixir/releases
Known Elixir releases:
0.7.2
0.8.0
...
1.11.2
1.11.3
From where you can pick the version you want to install and let kiex do it for you
$> kiex install 1.11.3
When going around project folders, you can select the Elixir version you want to use (provided it's been installed), via kiex with
kiex use 1.8.1
There are other interesting things you can do listed in their documentation. For now, I'll stick to the simple usage commands.
Basics
Elixir is a functional programming language, so writing code in it may not be the most intuitive thing for everyone. There's so much to learn about it, so feel free to check their documentation if you have specific doubts or are interested in learning more.
To make it easy for ourselves to start testing things out, we'll use a really neat tool called the Elixir Interactive Console (iex - Interactive Elixir). Similarly to the ruby irb
or Python interactive shell
, it will allow us to test simple concepts first.
If Elixir is correctly installed, you should be able to enter the iex from your shell by typing the iex
command
$> iex
Erlang/OTP 23
Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
Once in the console we can start playing around with Elixir.
Elixir has specific data types, but you don't have to explicitly declare them. The basic ones include
iex> 1 # integer
iex> 0x1F # integer
iex> 1.0 # float
iex> true # boolean
iex> :atom # atom / symbol
iex> "elixir" # string
iex> [1, 2, 3] # list
iex> {1, 2, 3} # tuple
These data types are pretty similar to other programming languages. So you can perform operations on them, pass them around in variables etc. There are a few important differences however, which will be mentioned towards the end of this section.
Using these data types is also pretty similar to other programming languages
# Arithmetic operations behave the same
iex> 1 + 2
3
iex> 5 * 5
25
# String interpolation behaves the same
iex(1)> string = :world
iex(2)> "hello #{string}"
"hello world"
# And you can print using the IO module
iex> IO.puts "hello world"
hello world
:ok
# Notice the return value of the method is :ok. More on that later
To perform operations on data types or variables, you will have to use the methods from its class. Take the String class for example
iex> String.upcase("hello world")
"HELLO WORLD"
iex> String.split("foo bar")
["foo", "bar"]
iex> String.reverse("abcd")
"dcba"
There are however, quite a few functions that are available in the global scope
# To check whether a variable belongs to a data type
iex> is_boolean(true)
true
# To check if a variable is NULL
iex> is_nil("hello world")
false
Pattern matching
Now, here's something that might trip many, and it's the =
operator. In most languages this operator is used to assign values to a variable, and at first sight in Elixir it may look just the same
iex(1)> hello = "hello world"
"hello world"
iex(2)> hello
"hello world"
But consider the following sequence
iex(1)> hello = "hello world"
"hello world"
iex(2)> "hello world" = hello
"hello world"
iex(3)> "goodbye world" = hello
** (MatchError) no match of right hand side value: "hello world"
What happened?
It turns out that the =
is actually called the match operator, or pattern matching operator. Its goal is not to simply assign values but to compare the two sides of the equation.
This is pretty useful in more complex situations, like destructuring expressions for example
iex(1)> {a, b, c} = {:hello, "world", 42}
{:hello, "world", 42}
iex(2)> a
:hello
iex(3)> b
"world"
iex(4)> c
42
But more interestingly when comparing specific types as a result of a particular operation.
Consider the following example
iex(1)> {:ok, result} = {:ok, 200}
{:ok, 200}
iex(2)> result
200
iex(3)> {:ok, result} = {:error, 401}
** (MatchError) no match of right hand side value: {:error, 401}
Notice that :ok
and 200
are values being matched. When those values do not match, we have received unexpected data, and the operation will be invalid. Do the codes seem familiar to you? 200
and 401
are common http response codes, and you can use pattern matching to handle http responses in a neat manner
# Simple http request example for pattern matching response
def http_request(url) do
case HttpRequest.get(url, headers(), []) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
body
{:ok, %HTTPoison.Response{status_code: 401}} ->
IO.puts "[ERROR] Authentication failed."
nil
{:ok, %HTTPoison.Response{status_code: status_code}} ->
IO.puts "Unexpected http response: #{status_code}"
nil
{:error, %HTTPoison.Error{reason: reason}} ->
IO.puts "Unexpected error: #{reason}"
nil
end
end
Process calls in Elixir typically return a status and the result. In the previous example you can see either the tuple {:ok, response_object}
or {:error, error_object}
for which we can pattern match accordingly and handle each scenario separately. The Pattern match can be as complex as you'd like.
There's more much to talk about this complex, yet amazing programming language. I will release a series dedicated to Elixir soon.
Capabilities
The real advantages of Elixir come when you're trying to parallelise tasks. Concurrency, scalability and fault tolerance are built into the language.
In Elixir, all code runs inside processes, which are isolated from each other and run concurrently and communicate passing messages between each other.
These processes are quite lightweight, for which at any given time you may be running a great amount of them.
This allows for incredible fault tolerance. Processes can be restarted quite quickly without big performance implications. In fact, Elixir's strategy for error recovery is simply killing the processes and starting it again. This is a "fail fast" strategy. Elixir developers made the design decision that letting a program fail and recover fast was a better strategy than defensive programming and trying to predict any possible error.
Additionally to standard processes, Elixir has GenServers and Supervisors. These are in charge or keeping state and monitoring other processes respectively. GenServers and Supervisors are the building blocks of server-client relationships.
In terms of parallelism, Elixir has built-in modules like the following
results = Task.Supervisor.async_stream_nolink(
MyApp.TaskSupervisor,
MyApp.list_items(),
(fn item -> MyApp.process_item(item) end),
[max_concurrency: 50]
)
In which we ask a supervisor to spawn 50 processes concurrently to deal with a list of items that need, well, processing. This is particularly useful in scenarios where we need to query external APIs or run particularly slow operations on each item.
If we have a list of 1000 items and 50 concurrent processes working on it, it only takes 20 rounds to get your results, and more importantly you can have them all combined at the end without dealing with any of the concurrency yourself.
Conclusion
There's much more to talk about Elixir, hopefully this brief introduction is enough to keep you interested.
Every programming language has a thing for which they're good at. Elixir is great for concurrent processing and easy parallelism. There are, of course, other solutions like GoLang or if you need greater data scale, tools like Apache Spark are better suited for the job. But Elixir should be considered given its simplicity and functional approach. Remember there's no one way to solve a problem.
For more topics, check my profile or visit rarias.dev.
Top comments (1)
Cool