My introduction to Elixir programming language
Mar 4, 2016Today I’m going to talk a bit about Elixir, the programming language that I chose to write my project in. I will start by saying that this is not a comprehensive introduction, but just my take on what are the Elixir’s most interesting features, and why I think this language has a bright future ahead of it.
If you would like to learn more about Elixir, there are really good resources out there in the Internet, notably:
- A getting started guide - Official guide on Elixir website, one of the best I’ve seen.
- Programming Elixir 1.2 - A book by the one and only Dave Thomas. I’ve read it and I wholeheartedly recommend it.
- Portal Game in Elixir - by Jose Valim, the author of Elixir. I remember reading this article was the first thing that got me into the language.
Erlang - the battle-tested foundation of Elixir
I will start my introduction by talking about a different language, called Erlang. Erlang is a 30 years old language built specifically to be used in distributed and highly available applications. It has proved its value over the years in telecommunications industry where it had been employed to deal with telephone connections at massive scale, but it also has been used by companies like Whatsapp or Facebook for their most connections-heavy applications. Such strong foundations are very important for me - as a pragmatic programmer, I’m looking for solutions that will last, and Erlang ecosystem is definitely a good ingredient to build one.
So how does Elixir utilize Erlang? First of all, it’s written in it. You can look at the Elixir’s Github repository, You will obviously notice that Elixir is a huge Erlang’s code base. But there’s more than that. When you write code in Elixir, you can utilize standard Erlang library. You will find a lot of useful 3rd party libraries written for Elixir or Erlang itself. In my opinion this helps a lot in Elixir’s adoption, because lack of decent library ecosystem is often a main problem of young programming languages. And last but not least, Elixir utilizes the power of BEAM, an Erlang’s virtual machine. It’s very interesting concept itself, and I won’t go much into detail of it here, let me just mention that Erlang VM in some way behaves very similarly to the Operating System - in fact, there is a special version of it running directly on Xen - without any additional operating system in between.
Elixir - let’s go meta
Enough about Erlang, let’s talk about our actor in a leading role, Elixir, and what have made me fall in love with that language. There are many reasons to that, but I will start with a small piece of code that comes from the book “Programming Elixir” by Dave Thomas:
def decode_response({:ok, body}), do: body
def decode_response({:error, error}) do
{_, message} = List.keyfind(error, "message", 0)
IO.puts "Error fetching from Github: #{message}"
System.halt(2)
end
def process({user, project, count}) do
Issues.GithubIssues.fetch(user,project)
|> decode_response
|> convert_to_list_of_maps
|> sort_into_ascending_order
|> Enum.take(count)
|> print_table_for_columns(["number", "created_at", "title"])
end
def printable(str) when is_binary(str), do: str
def printable(str), do: to_string(str)
This excerpt is not complete (there are few functions missing, as well as module definition, which means the example will not compile), but it perfectly depicts 2 out of my 3 favorite features of Elixir: pattern matching and pipe operator. let’s talk about them in detail.
Pattern matching - the beautiful “if”
I will start with the fact that Elixir is a functional programming language - the central concepts of it are functions and data immutability. This is important especially if you come from the object-oriented background like Java or C++. In Java your main friends are a class and an object instance of that class, and you have a lot of these. In Elixir, you have a lot of functions, and you want them to be clean and easily understandable. Your main enemy in such situations is a conditional statement, whether it is “if” or “switch”, it makes code harder to reason about - especially if you have nested several conditionals together (that’s something you always try to avoid).
In Elixir, you have a great help in form of pattern matching:
def decode_response({:ok, body}), do: body
def decode_response({:error, error}) do
{_, message} = List.keyfind(error, "message", 0)
IO.puts "Error fetching from Github: #{message}"
System.halt(2)
end
iex(3)> ExampleModule.decode_response({:warning, "body"})
** (FunctionClauseError) no function clause matching in ExampleModule.decode_response/1
iex:2: ExampleModule.decode_response({:warning, "body"})
This gives a tremendous value to limit the “ifs” in our code, but also makes code much easier to reason about - the error handling case like the code above is a great example where we see exactly which part is responsible for dealing with errors, and which processes the happy code path.
There is also a mechanism called guard clauses which I consider to be a logical part of pattern matching (although technically it might not be). In the code example I already used before we can observe:
def printable(str) when is_binary(str), do: str
def printable(str), do: to_string(str)
def combine(a, b) when a > b, do: a * b
def combine(a, b), do: a + b
Pipe operator - from unix with |love>
The second feature that attracted me to Elixir is pipe operator. Pipe operator is not something unique to Elixir - in one of The Changelog’s episodes, Jose Valim (author of Elixir) mentions that he borrowed that idea from F#, functional programming language that belongs to .NET family. Before we talk more about this operator though, let’s first talk about another Elixir’s property, which is data immutability. Its basic principle is that data, once created, cannot be modified in place, which in turn means that if we want store that modified data, we will create a new data entry for it. It’s very similar to Unix pipe (“|”) use case - when we use cat “file.txt” | grep “some text” in linux, we almost never want to save it back to file.txt - instead we’ll save it to a new file, or use it instantly. In that case we can talk about data transformation - and Elixir is all about transforming the data. So similarly to Unix, Elixir has its pipe operator, which differs from the standard “|” character by having an additional “greater than” sign after it.
Let’s see the example again:
def process({user, project, count}) do
Issues.GithubIssues.fetch(user,project)
|> decode_response
|> convert_to_list_of_maps
|> sort_into_ascending_order
|> Enum.take(count)
|> print_table_for_columns(["number", "created_at", "title"])
end
|> operator causes the output of Issues.GithubIssues.fetch(user,project) function call to be bound with the first argument of function _decoderesponse, which output of is in turn bound to the first argument of function _convert_to_list_ofmaps, and so on. Notice that some of the functions in that chain still take arguments - those arguments will be passed to the function starting from the second argument, as first is reserved for pipe operator.
Such code composition gives a great insight into what data transformation is happening here, and in what order. Also, if we need to modify/remove/add one of the transformation’s steps, it’s very clear how it should be done.
Meta programming in Elixir - the scary powerful wizard in a high tower
I mentioned that pattern matching and pipe operator are my 2 of 3 favorite Elixir features, and the third one couldn’t be anything else then macros and meta programming. It’s a big addition compared to the Erlang, and I remember reading an article where Jose mentions Meta programming and Protocols as the most important language features. I used the word “scary” because meta programming is indeed super powerful, but at the same time it might be dangerous - once you learn how many things can be done with help of macros, you might be tempted to use them even where it doesn’t really make much sense. Macros are expressions in code that are executed at compile time, and replaced with computed code. I won’t go into detail about macros and meta programming, because this topic requires really good explanation, so I will recommend two sources to learn more about it:
- Great presentation by Chris Mccord, where he talks about meta programming in Elixir at NDC conference in Norway,
- Metaprogramming Elixir - an awesome book, also by Chris Mccord, which goes into detail of macros wizardry.
But to give some perspective into how powerful macros in Elixir are, consider following piece of code (shamelessly taken from Chris’ presentation):
defmodule Notifier do
def ping(pid) do
if Process.alive?(pid) do
Logger.debug "Sending ping!"
send pid, :ping
end
end
end
Summary - just the tip of the iceberg
This introduction was only a brief summary of features that attracted me to the language. There are so, so many of them they may attract you to Elixir, and I encourage you to read about it and try it yourself! As of me, you can expect more posts about Elixir and its ecosystem, including Protocols, Streams, Processes, OTP, and others, as my work on the project progresses.
In next post, I will talk more about my project (game), and how can we model it Elixir language. Stay tuned!