An Introduction to Elixir

by

Elixir is a dynamic, functional language designed for building scalable and maintainable applications. It leverages the Erlang Virtual Machine, which is known for running low-latency, distributed, and fault-tolerant systems. In this post, I’ll talk a bit about Elixir’s history and current uses, and demonstrate some of its basic types and functions.

Origins of Elixir

Ericsson designed the first version of the Erlang Virtual Machine (on which Elixir is built) in the 1980; they released an open-source version some years later. 

Erlang was built to support a large number of concurrent connections, and has been used to develop a range of applications, including banking, online gaming, and e-commerce apps. It’s main features are:

  • Distributed 
  • Scalable
  • Fault-tolerant
  • Highly available
  • On-the-fly code releases (hot swapping) 

In 2010, while José Valim was working on improving the Ruby on Rails framework performance on core systems, he realized that Ruby wasn’t well designed to solve concurrency problems. He started to research other technologies, and when he discovered Erlang and after reading the advantages of it  he began to develop Elixir on top of BEAM (Erlang virtual machine)).

Like Erlang, Elixir has proven its value in managing millions of simultaneous connections. In 2015, Phoenix reported managing 2 million websocket connections with its Elixir app, and in 2017, Discord scaled Elixir to handle 5 million concurrent users. 

Who uses Elixir?

Elixir is used by many prominent tech companies. A few notable examples:

  • Discord,A gaming voice streaming platform  which handles 5 million concurrent voice users with egress traffic of more than 220 Gbps (bits per second) and 120 Mpps (packets per second).
  • Bleacher Report, a sports culture news publisher; after switching to Elixir, Bleacher Report was able to reduce their server use by more than 90 percent while simultaneously dropping their release times for breaking news stories from about three minutes to about three seconds. 
  • Pinterest, A Social network which serves 250 million active users who have collectively pinned 175 billion items on 3 billion virtual pinboards. Pinterest has reported that switching to Elixir doubled efficiency over their Java implementation; they now deliver over 14,000 notifications per second with just 15 servers.

Elixir fundamentals

Basic types

Elixir supports the following basic types. One notable type is the atom, which is a constant for which the name and value are the same.

Complex types

A map is Elixir’s equivalent of a Python dictionary, and is constructed as shown below:

iex> map = %{a: 1, b: 2}
 
%{a: 1, b: 2}
 
iex> map[:a]
 
1

A struct is a map with a defined structure. For example, we could have a “User” struct that we define and manipulate as follows:

defmodule User do
 
  defstruct name: "John", age: 27
 
end
 
iex> %User{}
 
%User{age: 27, name: "John"}
 
iex> %User{name: "Jane"}
 
%User{age: 27, name: "Jane"}
 
iex> %User{oops: :field}
 
** (KeyError) key :oops not found in: %User{age: 27, name: "John"}

 

Functions 

Anonymous functions in Elixir are very similar to their JavaScript equivalents:

Elixir

add = fn a, b -> a + b end
add.(1, 2)

JavaScript

const add = (a, b) => a + b
add(1, 2)

 

Named functions use the same syntax as Ruby:

Elixer

def sum(a, b) do
    a + b
end

Ruby

def sum(a, b) do
    a + b
end

Pattern matching

In Elixir, the ‘equals’ operator can be used for pattern matching in addition to value assignment. This is particularly useful for matching function parameters; as shown below. Finally, we can also write the functions on the left using an unique anonymous function that uses pattern matching for the parameters. 

def sum(a, b) do
   a + b
end 
 
 
def sum(list) when is_list(list)do
    Enum.sum(list)
end 
sum(5, 5) # returns 10
sum([1,2,3,4,5]) #returns 15
sum = fn 
 a, b -> a + b
  list when is_list(list) -> Enum.sum(list)
end

 

Recursion

As a functional language, Elixir doesn’t include ‘for’ or ‘while’ loops — instead, it relies heavily on recursion. Fortunately, its recursion syntax is very clean. The following example shows how we find the sum a list of values; we split the head and tail of the list, add the head to an accumulator, and repeat the process until the tail is an empty list: 

def sum([], acc) do
   acc
end
 
def sum([head | tail], acc = 0) do
   sum(tail, acc + head)
end 
 
sum([1,2,3], 0) # returns 6

 

Control structures

Elixir supports the standard ‘if’ and ‘else’ controls, along with the ‘switch’-type control case and the Boolean switch control cond. Case follows a familiar switch syntax:

case user[:status] do
    active -> {:ok, “cool"}
    false  -> {:error, "the user is not active anymore"}
    _      -> {:error, "Something weird happened"}
end

Cond uses a similar syntax but it allows you to work with Boolean operators:

x = 11
cond do
   x < 2 -> "x is less than 2"
   x < 10 -> "x is less than 10"
   :else  -> "x is greater than or equal to 10"
end

Finally, we can use the with control to condense code that cycles through multiple case controls; for example, checking that a user exists before retrieving their contacts:

{_, result} =
    case look_for_user(params["id"]) do
      {:ok, user} ->
        case (user.contacts) do # {message, list}
          {:ok, contacts} ->
              {:ok, user}
          {:error, []}  ->
            {:error, "No contacts found for this user"}
        end
      {:error, _} = >
        {:notfound, "User not found"}
    end
 
 
result =
    with {:ok, user} <- look_for_user(params["id"]),
         {:ok, contacts} <- user.contacts do user else _ -> “The user wasn’t found or doesn’t have contacts”

 

Pipe operator

The pipe operator passes the output of one function as the  first parameter to another function, allowing us to create a string (or pipeline) of functions. For example, say we have a function that bakes a cake. In most languages, we end up expressing the steps backwards, which requires some mental gymnastics:

cake = frost(bake(make_batter(mix(prepare(ingredients)))))

In Elixir, however, we can use the pipe operator to express this function more logically by writing it from the top down:

cake = ingredients
|> prepare()
|> mix()
|> make_batter()
|> bake()
|> frost()

Further reading

If you’d like to learn more about Elixir, I recommend the following resources:

Leave a Reply

Your email address will not be published. Required fields are marked