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 "Дракула"
"Первая буква строки Дракула это Д"
Эй, стража!
В то время как образцы – это способ убедиться, что значение соответствует некоторой форме, и разобрать его на части, if
: оно работает схожим образом. Однако охранные выражения гораздо легче читать, если у вас имеется несколько условий; к тому же они отлично работают с образцами.
Вместо того чтобы объяснять их синтаксис, давайте просто напишем функцию с использованием охранных условий. Эта простая функция будет оценивать вас на основе ИМТ (индекса массы тела). Ваш ИМТ равен вашему весу, разделённому на квадрат вашего роста.
Если ваш ИМТ меньше 18,5, можно считать вас тощим. Если ИМТ составляет от 18,5 до 25, ваш вес в пределах нормы. От 25 до 30 – вы полненький; более 30 – тучный. Запишем эту функцию (мы не будем рассчитывать ИМТ, функция принимает его как параметр и ругнёт вас соответственно).
bmiTell :: Double -> String
bmiTell bmi
| bmi <= 18.5 = "Слышь, эмо, ты дистрофик!"
| bmi <= 25.0 = "По части веса ты в норме. Зато, небось, уродец!"
| bmi <= 30.0 = "Ты толстый! Сбрось хоть немного веса!"
| otherwise = "Мои поздравления, ты жирный боров!"