3.18

Tuple

Like lists and arrays, tuples are also used to store multiple values. They are created with parentheses and their elements are separated by commas.

> ( 1, 2 )
(1,2)

> ( "Mia", "Vincent" )
("Mia","Vincent")

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.

> ( 1, 2.5, 'a', "Butch" )
(1,2.5,'a',"Butch")

They can even contain other collections such as lists or arrays or tuples themselves.

> ( [ 1, 2 ], [ "Jules", "Wolf" ] )
([1,2],["Jules","Wolf"])

> array = Array.fromList [ 5, 6 ]
Array.fromList [5, 6]

> (array, [ 1, 2 ], ( "Jules", "Wolf" ) )
(Array.fromList [5,6],[1,2],("Jules","Wolf"))

How about lists? Can they contain tuples? The answer is yes.

> [ ( 1, 2 ), ( 3, 4 ), ( 5, 6 ) ]
[(1,2),(3,4),(5,6)]

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?

> [ ( 1, 2 ), ( 3, 4, 5 ) ]

-------------------------- TYPE MISMATCH --------------------------
The 1st and 2nd entries in this list are different types of values.

7|   [ ( 1, 2 ), ( 3, 4, 5 ) ]
                 ^^^^^^^^^^^
The 1st entry has this type:

    ( number, number1 )

But the 2nd is:

    ( number, number1, number2 )

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:

> [ ( 1, 2 ), ( "Sansa", "Ygritte" ) ]

-------------------------- TYPE MISMATCH --------------------------
The 1st and 2nd entries in this list are different types of values.

7|   [ ( 1, 2 ), ( "Sansa", "Ygritte" ) ]
                 ^^^^^^^^^^^^^^^^^^^^^^
The 1st entry has this type:

    ( number, number1 )

But the 2nd is:

    ( String, String )

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.

Modifying Tuples

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.

> myList = [ 1, 2, 3, 4 ]
[1,2,3,4]

> myList ++ [ 5 ]
[1,2,3,4,5]

> List.drop 1 myList
[2,3,4]

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.

> myList
[1,2,3,4]

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.

Using Tuples

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.

> ( "Jon Snow", 14, [ "Sansa", "Arya", "Bran", "Rob", "Rickon" ] )
("Jon Snow",14,["Sansa","Arya","Bran","Rob","Rickon"])

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 main in Playground.elm.

validateEmail email =
    let
        emailPattern =
            Regex.regex "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b"

        isValid =
            Regex.contains emailPattern email
    in
        if isValid then
            ( "Valid email", "green" )
        else
            ( "Invalid email", "red" )


main
    ...

Now change the main function to this:

main =
    validateEmail "thedude@rubix.com"
        |> toString
        |> Html.text

Finally import the Regex module right below the line that imports the Html module.

import Html
import Regex

Run 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:

  1. If the email is valid, the string “Valid email” is returned, otherwise “Invalid email” is returned.

  2. 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.

> sides = [ 5, 4, 6 ]
[5,4,6]

Now let’s write a function that calculates perimeter in the repl.

> trianglePerimeter sides = List.sum sides
<function>

We used 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.

> trianglePerimeter sides
15

Now what happens when we apply the trianglePerimeter function to a list that contains four sides?

> trianglePerimeter [ 5, 4, 6, 8 ]
23

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.

> trianglePerimeter ( a, b, c ) = a + b + c
<function>

> trianglePerimeter ( 5, 4, 6 )
15

So far so good. Now let’s apply trianglePerimeter to a tuple with four elements.

> trianglePerimeter ( 5, 4, 6, 8 )

------------------------ TYPE MISMATCH ----------------------------
The argument to function `trianglePerimeter` is causing a mismatch.

7|   trianglePerimeter ( 5, 4, 6, 8 )
                       ^^^^^^^^^^^^^^
Function `trianglePerimeter` is expecting the argument to be:

    ( number, number, number )

But it is:

    ( number, number1, number2, number3 )

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.

Retrieving Values

Elm provides two functions (first and 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.

> Tuple.first ( 5, 10 )
5

> Tuple.second ( 5, 10 )
10

> Tuple.first ( "Pam", "Jim" )
"Pam"

> Tuple.second ( "Pam", "Jim" )
"Jim"

The repl automatically loads the Tuple module. Therefore, we don’t need to explicitly import it.

If we can’t use first and 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 ( a, b, c ) = a + b + c
<function>

> trianglePerimeter ( 5, 4, 6 )
15

trianglePerimeter uses pattern matching to deconstruct a tuple passed to it and assigns the first, second, and third values to a, b, and c respectively.

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.

Mapping Pairs

The Tuple module also provides two more functions for transforming values in a pair. mapFirst maps the first element and mapSecond maps the second element.

> Tuple.mapFirst (\_ -> "Jim") ( "Roy", "Pam" )
("Jim","Pam")

> Tuple.mapFirst String.reverse ( "live", "life" )
("evil","life")

> Tuple.mapSecond (\x -> x + 1) ( "fringe", 100 )
("fringe",101)

> Tuple.mapSecond (String.contains "-") ( "Gamora", "Star-Lord" )
("Gamora",True)

There are no functions available to map values in a tuple that contains more than two values. Tuples really are rigid, aren’t they?

Back to top

New chapters are coming soon!

Sign up for the Elm Programming newsletter to get notified!

* indicates required
Close