A List uses the
] characters to contain the elements of the list separated by commas. It looks like this:
my_list = [1, 2, 3, 4, 5]
This looks like an Array in other languages. However, a List doesn’t act like an Array.
A list in Elixir (and other functional programming languages) is actually a recursive data structure. Internally implemented as a linked list.
When you realize an Elixir list is an immutable linked list, it’s behavior makes sense and how you use it becomes clear.
my_list is “bound” to the list. The variable isn’t “assigned” the value of the list, it is said to be “bound”. Variables in Elixir are all “bound” to their values. You can think of “bound” as meaning “is pointing to”. So
my_list is pointing to a specific element in a linked list. Each element points to the next element in the list and separately points to the value for the element.
In many languages, it is common to add more items to the end of an Array as it is built up. With an immutable List, we can’t add items to the end as that would be affecting other variables that are bound to some element earlier in the list.
If I really do want to add to the end of the list, I can, but a whole new list is created with that value at the end. When a list is very large, this becomes an expensive operation. Two lists can be concatenated together using the
my_list ++  #=> [1, 2, 3, 4, 5, 6]
Adding to the front of the List, or the “head” is very cheap. It doesn’t alter other variables that are already bound to some element in the List.
This makes it very efficient both in speed and memory consumption. To efficiently add to the front of a list, you use the
| pipe character to join the new element and the existing list together.
[0 | my_list] #=> [0, 1, 2, 3, 4, 5]
It can be cheaper and faster to build up a large list in reverse, adding to the “head” rather than re-building the whole list with each new addition. Then, once built, perform a single “reverse” of the whole list using Enum.reverse/1.
Enum.reverse([6, 5, 4, 3, 2, 1]) #=> [1, 2, 3, 4, 5, 6]
Thinking about a list as a “linked list” data structure where it’s made up of pointers to the actual data helps to understand how immutable data can work and be so memory efficient. The same kind of thing is happening with the other data structures, but I think it’s easiest to start imagining it with a list.
Working with immutable data may be a different experience for you. It probably feels uncomfortable. However, it is what enables concurrency and immutable data removes a whole category of state related bugs. If it is uncomfortable now, just know that understanding and embracing it will help make you a better programmer. And you’ll end up loving it!
Lists don’t just contain integers. Lists can contain any data type and can contain different types from one element to the next.
A list’s contents are ordered. They remain in the order they are declared.
[1, "Hello", 42.0, true, nil]
Experiment with Lists
Take some time to experiment with lists in IEx. Try concatenating lists together with
++, you can also remove elements from a list using
--. Try adding to the front of a list using the
| pipe separator. Experimentation is playing! Have fun playing!