Сопоставление с образцом может быть использовано и для кортежей. Что если мы хотим создать функцию, которая принимает два двумерных вектора (представленных в форме пары) и складывает их? Чтобы сложить два вектора, нужно сложить их соответствующие координаты. Вот как мы написали бы такую функцию, если б не знали о сопоставлении с образцом:
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 [] = "Список пуст"