Do's & don't's of writing Elixir
Tags: elixir, programming, python, tutorial
Published: 2022-11-15 08:18:33


In this article, we are going to explore do's and don't's of writing Elixir code. We will briefly learn the key challenges of switching from other languages to Elixir. For simplicity, we will write all the examples and code snippets using Python & Elixir and learn some common mistakes that programmers often do when switching to Elixir from a different programming language.

## What is Elixir?

Elixir is a functional progamming language that runs on the powerful Erlang VM. Elixir takes full advantage of the VM and provides the perfect environment to the developer to build fault tolerant,low-latency, distributed systems.

According to stackoverflow's 2022 Developer Survey, it is the Second [most loved Programming language](https://survey.stackoverflow.co/2022/#section-most-loved-dreaded-and-wanted-programming-scripting-and-markup-languages) of the Year.

The Elixir Community is growing as more and more people & companies adopting elixir to their code base.

## The Key Challenges of Switching to Elixir

Unlike other functional programming languages such as Erlang, Clojure, etc. Elixir has a more beginner friendly syntax. This also opens the door to bring the baggage of other programming languages to Elixir.

For example, Python programmers with no previous functional programming experience might attempt to apply coding patterns of Object Oriented programming that are not suitable for Functional Programming. Even though, they might get away with writing codes that are more "Pythonic" than "Elixir". It will hinder their growth & productivity in the long term.

The lack of functional programming knowledge will also prevent them from taking advantage of various functional programming paradigms. It will also challenge their core concepts in at least three different layers:

1. The Functional way of thinking (i.e. immutability/streams/transformation/pipeline)
2. OTP way of thinking (i.e. concurrency, fault-tolerence, distributed system)
3. Patterns and Pipes.

So they will have to unlearn what they already know and think with a fresh perspective. This initial step is relatively hard but once, they master the concepts they will become productive in no time.

Now let's take a look at a few examples:

## Mutable vs Immutable

In a Python environment, Variables or objects are mutable. Consider the following code snippet:

```python
my_list = [1, 2, 3, 4]
do_something(my_list)
print(my_list)
```

Here, we can't reliably say, what will be the output of the print statement without looking at the source code of function `do_something`. In Python, Lists are passed as reference, so it is possible that the `do_something` function can alter the original `my_list` e.g. add something to the list or remove a thing or two. If we think in concurrency or parallel programming this could lead to unintended bugs. Two processes accessing the same object can get different values based on execution order or moment.

In Elixir, this is not an issue cause, variables are always immutable:

```elixir
my_list = [1, 2, 3]
do_something(my_list)
IO.inspect(my_list)
```

Here the output of `IO.inspect()` is guaranteed to get the list `[1, 2, 3]`. In other words, the `do_something()` function can not modify the my_list variable. Even if we spawn hundreds or thousands of processes and access `my_list` all of those processes will get the same value of `my_list`. This is also true for complex data such as Nested List of Lists, Maps, or Database Records, etc.

Mastering the concepts of immutability is essential to write Good Elixir Code.

## Nested Function Calls vs Piping

Nested function calls are common in most programming languages. When we want to pass the return value of a function as an agrument to another function the nesting occurs. it is more like a series of transformation to data. Elixir also allows nested function calls to transform data. Consider the below example:

```elixir
decorate(bake(add_flavor(mix(ingredients))))
```

You will have to take some time to read the statement carefully and figure out which function is getting called in which sequence, what are their effects. And, how the data is getting transformed in each steps.

Fortunately, Elixir solves this problem elegantly by introducing a new operator called "Pipe" (`|>`). It takes the result of the expression to its left/top and inserts it as the first parameter of the function invocation to its right/bottom. Using the pipe operator we can rewrite the above example:

```elixir
ingredients
|> mix()
|> add_flavor()
|> bake()
|> decorate()
```

Here the steps & sequence of function calls are clear. We are passing the `ingredients` to `mix()`. The result of `mix()` is getting piped to `add_flavor()`. Then we `bake()` the result and finally we `decorate()`

The Bottomline is, do not write Elixir codes with nested function calls use piping instead. It will make your code clear, concise and much more readable.

## Functions & Pattern Matching

Pattern Matching is one of the most powerful feature of Elixir Programming Language. Mastering pattern matching techniques will boost productivty and improve code quality. The key is to know when and where we can apply pattern matching. Let's explore the below python function:

```python

def flip(coin):
    if coin == "head":
        return "tail"
    elif coin == "tail":
        return "head"
    else:
        return "invalid"
```

This function simply flips a coin. If the coin is `head` it returns `tail` and vice versa. It also returns `invalid` for any other arguments. Now, if we want we can write it in a similar way in elixir:

```elixir
def flip(coin) do
    if coin == "head" do
        "tail"
    else
        if coin == "tail" do
            "head"
        else
            "invalid"
        end
    end
end

```

This sure works, but it doesn't look good. First of all, it has a nested `if...else` block. Whenever you notice your code has nested blocks, take a few moment and think it thoroughtly. There must be a better way of doing it. In the above case, we do have a better way of achieving the same functionality. We can refactor this code with 3 simple lines of code:

```elixir
def flip("head"), do: "tail"
def flip("tail"), do: "head"
def filp(_), do: "invalid"
```

Looks neat, isn't it? We are using pattern matching to match the argument directly from the function parameter. When the function `flip` will be called, Elixir will take each method signature and match it with the input argument. As soon as the match occurs, it will execute the function and stop. So for example, if we call the `flip` function with the parameter "tail". Elixir will first try to match the argument with the first function once, it fail it will move to the next one. Since, the second function takes "tail" as argument, it will get executed. We will get an output "head".

It will also work for complex Data Structure such as Dictionary or Maps. For example, see the below function that takes a dictionary `person` as argument and prints the full name.

```python
def get_fullname(person):
    firstname = person.get("firstname") 
    lastname = person.get("lastname")
    return f"{firstname} {lastname}"
```

The dict `person` has two key value pairs, `firstname` and `lastname`. We first retrieve the values from the dict and store them in two variables and later we print them by concatenating them using f-strings.

In Elixir, we can do the pattern matching in the function signature like below:

```elixir

def get_fullname(%{firstname: first_name, lastname: last_name}) do
    "#{first_name} #{last_name}"
end
```

just like `dict` Elixir has a data structure named Map which we are using for this example. The `firstname` and `lastname` are atoms, they are the key of the Map. The `first_name` and `last_name` variable will get the value of the Map using pattern matching. And, in the function body we just return the concatenated string.

The above examples are pretty basic but, they give us the glimpse of how pattern matching works. And, how it can help us to improve Elixir code.

## Conclusion

Before calling it a day, let's quickly recap what we've explored so far. We learned the key challenges of switching from different programming language to Elixir. We also explored some common mistakes and how to avoid them by taking advantage of Elixir features and best practices.