Mar 28, 2016
Table of contents:
A couple of weeks ago we briefly looked at Tuples in Elixir describing them as a “list” type that can contain items of different values.
This is slightly confusing as Elixir also has the List
type, which on the surface, seems to have many of the same characteristics as a Tuple.
In today’s tutorial we are going to be taking a good look into Tuples in Elixir to see what are the unique characteristics of the Tuple type, and when you should use them in your day-to-day work.
So as I mentioned in the introduction to this post, a Tuple is a “list” type that can hold any value:
{:hello, "world", 123}
As you can see from the example above, a Tuple is defined with curly braces and then a list of values. The Tuple itself can contain any value and contain a mixture of different value types.
So what are Tuples used for?
Tuples are used for storing related data together to form a single “aggregate” value.
For example, you might have a person Tuple such as:
person = {"Philip", 27}
A “person” requires a name and an age, and so the Tuple stores those multiple bits of data together as a single value.
If you were to remove the age property, then this would no longer be a “person”.
This is in contrast to Lists that store many individual values. When you remove a value from a List, it just makes the List smaller, it doesn’t change the meaning of the value as a whole.
Tuples are stored contiguously in memory and so getting a piece of data from a Tuple is really fast.
For example, if you had a person
Tuple as we did earlier, it’s very quick to get the person’s name:
person = {"Philip", 27}
elem(person, 0)
Here I’m selecting the 0 index value from the Tuple. This would return the string "Philip"
However, modifying Tuples is expensive because Elixir will copy the whole Tuple in memory.
You also can’t enumerate Tuples. This makes sense because a Tuple is not a “collection” of individual values, it’s an aggregate of values that make a whole.
You will see Tuples used a lot in Elixir, most prominently as the return value of a function.
For example, when you read a file in Elixir, you will see something like this:
File.read("hello.txt")
# {:ok, "Hello World\n"}
The first element of the Tuple is an atom :ok
which represents whether the call to the function was successful, and the second element contains the returned value, in this case the contents of the file.
If you try to read a file that does not exist you will get a different Tuple:
File.read("invalid.txt")
# {:error, :enoent}
In this example, the first value of the Tuple is an atom of :error
to tell you something went wrong, and the second element of the Tuple is the reason why the function call failed (:enoent
means No such directory entry because the file does not exist).
The return value of a function is a good use of Tuples for a couple of reasons.
Firstly, it enables Pattern Matching (which we will look into in a future tutorial because it’s really cool, but out of the scope of this tutorial).
Secondly, the return value is a known value and it’s not going to change.
So hopefully today’s tutorial has given you insight into the Tuple type and how it differs from the List type.
Tuples and Lists can seem similar on the surface, but they are actually quite different.
For the most part, Elixir will guide you to use the correct types.
For example, you can get an element from a Tuple with the elem()
function as we saw earlier, but there is not comparative function for Lists.
This is because getting a value from a List by it’s index is expensive, so if that’s what you want to do, you’re probably using the wrong type.