Кортежи позволяют хранить несколько элементов разных типов как единое целое.
В некотором смысле кортежи похожи на списки, однако есть и фундаментальные отличия. Во-первых, кортежи гетерогенны, т. е. в одном кортеже можно хранить элементы нескольких различных типов. Во-вторых, кортежи имеют фиксированный размер: необходимо заранее знать, сколько именно элементов потребуется сохранить.
Кортежи обозначаются круглыми скобками, а их компоненты отделяются запятыми:
ghci> (1, 3)
(1,3)
ghci> (3, 'a', "привет")
(3,'a',"привет")
ghci> (50, 50.4, "привет", 'b')
(50,50.4,"привет",'b')
Использование кортежей
Подумайте о том, как бы мы представили двумерный вектор в языке Haskell. Один вариант – использовать список. Это могло бы сработать – ну а если нам нужно поместить несколько векторов в список для представления точек фигуры на двумерной плоскости?.. Мы могли бы, например, написать: [[1,2],[8,11],[4,5]]
.
Проблема подобного подхода в том, что язык Haskell не запретит задать таким образом нечто вроде [[1,2],[8,11,5],[4,5]]
– ведь это по-прежнему будет список списков с числами. Но по сути данная запись не имеет смысла. В то же время кортеж с двумя элементами (также называемый «парой») имеет свой собственный тип; это значит, что список не может содержать несколько пар, а потом «тройку» (кортеж размера 3). Давайте воспользуемся этим вариантом. Вместо того чтобы заключать векторы в квадратные скобки, применим круглые: [(1,2),(8,11),(4,5)]
. А что произошло бы, если б мы попытались создать такую комбинацию: [(1,2),(8,11,5),(4,5)]
? Получили бы ошибку:
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)]
Мы попытались использовать пару и тройку в одном списке, и нас предупреждают: такого не должно быть. Нельзя создать и список вроде [(1,2),("Один",2)]
, потому что первый элемент списка – это пара чисел, а второй – пара, состоящая из строки и числа.
Кортежи также можно использовать для представления широкого диапазона данных. Например, если бы мы хотели представить чьё-либо полное имя и возраст в языке Haskell, то могли бы воспользоваться тройкой: ("Кристофер", "Уокен", 69)
. Как видно из этого примера, кортежи также могут содержать списки.
Используйте кортежи, когда вы знаете заранее, из скольких элементов будет состоять некоторая часть данных. Кортежи гораздо менее гибки, поскольку количество и тип элементов образуют тип кортежа, так что вы не можете написать общую функцию, чтобы добавить элемент в кортеж – понадобится написать функцию, чтобы добавить его к паре, функцию, чтобы добавить его к тройке, функцию, чтобы добавить его к четвёрке, и т. д.
Как и списки, кортежи можно сравнить друг с другом, если можно сравнивать их компоненты. Однако вам не удастся сравнить кортежи разных размеров (хотя списки разных размеров сравниваются, если можно сравнивать их элементы).
Несмотря на то что есть списки с одним элементом, не бывает кортежей с одним компонентом. Если вдуматься, это неудивительно. Кортеж с единственным элементом был бы просто значением, которое он содержит, и, таким образом, не давал бы нам никаких дополнительных возможностей[6].
Использование пар
Вот две полезные функции для работы с парами:
• fst
– принимает пару и возвращает её первый компонент.
ghci> fst (8,11)
8
ghci> fst ("Вау", False)
"Вау"
• snd
– принимает пару и возвращает её второй компонент. Неожиданно!
ghci> snd (8,11)
11
ghci> snd ("Вау", False)
False
ПРИМЕЧАНИЕ. Эти функции работают только с парами. Они не будут работать с тройками, четвёрками, пятёрками и т. д. Выделение данных из кортежей мы рассмотрим чуть позже.
Замечательная функция, производящая список пар, – zip
. Она принимает два списка и сводит их в один, группируя соответствующие элементы в пары. Это очень простая, но крайне полезная функция. Особенно она полезна, когда вы хотите объединить два списка или обойти два списка одновременно. Продемонстрируем работу zip
:
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] ["один", "два", "три", "четыре", "пять"]
[(1,"один"),(2,"два"),(3,"три"),(4,"четыре"),(5,"пять")]
Функция «спаривает» элементы и производит новый список. Первый элемент идёт с первым, второй – со вторым и т. д. Обратите на это внимание: поскольку пары могут содержать разные типы, функция zip
может принять два списка, содержащих разные типы, и объединить их. А что произойдёт, если длина списков не совпадает?