Learn-you-a-Haskell-for-great-good

Tuples

In some ways, tuples – are like lists: they – are a way to store several values (into a single value). However, there – are a few (fundamental) differences… A list of numbers – is a list of numbers. That’s its type – and it doesn’t matter, if it has only one number (in it), or – an infinite amount of numbers… Tuples (however) – are used, when you know exactly: how many values you want to combine; and its type – depends on how many components it has, and the types of the components. They – are denoted with parentheses, and their components – are separated by commas.

Another key difference – is that: they – don’t have to be homogenous… Unlike a list, a tuple – can contain a combination of several types!

Think – about how we’d represent a two-dimensional vector in “Haskell”… One way – would be to use a list. That – would (kind of) work. So what – if we wanted to put a couple of vectors – in a list: to represent points of a shape, on a two-dimensional plane? … We could do something like [[1, 2], [8, 11], [4, 5]]. The problem (with that method) – is that: we – could (also) do stuff like [[1, 2], [8, 11, 5], [4, 5]], which “Haskell” has no problem with – since it’s still a list, of lists with numbers, but it kind of doesn’t make sense… But a tuple of size two (also – called a “pair”) – is its own type, which means that: a list – can’t have a couple of pairs (in it) and then – a triple (a tuple – of size three); so let’s use that instead. Instead of surrounding the vectors with square brackets, – we use parentheses: [(1, 2), (8, 11), (4, 5)]. What if we tried to make a shape like [(1, 2), (8, 11, 5), (4, 5)]? … Well; we’d get this error:

Couldn't match expected type `(t, t1)'  
against inferred type `(t2, t3, t4)'  
In the expression: (8, 11, 5)  
In the expression: [(1, 2), (8, 11, 5), (4, 5)]  
In the definition of `it': it = [(1, 2), (8, 11, 5), (4, 5)]  

It’s telling us that: «We – tried to use a pair, and a triple, – in the same list, – which is not supposed to happen…». You – also couldn’t make a list like [(1, 2), ("One", 2)] – because the first element (of the list) – is a pair of numbers, and the second element – is a pair, consisting of a string and a number… Tuples – can also be used to represent a wide variety of data… For instance: if we wanted to represent someone’s name and age (in “Haskell”), – we could use a triple: ("Christopher", "Walken", 55). As seen in this example, – tuples – can also contain lists.

Use tuples – when you know (in advance) how many components some piece of data should have… Tuples – are much more rigid, – because each different size (of tuple) – is its own type; so, you – can’t write a “general” function, to append an element to a tuple. You’d – have to write:

While there are singleton lists, – there’s no such thing, as a singleton tuple. It doesn’t (really) make much sense, when you think about it… A singleton tuple – would (just) be the value it contains; and, as such – would have no benefit (to us).

Like lists, – tuples – can be compared with each other, – if their components can be compared. Only – you can’t compare two tuples – of different sizes, – whereas you can compare two lists of different sizes… Two useful functions which operate on pairs:

  1. fst – takes a pair – and returns its first component.
ghci> fst (8,11)  

8  
ghci> fst ("Wow", False)  

"Wow"  
  1. snd – takes a pair – and returns its second component. Surprise?
ghci> snd (8,11)  

11  
ghci> snd ("Wow", False)  

False  

Note: these functions – operate only on pairs. They – won’t work on triples, 4-tuples, 5-tuples, etc… We’ll go over extracting data from tuples (in different ways) a bit later.

A cool function (which – produces a list of pairs): zip. It takes two lists; and then – “zips” them (together): into one list, by joining the matching elements (into pairs)… It’s a (really) simple function – but it has loads of uses… It’s – especially useful – for when you want to combine two lists (in a way); or – traverse two lists (simultaneously). Here’s – a demonstration:

ghci> zip [1, 2, 3, 4, 5] [5, 5, 5, 5, 5]  

[(1, 5), (2, 5), (3, 5), (4, 5), (5, 5)]  
ghci> zip [1 .. 5] ["one", "two", "three", "four", "five"]  

[(1, "one"), (2, "two"), (3, "three"), (4, "four"), (5, "five")]  

It pairs up the elements (and produces a new list). The first element – goes with the first; the second – with the second; etc… Notice that: because pairs can have different types (in them), – zip – can take two lists (which – contain different types), and – “zip” them up… What happens – if the lengths (of the lists) “don’t” match?

ghci> zip [5, 3, 2, 6, 2, 7, 2, 5, 4, 6, 6] ["im", "a", "turtle"]  

[(5, "im"), (3, "a"), (2, "turtle")]  

The longer list – simply – gets cut off: to match the length of the shorter one. Because “Haskell” is “lazy”, – we – can zip finite lists – with infinite lists:

ghci> zip [1 ..] ["apple", "orange", "cherry", "mango"]  

[(1, "apple"), (2, "orange"), (3, "cherry"), (4, "mango")]  

Here’s – a problem, which combines tuples and list comprehensions: which right triangle (having: integers – for all sides, and all sides – equal to, or smaller, than 10) has a perimeter of 24? … First: let’s try generating all triangles (with – sides – equal to, or smaller, than 10):

ghci> let triangles = [ (a, b, c) | c <- [1 .. 10], b <- [1 .. 10], a <- [1 .. 10] ]   

We’re just “drawing” from three lists; – and, – our output function – is combining (them) into a triple… If you would evaluate that (by typing out: triangles – in GHCI), – you’ll get a list: of all possible triangles – with sides under (or – equal to) 10… Next, – we’ll add a condition, – so they all have to be right triangles… We’ll (also) modify this function; – by taking into consideration that: side bisn’t larger, than the hypothenuse, – and that: side aisn’t larger, than b:

ghci> let rightTriangles = [ (a, b, c) | c <- [1 .. 10], b <- [1 .. c], a <- [1 .. b], a^2 + b^2 == c^2]   

We’re – almost done. Now, we (just) “modify” the function – by saying that: we – want the ones, where the perimeter – is 24:

ghci> let rightTriangles' = [ (a, b, c) | c <- [1 .. 10], b <- [1 .. c], a <- [1 .. b], a^2 + b^2 == c^2, a + b + c == 24]  

ghci> rightTriangles'  

[(6, 8, 10)]  

And; – there’s – our answer! … This – is a common pattern (in functional programming). You – take a starting set (of solutions), – and (then) – you apply transformations (to those solutions); – and filter them, – until – you get the right ones.