133

Discovering the Elixir programming language

Elixir is an exciting programming language to learn. Especially coming from an Object Oriented Programming (OOP) background. There is so much to discover, because for many, it has flown under the radar. Just look at this chart!

Look at it 👇

(ignore the cats)

Two charts, where one is clearly higher than the other.

While OOP is a popular thing, functional programming is growing in usage and being used in more contexts. While some languages allows for many approaches, Elixir locks you into its functional approach. We won't see any classes or loops, and we will soon find out why!

Today we will explore some basic structures of the language by building a Fibonacci sequence function and a few tests for it. But before we do. A quick run-down of the Fibonacci Sequence. It follows a principle of every number being the sum of the one just before and the one before that. To start off, it is stipulated that the first and second number are 1. This means that the third number is (1 + 1) = 2. The fourth will be (2 + 1) = 3. So the first 7 numbers will be: 1 1 2 3 5 8 13.

Let's purr some Elixir

A cat in a lab coat is pouring a liquid out of a beaker.

We create a new file called fibonacci.ex and define a module called Fibonacci:

defmodule Fibonacci do
  def fib(n) when is_integer(n) and n >= 0 do
    fib_helper(n)
  end

  defp fib_helper(0), do: 0
  defp fib_helper(1), do: 1
  defp fib_helper(n), do: fib_helper(n - 1) + fib_helper(n - 2)
end

The Module contains a function called fib with a guard defined for it. This means that the function will only be run on arguments that are positive integers. Inside the function is another function-call. We make use of a function called fib_helper. This function has 3 definitions;

  1. One is matching an argument that is 0 and returns 0.
  2. One is matching an argument that is 1 and returns 1.
  3. One is the Fibonacci sequence logic all wrapped up in recursion.

When we pass it 3, we will match with the third fib_helper, it will first call, fib_helper(2) + fib_helper(1). The first function call will again match the 3rd definition, so recursion, and the second function call will match the 2nd definition. It will continue to do recursive calls until it does not call itself again. Such as is the case for the calls that match either the first or the second definition of fib_helper.

A fun detail about fib_helper is that it is private within the module. We can make functions private by using defp instead of def.

An even more fun (as in fundamental) detail is how Elixir just returns values from our functions without us having to tell it to do so.

If you don't like recursions, you gonna have a bad time

A cat in a lab coat is pouring a liquid out of a beaker.

To run our function we can run iex from the terminal where the files are located. This will open up the interactive Elixir interface, or a REPL. From this we will compile our file: c("fibonacci.ex"). This will compile the module for us so we can call the fib function on it.

 
iex(1)> c("fibonacci.ex")
[Fibonacci]
iex(2)> Fibonacci.fib(5)
5
iex(3)> Fibonacci.fib(7)
13

How do we know that our function really works correctly? Usually we would write tests. While many programming languages has excellent unit testing frameworks available to them as third party libraries, this is not necessary in Elixir. Because every line of code in Elixir is perfect! Well, mine are. As long as I keep the number of lines to 0.

Well, if we do really want tests (and we do), we have a built-in testing-tool called ExUnit. This means we can start writing tests just by starting a new file. Let's create fibonacci_test.exs. It will start the ExUnit testing application and make some assertions with the help of a module called ExUnit.Case. This will make it possible for us to assert that we get the correct Fibonacci value for n-th position of the sequence.

Code.require_file("./fibonacci.ex")

ExUnit.start()

defmodule FibonacciTest do
  use ExUnit.Case

  test "fibonacci of 0 is 0" do
    assert Fibonacci.fib(0) == 0
  end

  test "fibonacci of 1 is 1" do
    assert Fibonacci.fib(1) == 1
  end

  test "fibonacci of n is correct" do
    assert Fibonacci.fib(10) == 55
    assert Fibonacci.fib(20) == 6765
    assert Fibonacci.fib(30) == 832040
  end
  
  test "fibonacci of float is not allowed" do
    catch_error Fibonacci.fib(1.2)
  end

  test "fibonacci of negative is not allowed" do
    catch_error Fibonacci.fib(-2)
  end
end

For the test file to be aware of the module we want to test, we first have to import the file. Next, we are starting the testing application in the file for it to be able to run the module's test-cases. We define a few test-cases, just to see that we have defined the constraints for the sequence correctly, and that a few n-th positions returns correct values.

To run the test-file, we will run this in the terminal (not in iex): elixir fibonacci_test.exs

Giving us the following output:

.....
Finished in 0.04 seconds (0.04s on load, 0.00s async, 0.00s sync)
5 tests, 0 failures

To dip a bit further into the Elixir pond, let's create a function to return a list of all the values in a sequence up to a n-th number, but limit to maximum of 30 numbers. The test for it would look like this:

  test "fibonacci sequence up to 8" do
    assert Fibonacci.seq(8) == [0, 1, 1, 2, 3, 5, 8, 13, 21]
  end

As a matter of the modern take on Fibonacci sequence, the 0-th number is included.

We create the seq function in fibonacci.ex. It will make use of our already defined fib function. We will see some esoteric language, but we will be able to discern the meaning.

  def seq(n) when is_integer(n) and n >= 0 and n <= 30 do
    Enum.map(0..n, &fib/1)
  end

We define the function seq to take an integer between (and including) 0 and 30. We then define a list of numbers from 0 up to the supplied number (0..n) and have a map-function (from Enum) to have a function applied for each number on the sequence (&fib/1). Let's unpack that series of characters a bit!

Is that all it takes?

Two cats looking up, a bit questioning.

While this looks neat, let's run the tests:

elixir fibonacci_test.exs                                                                                                                                                                         
......
Finished in 0.04 seconds (0.03s on load, 0.00s async, 0.01s sync)
6 tests, 0 failures

...and all was good!

Let us now create a print function to print out each value in a sequence. We will see one convoluted way of doing it and refine it later. What we will see is a pipe operator for passing the result of one function in to the other, pattern matching to unpack a list, the unless-condition to check if a pattern match is not true and a perfectly good for-loop nowhere to be seen and in its place, recursion!

In the module Fibonacci (fibonacci.ex-file) we add these functions:

  def print_seq(n) do
    seq(n) |> print_seq_helper
  end

  defp print_seq_helper([head|tail]) do
    IO.puts(head)

    unless [] == tail do
      print_seq_helper(tail)
    end

    :ok
  end

The first function is the public facing function print_seq. This will take an argument and pass it to the seq function. With n = 3 the seq function would return a list with the value [0, 1, 2, 3]. We pipe |> this to the next function; print_seq_helper. Something incredible happens when we do that. We will pattern match the incoming argument and have the first element of the list signed onto head and keep the rest of the elements as a list in tail.

When we call IO.puts(head) it will print out the value that is currently in head. Next we check that the tail is not an empty list and again call print_seq_helper and let it take the next element in the list to the head. And so on it goes until the list is empty and a "simple" :ok is returned.

The :ok is called an atom. It is a constant that has the same value as it is named. The name can be most anything we want. We could return :cat_in_a_hat if we wanted to!

When we return to iex we can compile the latest changes and see what our output will be:

iex(1)> c("fibonacci.ex")
warning: redefining module Fibonacci (current version defined in memory)
  fibonacci.ex:1

[Fibonacci]
iex(2)> Fibonacci.print_seq(8)
0
1
1
2
3
5
8
13
21
:cat_in_a_hat
A cat in a hat on top of a table with a few beakers surrounding it.

Great! It's just the way we wanted it. And we got a cat in a hat as well. But why no for loop to go through the list? The thing about Elixir is immutability. It just has a complete loathing of variables that are variable... anything that changes what they once have said they are. It rather have you trash your variable and replace it with a new than you were to change the value of the variable. A for-loop or a for-each-loop would have a variable hold the current iteration or element without replacing the variable. So a loop like this would constantly be changing the value of the loop variable.

With all of that now out of the way, how could we rewrite the print-function to be a bit more neat? How about this?!

  def print_seq(n) do
    Enum.each(seq(n), &IO.puts/1)
  end

No need for a helper-function and everything done in a single row. It looks very nice, and it has a pretty good replacement for a for-each loop. The sad news though: we lost the :cat_in_a_hat, but it's :ok!

iex(3)> r(Fibonacci)
warning: redefining module Fibonacci (current version defined in memory)
  fibonacci.ex:1

{:reloaded, [Fibonacci]}
iex(4)> Fibonacci.print_seq(8)
0
1
1
2
3
5
8
13
21
:ok

In this article, we have made some pretty astounding discoveries!

A happy cat in a top hat on top of a table holding up a vile with liquid in it. The cat, despite doing this, looks professional.