Вызов функции >error
приводит к аварийному завершению программы, так что не стоит использовать её слишком часто. Но вызов функции >head
на пустом списке не имеет смысла.
Давайте напишем простую функцию, которая сообщает нам о нескольких первых элементах списка – в довольно неудобной, чересчур многословной форме.
>tell :: (Show a) => [a] –> String
>tell [] = "Список пуст"
>tell (x:[]) = "В списке один элемент: " ++ show x
>tell (x:y:[]) = "В списке два элемента: " ++ show x ++ " и " ++ show y
>tell (x:y:_) = "Список длинный. Первые два элемента: " ++ show x
> ++ " и " ++ show y
Обратите внимание, что образцы >(x:[])
и >(x:y:[])
можно записать как >[x]
и >[x,y]
. Но мы не можем записать >(x:y:_)
с помощью квадратных скобок, потому что такая запись соответствует любому списку длиной два или более.
Вот несколько примеров использования этой функции:
>ghci> tell [1]
>"В списке один элемент: 1"
>ghci> tell [True, False]
>"В списке два элемента: True и False"
>ghci> tell [1, 2, 3, 4]
>"Список длинный. Первые два элемента: 1 и 2"
>ghci> tell []
>"Список пуст"
Функцию >tell
можно вызывать совершенно безопасно, потому что её параметр можно сопоставлять пустому списку, одноэлементному списку, списку с двумя и более элементами. Она умеет работать со списками любой длины и всегда знает, что нужно возвратить.
А что если определить функцию, которая умеет обрабатывать только списки с тремя элементами? Вот один такой пример:
>badAdd :: (Num a) => [a] -> a
>badAdd (x:y:z:[]) = x + y + z
А вот что случится, если подать ей не то, что она ждёт:
>ghci> badAdd [100, 20]
>*** Exception: Non-exhaustive patterns in function badAdd
Это не так уж и хорошо. Если подобное случится в скомпилированной программе, то она просто вылетит.
И последнее замечание относительно сопоставления с образцами для списков: в образцах нельзя использовать операцию >++
(напомню, что это объединение двух списков). К примеру, если вы попытаетесь написать в образце >(xs++ys)
, то Haskell не сможет определить, что должно попасть в >xs
, а что в >ys
. Хотя и могут показаться логичными сопоставления типа >(xs++[x,y,z])
или даже >(xs ++ [x])
, работать это не будет – такова природа списков[7].
Именованные образцы
Ещё одна конструкция называется именованным образцом. Это удобный способ разбить что-либо в соответствии с образцом и связать результат разбиения с переменными, но в то же время сохранить ссылку на исходные данные. Такую задачу можно выполнить, поместив некий идентификатор образца и символ >@
перед образцом, описывающим структуру данных. Например, так выглядит образец >xs@(x:y:ys)
.
Подобный образец работает так же, как (>x:y:ys)
, но вы легко можете получить исходный список по имени >xs
, вместо того чтобы раз за разом печатать >x:y:ys
в теле функции. Приведу пример:
>firstLetter :: String –> String
>firstLetter "" = "Упс, пустая строка!"
>firstLetter all@(x:xs) = "Первая буква строки " ++ all ++ " это " ++ [x]
Загрузим эту функцию и посмотрим, как она работает:
>ghci> firstLetter "Дракула"
>"Первая буква строки Дракула это Д"
Эй, стража!
В то время как образцы – это способ убедиться, что значение соответствует некоторой форме, и разобрать его на части,