Like lists and arrays, tuples are also used to store multiple values. They are created with parentheses and their elements are separated by commas.
Similarly to lists, Elm style guide recommends using a space after
( and a space before
) when creating a tuple.
Unlike lists and arrays, tuples can contain values of different kinds.
They can even contain other collections such as lists or arrays or tuples themselves.
How about lists? Can they contain tuples? The answer is yes.
Remember lists can only contain values of the same kind. In the example above, all three tuples have two numbers in them. What happens if we have tuples of different sizes?
As Elm points out,
( 1, 2 ) and
( 3, 4, 5 ) are different types of values because their sizes are different. For tuples to be of the same type in Elm, they must contain the same number and type of values. So this won’t work either:
The first tuple is a pair of numbers, whereas the second is a pair of strings which makes them of different types. And a list can’t have values of different types.
In the previous sections, we learned that lists and arrays are immutable. Once they are created, we can never change them. However, despite them being immutable, we were still able to add and remove values from them.
How’s that possible? It’s possible because Elm gives us the appearance of adding (or removing) values from a list when in fact behind the scenes it creates a completely new list that contains the new value we appended to the original list. When we print
myList, we discover that its values are intact.
From practical standpoint all we care about is the ability to add and remove values from a collection. How Elm enables us to do that is of little concern to us.
Although, in chapter 4 we will find out that immutability does have far-reaching consequences. It’s at the core of some of Elm’s truly exciting features.
Like lists and arrays, tuples are also immutable. But, Elm goes a step further to keep a tuple’s immutability intact. It strips away our ability to even create a completely new tuple containing a value we want to add to the original tuple. Therefore, there are no functions or operators we can use to append or drop values from tuples.
When you think about it, it does make sense to put this restriction on tuples. Earlier we discovered that for tuples to be of the same type in Elm, they must contain the same number of values. If we were to add or remove a value from a tuple, we would essentially be changing its underlying type as well which is not the case with list or array. Furthermore, if we could add or remove values from tuples, they would start looking a lot like lists and arrays with only one difference: the ability to hold different types of values. In the next section we will find out that there already exists a data structure called record that can hold different types of values. So without this rigidity, tuples can be easily replaced with other data structures.
Because we can’t add or remove values from tuples, we should only use them when we know in advance how many elements we will need.
Despite their rigidity, tuples can be quite useful. Listed below are a few scenarios where using tuples makes our lives easier.
Representing Complex Data
Tuples can be used to represent a wide variety of data. For example, if we want to represent someone’s name, age, and a list of siblings we can easily do that with a tuple.
Returning Multiple Values From a Function
It’s a common pattern in Elm to use tuples for returning multiple values from a function. To see how this works, let’s write a function that determines whether or not a given email is valid. Add the following function definition right above
Now change the
main function to this:
Finally import the
Regex module right below the line that imports the
elm-reactor in terminal from the
beginning-elm directory if it’s not running already and go to
http://localhost:8000/elm-examples/Playground.elm on your browser. You should see
("Valid email","green"). The
validateEmail function uses a regular expression to determine whether a given email is valid or not. It returns a tuple containing two values:
If the email is valid, the string “Valid email” is returned, otherwise “Invalid email” is returned.
The color to use when displaying the validity status text.
As you can see, using a tuple made it very easy for us to return multiple values.
Being Explicit About the Structure of Data
Let’s say we want to compute the perimeter of a triangle using the formula shown below.
One way to represent the sides of a triangle is by using a list.
Now let’s write a function that calculates perimeter in the repl.
List.sum to add up all sides of a triangle. If we apply this function to the
sides list we created above, we will get the perimeter.
Now what happens when we apply the
trianglePerimeter function to a list that contains four sides?
It will happily compute the perimeter even though we gave it four sides. If we want to make sure that only three sides are being passed to this function, we will have to write some additional code to verify that an input list has only three elements. But if we use a tuple instead, Elm will do that for us. Let’s try it.
So far so good. Now let’s apply
trianglePerimeter to a tuple with four elements.
There you go. Elm points out that we need to pass in a tuple with only three elements. Picking a right data structure for the right problem often gets rid of subtle issues like this. Hopefully you understood why tuples could be very useful in a situation like this.
Elm provides two functions (
second) for retrieving values from a tuple with two elements also known as a pair. Storing data in pairs is quite common in Elm.
first returns the first element in a pair and
second returns the second element.
The repl automatically loads the
Tuple module. Therefore, we don’t need to explicitly import it.
If we can’t use
second on tuples that have more than two elements, how else can we read values from them? We have to use pattern matching for that. You already saw an example of this above. Here it is again.
trianglePerimeter uses pattern matching to deconstruct a tuple passed to it and assigns the first, second, and third values to
- Pattern Matching
- As mentioned in the Case Expression section, pattern matching is the act of checking one or more inputs against a pre-defined pattern and seeing if they match. The pattern we are checking for in the above example is a tuple that contains three, and only three, numbers. We will look into some more examples of pattern matching with tuples in chapter 4.
In most cases, it’s best to use a record instead of a tuple for storing more than two values. It’s much easier to access values in a record than a tuple. However, if we need to make sure that a data structure can contain only certain number of values similar to the
trianglePerimeter example above, tuple is our best option.
Tuple module also provides two more functions for transforming values in a pair.
mapFirst maps the first element and
mapSecond maps the second element.
There are no functions available to map values in a tuple that contains more than two values. Tuples really are rigid, aren’t they?