Understanding Lists in Elixir

Last week we looked at using Tuples in Elixir. Tuples are a special type that hold many related values as a single whole. This is in contrast to the List type that holds individual items as a list of separate values.

Whilst on the surface these two types can seem very similar, they are actually very different.

In today’s tutorial we will be looking at the properties and characteristics of the List type in Elixir.

Working with Lists

Before we get into the details of Lists, first we will take a look at the basics of working with them. A List is defined using square brackets around a list of values of any type:

[:hello, "world", 123]  

You can get the length of a List by using the length function:

length([1,2,3])  

However, if you try to use the length function on a Tuple you will get an error:

length({:hello, :world})  
** (ArgumentError) argument error  
:erlang.length({:hello, :world})  

Instead you can use the tuple_size function:

tuple_size({:hello, :world})  

This is another case of Elixir pointing you in the right direction. Elixir will use size when the operation to count the number of elements is constant time, and length when it’s linear.

As we saw in Working with Strings in Elixir, sometimes you will create a list, but you will be returned a Character List:

[65, 66, 67]  
‘ABC’  

This is because these values are printable ASCII numbers and so Elixir will print them to the screen.

List Module functions

The Elixir Standard Library has a List Module that provides a number of useful functions for working with Lists. You don’t need to memorise all of these functions, but here are some of the highlights.

Getting the first or last item of a list:

list = [1,2,3,4]

List.first(list)  
1

List.last.(list)  
4  

Flatten a list of nested lists:

list = [1,[2,3],[[4,5,6]]]

List.flatten(list)  
[1, 2, 3, 4, 5, 6]  

Enum Module functions

Lists can also work with Enum Module. The Enum Module provides a set of algorithms that enumerate over enumerables according to the Enumerable protocol.

Again, you don’t need to memorise these functions, you just need to be aware that they exist.

Here are a couple of examples from the Enum Module.

The map function will iterate through a list and apply the given function to each value:

Enum.map([1, 2, 3], fn(x) -> x * 2 end)  
[2, 4, 6]  

The reduce function will iterate through a list and pass each value and an accumulator to a given function. The accumulator will then be returned:

Enum.reduce([1, 2, 3, 4], fn(x, acc) -> x * acc end)  
24  

The filter function will iterate through a list and return only the values as a list that return a truthy value for the given function:

Enum.filter([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)  
[2]  

Using the Head and the Tail

One of the most common things you will do with Lists in Elixir is using the head and the tail. The head of a List is the first element, and the tail is the remaining elements as a List. For example, if we have the following List:

list = ["a", "b", "c"]  

We would get the head of the List with the hd function:

hd(list)  
"a"  

And we could the the tail of the List with the tl function:

tl(list)  
["b", "c"]  

Processing a List as a head and tail is used extensively in recursive functions. We will be looking at this in more depth in a future tutorial.

Conclusion

If you’ve worked with a “list” type in another programming language you will probably feel right at home with the Elixir List type.

As you probably already know, lists are extremely useful and so you find yourself using them a lot. In particular, Elixir makes good use of recursive functions, something that we will explore in the coming weeks.