Note: 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 types.
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 different types. And a list can’t have values of different types.
Length of a Tuple
We can only have up to three elements in a tuple. If we add one more, we’ll get an error. It’s better to use a record when we need to store more than three 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 on the surface Elm makes it look like it’s modifying an existing list when in fact behind the scenes it creates a completely new 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.
Note: 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 add or remove values from a tuple. That’s why there are no functions or operators in the
Tuple module to perform those actions.
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 a list or an 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 from the
beginning-elm directory in terminal if it’s not running already and go to
http://localhost:8000/src/Playground.elm on your browser. You should see
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
"Valid email"string 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
triangleSides list we created above, we will get the perimeter.
Now what happens when we apply the
trianglePerimeter function to a list that contains only two sides?
It will happily compute the perimeter. If we want to make sure that the
trianglePerimeter always receives exactly three sides, 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 only two elements.
There you go. Elm points out that we need to pass in a tuple with exactly three elements. Picking a right data structure for the right problem often gets rid of subtle issues 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.
elm repl automatically loads the
Tuple module. That’s why we don’t need to import it.
There is no function for retrieving the third element in a tuple. But we can 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 earlier in this chapter, pattern matching allows us to check one or more inputs against a pre-defined pattern and see 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.
Tuple module also provides three functions for transforming values in a pair:
mapFirst— maps the first element.
mapSecond— maps the second element.
mapBoth— maps both elements.
There is no function for mapping the third element in a tuple. Tuples really are rigid, aren’t they?