Сопоставление с образцом может быть использовано и для кортежей. Что если мы хотим создать функцию, которая принимает два двумерных вектора (представленных в форме пары) и складывает их? Чтобы сложить два вектора, нужно сложить их соответствующие координаты. Вот как мы написали бы такую функцию, если б не знали о сопоставлении с образцом:
addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double)
addVectors a b = (fst a + fst b, snd a + snd b)
Это, конечно, сработает, но есть способ лучше. Давайте исправим функцию, чтобы она использовала сопоставление с образцом:
addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double)
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)
Так гораздо лучше. Теперь ясно, что параметры функции являются кортежами; к тому же компонентам кортежа сразу даны имена – это повышает читабельность. Заметьте, что мы сразу написали образец, соответствующий любым значениям. Тип функции addVectors в обоих случаях совпадает, так что мы гарантированно получим на входе две пары:
ghci> :t addVectors
addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double)
Функции fst и snd извлекают компоненты пары. Но как быть с тройками? Увы, стандартных функций для этой цели не существует, однако мы можем создать свои:
first :: (a, b, c) –> a
first (x, _, _) = x
second :: (a, b, c) –> b
second (_, y, _) = y
third :: (a, b, c) –> c
third (_, _, z) = z
Символ _ имеет то же значение, что и в генераторах списков. Он означает, что нам не интересно значение на этом месте, так что мы просто пишем _.
Сопоставление со списками и генераторы списков
В генераторах списков тоже можно использовать сопоставление с образцом, например:
ghci> let xs = [(1,3), (4,3), (2,4), (5,3), (5,6), (3,1)]
ghci> [a+b | (a,b) <– xs]
[4,7,6,8,11,4]
Если сопоставление с образцом закончится неудачей для одного элемента списка, просто произойдёт переход к следующему элементу.
Списки сами по себе (то есть заданные прямо в тексте образца списковые литералы) могут быть использованы при сопоставлении с образцом. Вы можете проводить сравнение с пустым списком или с любым образцом, который включает оператор : и пустой список. Так как выражение [1,2,3] – это просто упрощённая запись выражения 1:2:3:[], можно использовать [1,2,3] как образец.
Образец вида (x:xs) связывает «голову» списка с x, а оставшуюся часть – с xs, даже если в списке всего один элемент; в этом случае xs – пустой список.
ПРИМЕЧАНИЕ. Образец (x:xs) используется очень часто, особенно с рекурсивными функциями. Образцы, в определении которых присутствует :, могут быть использованы только для списков длиной не менее единицы.
Если вы, скажем, хотите связать первые три элемента с переменными, а оставшиеся элементы списка – с другой переменной, то можете использовать что-то наподобие (x:y:z:zs). Образец сработает только для списков, содержащих не менее трёх элементов.
Теперь, когда мы знаем, как использовать сопоставление с образцом для списков, давайте создадим собственную реализацию функции head:
head' :: [a] –> a
head' [] = error "Нельзя вызывать head на пустом списке, тупица!"
head' (x:_) = x
Проверим, работает ли это…
ghci> head' [4,5,6]
4
ghci> head' "Привет"
H'
Отлично! Заметьте, что если вы хотите выполнить привязку к нескольким переменным (даже если одна из них обозначена всего лишь символом _ и на самом деле ни с чем не связывается), вам необходимо заключить их в круглые скобки. Также обратите внимание на использование функции error. Она принимает строковый параметр и генерирует ошибку времени исполнения, используя этот параметр для сообщения о причине ошибки.
Вызов функции error приводит к аварийному завершению программы, так что не стоит использовать её слишком часто. Но вызов функции head на пустом списке не имеет смысла.
Давайте напишем простую функцию, которая сообщает нам о нескольких первых элементах списка – в довольно неудобной, чересчур многословной форме.
tell :: (Show a) => [a] –> String
tell [] = "Список пуст"