Why Elixir should be your next language if you’re a Pythonista

Photo by Fabian Grohs on Unsplash

I started to learn Elixir programming language recently, and as a Pythonista I am liking the language a lot. While the syntax of Elixir is very different from Python, remembering more Ruby (the language that I current work with), Elixir have some of the same philosophy as Python, making it a very good choice for Pythonistas looking for their next language.

So what is the Python philosophy? It can basically be described in the famous poem included in Python itself, “The Zen of Python”:

The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren’t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one — and preferably only one — obvious way to do it.
Although that way may not be obvious at first unless you’re Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea — let’s do more of those!

Elixir have most of the same qualities described in “The Zen of Python” poem. For example, the principle of Explicit is better than implicit, we have even a citation from Elixir documentation itself:

Elixir already provides mechanisms to write your everyday code in a simple and readable fashion by using its data structures and functions. Macros should only be used as a last resort. Remember that explicit is better than implicit. Clear code is better than concise code.

However, talk is cheap, show me the code. So let’s see some examples.


Imports and Modules

Principles:
In the face of ambiguity, refuse the temptation to guess.
Namespaces are one honking great idea — let’s do more of those!

Module is probably one of my favorite Python features. To create a new module in Python, you can simple create a new file, let’s say hello.py with the following contents:

def world():
print(“Hello World!”)

That’s it, we can already access this module from any other file in the same directory by calling:

import hello
hello.world() # "Hello World!"

This small example already shows some of the nice things about the Python’s module system: our module have the same name as the file and the import generates a namespace hello. So, how is a module in Elixir?

defmodule Hello do
def world do
IO.puts("Hello World!")
end
end

In Elixir we need to explicitly declare a module. However, we can’t have a function definition outside a module, or we get the following error:

** (ArgumentError) cannot invoke def/2 outside module
(elixir) lib/kernel.ex:5142: Kernel.assert_module_scope/3
(elixir) lib/kernel.ex:3905: Kernel.define/4
(elixir) expanding macro: Kernel.def/2
hello.exs:1: (file)

And how can we call this function? Like this:

Hello.world() # "Hello World!"

So no import is necessary, however we need to pass the whole module name during the call (same as an import). This explicit approach is less magic than Rubyists (specially in Rails community) like, since it requires more typing. However it makes things clearer and explicit, something that the Python community values.


Class and State

Principles:
Explicit is better than implicit.
Readability counts.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.

So in Object Oriented languages (like Python) the traditional way to store state about something is by creating a class and instancing an object based on that class. In Functional Programming, we don’t have a concept of objects so we need to do state transformations. However, are they that different?

Let’s define a class in Python:

class Hello:
def __init__(self, name):
self.name = name
def greet(self):
print(f"Hello {self.name}!")
hello = Hello("Thiago")
hello.greet() # "Hello Thiago!"

We have some interesting design decisions in here: each method inside a class receives an explicit self parameter (that can be named as anything) that stores the current state of the object.

In Elixir we don’t have a class because the language is functional, however we have a similar pattern of explicitly declaring our state:

defmodule Hello do
def init(name), do: %{name: name}
def greet(person) do
IO.puts("Hello #{person.name}!")
end
end
Hello.init("Thiago") |> Hello.greet() # "Hello Thiago!"

So in the above code I am create a new module Hello, with two functions: init(name) and greet(person). Inside init(name) I simple takes a name parameter and returns a dictionary, that represents our initial state. Afterwards, I use Elixir’s Pipe operator to transform our initial state, that is the same as calling:

Hello.greet(Hello.init("Thiago")) # "Hello Thiago!"

Looking closely, the Pipe operator in Elixir can represent our self parameter in Python. That is: a parameter that stores our current state. In Python you can transform the initial state of the Object modifying your self parameter, while in Elixir you can pass a chain of functions using Pipe operator. If you’re still not convinced, see this Python code:

Hello.greet(Hello("Thiago)) # "Hello Thiago!"

Yes, this is an instance being explicitly passed to a class method.


Error handling

Principles:
Errors should never pass silently.
Unless explicitly silenced.

Python community has a different approach from most languages, even considering the languages that uses Exceptions for errors. That is because this is considered a good practice:

try:
do_something()
exception SomethingError as e:
handle_error(e)

Other languages prefer to avoid handling exceptions unless strictly needed, however in Python an exception is part of the flow to treat possible errors. It is an explicit way to handle exceptions, though, since it is a good practice to only catch exceptions that you care.

Elixir has exceptions, however it is not common to be used for treating errors. A more common idiom is this:

case do_something() do
{:ok, something} -> "Success! Here we have #{something}"
{:error, _} -> "Oh noes!"
_ -> "What?!"
end

Most functions in Elixir returns a tuple {:ok, something} when an operation is a success, or {:error, something_else} when an operation results in a error. This forces you to either treat errors explicitly, or to use something like do_something!() that will returns something without the tuple to ignore error handling. The _ is a special case: it matches anything that the other cases didn’t match. This also includes exceptions happening in do_something() call.

While using a different construct, both Python and Elixir makes error handling explicit.


Closing up

So sure, Python and Elixir are very different languages.

However, what I presented in this post are some examples where Python and Elixir, while being different, have similar philosophies about language design. There are many other examples where Python and Elixir have similar design choices, however I decided to finish the post here to not extended it too much.

So if you like Python because it is a clean and consistent language, you will probably also like Elixir for the same reasons.

Trust me, I am a Pythonista and I am liking Elixir a lot.