
lucky :: (Integral a) => a -> String
lucky 7 = "LUCKY NUMBER SEVEN!"
lucky x = "Sorry, you're out of luck, pal!"lucky, происходит проверка параметра на совпадение с заданными шаблонами, в том порядке как они были заданы. Когда проверка даст положительный результат — используется соответствующее тело функции. Единственный случай, когда число переданное функции удовлетворяет первому шаблону — когда оно равно семи. Если нет, то происходит проверка на совпадение со следующим шаблоном. Следующий шаблон может быть успешно сопоставлен с любым числом, также он привязывает переданное число к переменной x."Not between 1 and 5" для чисел? Без сопоставления с образцом нам бы пришлось создать очень запутанное дерево выражений if then else. А вот что получится, если использовать сопоставление:sayMe :: (Integral a) => a -> String
sayMe 1 = "One!"
sayMe 2 = "Two!"
sayMe 3 = "Three!"
sayMe 4 = "Four!"
sayMe 5 = "Five!"
sayMe x = "Not between 1 and 5""Not between 1 and 5", потому что этот шаблон подходит для любого числа, и невозможно было бы пройти дальше и сделать проверку на совпадение с другими шаблонами.n как произведение чисел [1..n]. Мы можем определить данную функцию рекурсивно, точно так же, как факториал определяется в математике. Начнем с того, что объявим факториал нуля равным единице.factorial :: (Integral a) => a -> a
factorial 0 = 1
factorial n = n * factorial (n - 1)3 * factorial 2. factorial 2 — это 2 * factorial 1, таким образом, у нас получается 3 * (2 * factorial 1). factorial 1 — это 1 * factorial 0, и в итоге у нас 3 * (2 * (1 * (factorial 0))).charName :: Char -> String
charName 'a' = "Albert"
charName 'b' = "Broseph"
charName 'c' = "Cecil"ghci> charName 'a'
"Albert"
ghci> charName 'b'
"Broseph"
ghci> charName 'h'
"*** Exception: tut.hs:(53,0)-(55,21): Non-exhaustive patterns in function charName
addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
addVectors a b = (fst a + fst b, snd a + snd b)addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a)
addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)addVectors (в обоих случаях) — (Num a) => (a, a) -> (a, a) -> (a, a), так что мы гарантировано получим две пары на входе.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 "Can't call head on an empty list, dummy!"
head' (x:_) = xghci> head' [4,5,6]
4
ghci> head' "Hello"
'H'_ и на самом деле ни с чем не связывается), вам необходимо заключить их в круглые скобки. Также обратите внимание на использование функции error. Она принимает строковый параметр и генерирует ошибку времени исполнения, используя этот параметр для сообщения о причине ошибки.error приводит к аварийному завершению программы, так что не стоит использовать её слишком часто. Но вызов head на пустом списке не имеет смысла.tell :: (Show a) => [a] -> String
tell [] = "The list is empty"
tell (x:[]) = "The list has one element: " ++ show x
tell (x:y:[]) = "The list has two elements: " ++ show x ++ " and " ++ show y
tell (x:y:_) = "This list is long. The first two elements are: " ++ show x ++ " and " ++ show y(x:[]) и (x:y:[]) могут быть записаны как [x] и [x,y] (так как это облегченная запись, нам не нужны круглые скобки). Мы не можем записать (x:y:_) с помощью квадратных скобок, потому что такая запись соответствует любому списку длиной два или более.length, используя выражения списков. Теперь мы попробуем создать её с помощью сопоставления с образцом и небольшой рекурсии:length' :: (Num b) => [a] -> b
length' [] = 0
length' (_:xs) = 1 + length' xs_, поскольку нам не нужно знать, какое именно значение содержалось в головном элементе. Также стоит отметить, что шаблоны записаны для всех возможных случаев, которые могут возникнуть при работе со списками. Первый шаблон обрабатывает случай, когда список пуст, второй шаблон обрабатывает все непустые списки. length' на "ham". Во-первых, произойдет проверка, пустой список или нет. Так как список не пустой, будет проверяться второй шаблон. Список соответствует второму шаблону, следовательно, он разбивается на голову и хвост, длина получается равна 1 + length' "am". Океюшки."am", таким же образом, это 1 + length' "m". Длина "m" — это 1 + length' "" (также может быть записано как 1 + length' []). Мы определили length' [] как равную нулю. Таким образом, в конце получаем 1 + (1 + (1 + 0)).sum. Мы знаем, что сумма элементов пустого списка равна нулю. Запишем это в виде шаблона. Также ясно, что сумма элементов списка — это значение из головы списка плюс сумма всех остальных элементов. Если все это записать, у нас получится так:sum' :: (Num a) => [a] -> a
sum' [] = 0
sum' (x:xs) = x + sum' xs@ перед образцом. Например, шаблон xs@(x:y:ys).x:y:ys, но вы легко можете получить исходный список по имени xs, вместо того, чтобы раз за разом печатать x:y:ys в теле функции. Вот пример на скорую руку: capital :: String -> String
capital "" = "Empty string, whoops!"
capital all@(x:xs) = "The first letter of " ++ all ++ " is " ++ [x]
ghci> capital "Dracula"
"The first letter of Dracula is D"++ в шаблонах. Если вы попытаетесь записать шаблон который сравнивает с (xs ++ ys), что будет соответствовать первому и второму списку? В этом немного смысла. Возможно было бы полезно уметь сопоставлять с (xs ++ [x,y,z]) или (xs ++ [x]), но по самой сути списков вы не можете этого делать.
В то время как шаблоны — это способ убедиться, что значение соответствует некоторой форме, и разобрать его на части, сторожевые условия — это способ проверить истинность некоторого свойства у значения, переданного функции (или нескольких значений). Это похоже на оператор if и работает схожим образом. На самом деле, сторожевые условия гораздо легче читать, если у вас несколько условий и они отлично работают с шаблонами. bmiTell :: (RealFloat a) => a -> String
bmiTell bmi
| bmi <= 18.5 = "You're underweight, you emo, you!" --Эй ты, дистрофик!
| bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!" --Ладно, нормальный. Зато небось уродец!
| bmi <= 30.0 = "You're fat! Lose some weight, fatty!" --Ты толстый! Хоть немного веса сбрось!
| otherwise = "You're a whale, congratulations!" --Мои поздравления, ты - жирный боров!True, используется соответствующее тело функции. Если вычисленное условие ложно, проверка продолжается со следующего условия, и так далее.24.3, она вначале проверит, не является ли это значение меньшим или равным 18.5. Так как сторожевое условие не выполняется, функция перейдет к следующему. Проверяется следующее условие, и так как 24.3 меньше чем 25.0, будет возвращена вторая строка.otherwise (иначе). Otherwise определяется просто как otherwise = True, такое условие всегда истинно. Работа условий очень похоже на то, как работают шаблоны, но шаблоны проверяют входные данные, а сторожевые условия могут делать любые проверки.bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!" --Эй ты, дистрофик!
| weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!" --Ладно, нормальный. Зато небось уродец!
| weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!" --Ты толстый! Хоть немного веса сбрось!
| otherwise = "You're a whale, congratulations!"--Мои поздравления, ты жирный боров!
ghci> bmiTell 85 1.90
"You're supposedly normal. Pffft, I bet you're ugly!"
--Ладно, нормальный. Зато небось уродец!max' :: (Ord a) => a -> a -> a
max' a b
| a > b = a
| otherwise = bmax' таким образом:max' :: (Ord a) => a -> a -> a
max' a b | a > b = a | otherwise = bmyCompare :: (Ord a) => a -> a -> Ordering
a `myCompare` b
| a > b = GT
| a == b = EQ
| otherwise = LTghci> 3 `myCompare` 2
GTbmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| weight / height ^ 2 <= 18.5 = "You're underweight, you emo, you!"
| weight / height ^ 2 <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| weight / height ^ 2 <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= 18.5 = "You're underweight, you emo, you!"
| bmi <= 25.0 = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= 30.0 = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2where после сторожевых условий (обычно его печатают с тем же отступом как и сторожевые условия), и затем определяем несколько имен или функций. Эти имена видимы внутри объявления функции и позволяют нам не повторять код. Если вдруг нам вздумается вычислять ИМТ другим образом, мы должны исправить способ его вычисления только однажды.where улучшает читаемость, так как дает имена понятиям и может сделать наши программы быстрее за счет того, что переменные вроде нашей bmi вычисляются только однажды. Мы можем зайти еще дальше и представить нашу функцию так:bmiTell :: (RealFloat a) => a -> a -> String
bmiTell weight height
| bmi <= skinny = "You're underweight, you emo, you!"
| bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"
| bmi <= fat = "You're fat! Lose some weight, fatty!"
| otherwise = "You're a whale, congratulations!"
where bmi = weight / height ^ 2
skinny = 18.5
normal = 25.0
fat = 30.0...
where bmi = weight / height ^ 2
(skinny, normal, fat) = (18.5, 25.0, 30.0)initials :: String -> String -> String
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."
where (f:_) = firstname
(l:_) = lastnamecalcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi w h | (w, h) <- xs]
where bmi weight height = weight / height ^ 2
bmi в виде функции в данном примере, заключается в том, что мы не можем просто вычислить один ИМТ для параметров, переданных в функцию. Нам необходимо пройтись по всему списку и для каждой пары вычислить ИМТ.let очень похожи на определения where. Where — это синтаксическая конструкция, которая позволяет вам связывать выражения с переменными в конце функции, объявленные переменные видны во всем теле функции, включая сторожевые условия. Let позволяет вам связывать выражения с именами в любом месте функции, конструкции let сами по себе являются выражениями, но их область видимости ограничена локальным контекстом. Таким образом определение let сделанное в сторожевом условии видно только в нем самом.cylinder :: (RealFloat a) => a -> a -> a
cylinder r h =
let sideArea = 2 * pi * r * h
topArea = pi * r ^2
in sideArea + 2 * topArea
Общее выражение выглядит как let <определения> in <выражение>
. Имена, которые вы определили в части let, видимы в выражении после in. Как видите, мы могли бы воспользоваться where для той же цели. Обратите внимание, что имена также выровнены в одну позицию. Ну и какая разница между where и let? Выглядит так, будто в let вначале следуют определения, а затем выражение, а в where наоборот.ghci> [if 5 > 3 then "Woo" else "Boo", if 'a' > 'b' then "Foo" else "Bar"]
["Woo", "Bar"]
ghci> 4 * (if 10 > 5 then 10 else 0) + 2
42ghci> 4 * (let a = 9 in a + 1) + 2
42ghci> [let square x = x * x in (square 5, square 3, square 2)]
[(25,9,4)]ghci> (let a = 100; b = 200; c = 300 in a*b*c, let foo="Hey "; bar = "there!" in foo ++ bar)
(6000000,"Hey there!")ghci> (let (a,b,c) = (1,2,3) in a+b+c) * 100
600calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2]calcBmis :: (RealFloat a) => [(a, a)] -> [a]
calcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]bmi в части (w, h) <- xs, потому что оно определено до let.ghci> let zoot x y z = x * y + z
ghci> zoot 3 9 2
29
ghci> let boot x y z = x * y + z in boot 3 4 2
14
ghci> boot
<interactive>:1:0: Not in scope: `boot'
Многие императивные языки (C, C++, Java, и т.д.) имеют оператор case, и если вам доводилось программировать на них, вы знаете, что это такое. Вы берете переменную и выполняете некую часть кода для каждого значения этой переменной, ну и, возможно, используете финальное условие, которое срабатывает если не отработали другие.head' :: [a] -> a
head' [] = error "No head for empty lists!"
head' (x:_) = x
head' :: [a] -> a
head' xs = case xs of [] -> error "No head for empty lists!"
(x:_) -> x
case expression of pattern -> result
pattern -> result
pattern -> result
...describeList :: [a] -> String
describeList xs = "The list is " ++ case xs of [] -> "empty."
[x] -> "a singleton list."
xs -> "a longer list."describeList :: [a] -> String
describeList xs = "The list is " ++ what xs
where what [] = "empty."
what [x] = "a singleton list."
what xs = "a longer list."Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.
комментарии (10)