collage of Pragpub magazine covers
Take a step back in history with the archives of PragPub magazine. The Pragmatic Programmers hope you’ll find that learning about the past can help you make better decisions for the future.

FROM THE ARCHIVES OF PRAGPUB MAGAZINE JUNE 2013

Programming Elixir: A Gentle Introduction

By Dave Thomas

8 min readMay 19, 2022

--

Elixir is a modern, functional programming language designed for high availability and concurrency. It has Ruby-like syntax married to the power and reliability of the Erlang VM. If you wanted to get into functional programming but were put off by the academic feel, now’s the time to jump in.

https://pragprog.com/newsletter/
https://pragprog.com/newsletter/

Fifteen years ago. That was the last time I was this excited by a programming language, and that language was Ruby.

It wasn’t for lack of trying. I explored them all as they came out, but none grabbed me — none made me feel like I’d enjoy spending years digging in.

Then I found Elixir. Twice. The first time was a year ago, and I thought it was very nice, but not compelling. But Corey Haines pushed me to look again. He was right. Elixir is special.

Enough with the Fanboy Hype

Sorry.

Elixir is a functional programming language that runs on the Erlang virtual machine. It has a Ruby-like syntax and features protocols (for extending modules without changing their source), macros, and very good metaprogramming support. It has the benefit of learning from other languages’ experiences, too, so it has many modern features. For example, protocols and macros are lexically scoped, so metaprogramming no longer risks messing up the global execution environment.

Elixir runs on the Erlang VM, Beam, and is fully compatible with existing Erlang code. This means you can take advantage of the high availability, high concurrency, and distributed nature of Beam. It also means you can use all the thousands of existing Erlang libraries, both built-in and third-party. In particular, Elixir can run within the OTP framework.

Enough — Show Me the Code

(If you want to follow along at home, download Elixir. The instructions are on the Elixir site.)

The following code calculates the sum of the elements in a list.

defmodule MyList do 
def
sum([]), do: 0
def sum([ head | tail ]), do: head + sum(tail)
end
IO.puts MyList.sum [1,2,3] #=> 6

Our sum function lives in a module called MyList. The function is defined in two clauses. So how does Elixir know which to run?

Pattern Matching

This is where pattern matching comes in. The parameter list of the first function clause is just the empty list. When you call sum, this clause will match only if you call it with an empty list. If it does match, the function returns 0.

The second clause has a more complicated parameter list: [head|tail]. This is a pattern that matches a list (the square brackets tell us that). The list must have a first element (the head) and a tail (the remainder of the list). If the list we pass in has only one element, the head will be set to that element, and the tail will be the empty list.

So let’s call MyList.sum with the argument [1,2,3]. Elixir looks for the first clause whose pattern matches the argument. The first clause doesn’t match — the argument isn’t the empty list — but the second clause does. head is set to 1 and tail to [2,3]. Elixir evaluates the body, head+sum(tail), which means that the sum function is called again, recursively. The sequence of calls looks something like this:

sum([1,2,3])
1 + sum([2,3])
1 + 2 + sum([3])
1 + 2 + 3 + sum([])
1 + 2 + 3 + 0

Pattern matching is at the core of Elixir. In fact, it’s the only way of binding a value to a variable. When we write what looks like an assignment:

a = [1,2,3]

Elixir is busy trying to work out how to have the left-hand side match the right. In this case, it does so by binding the list [1,2,3] to the variable a.

Once a has this value, you can write:

[1,2,3] = a

And Elixir will be happy — the value on the left matches the value on the right. But if you write:

99 = a

Elixir will complain that it can’t find a match. That’s because Elixir will only change the value bound to a variable if it is on the left-hand side of the match operator.

Pattern Matching Structured Data

Matching goes further — Elixir will look at the structure of the two sides when making a match:

[ a, b, c ] = [1, 2, 3] # a = 1, b = 2, c = 3
[ head | tail ] = [1, 2, 3] # head = 1, tail = [ 2, 3 ]

Once bound, a variable keeps the same value for the duration of the pattern match. So the following match will only succeed if the variable list is a three-element list where the first and last elements have the same value:

[ a, b, a ] = list

You can mix constants and variables on the left-hand side. To illustrate this, I’m going to introduce a new Elixir data type, the tuple. A tuple is a fixed-length collection of values. We write tuple constants between braces:

{ 1, 2, “cat” }
{ :ok, result }

(The :ok in the second line of code is an Elixir symbol. You can think of it as a constant string, or as a symbol in Ruby. You can also think of it as a constant whose value is its name, but that can lead to catatonic states.)

Many library functions return two-element tuples. The first element will be the status of the result — if it is :ok, the call succeeded; if it is :error it failed. The second value will be the actual result of the call, with more information on the error.

You can use pattern matching to determine if a call succeeded:

{ :ok, stream } = File.open(“somefile.txt”)

If the File.open succeeds, then stream will be set to the result. If it doesn’t, the pattern won’t match, and Elixir will raise a runtime error. (Although in this case, you’re better off opening the file with File.open!(), which will raise a more meaningful error on failure.

Pattern Matching and Functions

Our MyList.sum example showed that pattern matching also applies to calling functions. The function parameters act as the left-hand side of the match, and the arguments you pass act as the right-hand side.

Here’s another (hoary old) example: it calculates the value of the nth Fibonacci number.

Let's start with the specification of Fibonacci numbers:

fib(0) -> 1
fib(1) -> 1
fib(n) -> fib(n-2) + fib(n-1)

Using pattern matching, we can turn this specification into executable code with minimal effort:

defmodule Demo do 
def
fib(0), do: 1
def fib(1), do: 1
def fib(n), do: fib(n-2) + fib(n-1)
end

Elixir comes with an interactive shell called iex. This lets us play with our fib method:

elixir % iex fib.ex 
Erlang R15B03 …
Interactive Elixir (…)
iex(1)> Demo.fib(0)
1
iex(2)> Demo.fib(1)
1
iex(3)> Demo.fib(10)
89

But there’s a problem with this code. What happens if we call fib(-1)? The third clause will match and will call fib(-3), which will call fib(-5), and so on until we run out of stack. We should limit the arguments to fib to nonnegative numbers.

In Elixir, we do this with a guard clause. A guard clause enhances pattern matching by letting us write one or more conditions that the arguments must meet if the match is to succeed. In this case, we could write:

defmodule Demo do 
def
fib(0), do: 1
def fib(1), do: 1
def fib(n) when n >= 0, do: fib(n-2) + fib(n-1)
end

The when clause is the guard. It says that the third function clause can only match if its argument is greater than or equal to zero. (Technically, it could have been greater than or equal to two, as the first two function clauses handle the 0 and 1 cases, but for some reason, I find using 0 here is clearer.)

The wonderful thing about this is how easy it is to convert a specification into runnable code. And, once that code is written, it’s easy to read it and see what it does.

Something else to note: there are no conditional statements in the implementation of our function (apart from the guard clause). This also makes the code easier to read (and maintain). In fact, many fairly large Elixir modules are written with few or no conditional statements.

Transformation Is Job #1

You might be thinking that this is all very well, but you don’t write mathematical functions as part of your daily job.

But functional programming isn’t about mathematical functions.

Functions are things that transform data. The trig function sin transforms the value 90° to the value 1.0. And that’s the hint.

Programming is not about data. It’s about transforming data. Every program we write takes some input and transforms it into some output. The input could be a web request, some command line parameters, or the weather in Boise. Whatever it is, our code takes it and transforms it multiple times on the way to producing the desired result.

And that’s why I think functional programming is a natural successor to object-oriented programming. In OO programming, we’re constantly concerned about the state of our data. In functional programming, our focus is on the transformation of data. And transformation is where the value is added.

Next Month

Next month, we’ll look at functions, both named and anonymous. And we’ll explore the pipeline operator, which lets us write things like:

request |> authenticate |> lookup_data |> format_response

If you can’t wait until then, there are a couple of Elixir screencasts available on my Elixir book’s home page. And if you want the real scoop, you can always buy the book. :)

About Dave Thomas

Author Dave Thomas giving a talk
Author Dave Thomas

Dave Thomas is a programmer who likes to evangelize cool stuff. He cowrote The Pragmatic Programmer and was one of the creators of the Agile Manifesto. His book Programming Ruby introduced the Ruby language to the world, and Agile Web Development with Rails helped kickstart the Rails revolution.

Cover from PragPub Magazine, June 2013
Cover from PragPub Magazine, June 2013

--

--

PragPub
The Pragmatic Programmers

The Pragmatic Programmers bring you archives from PragPub, a magazine on web and mobile development (by editor Michael Swaine, of Dr. Dobb’s Journal fame).