beside :: Nat -> (Nat, Nat)
beside
Zero
= error ”undefined”
beside
x@(Succ y) = (y, Succ x)
В выражении x“(Succ y)@ мы одновременно проводим разбор и даём имя всему значению.
Декомпозиция | 61
case-выражения
Оказывается декомпозицию можно проводить в любом выражении, для этого существуют case-
выражения:
data AnotherNat = None | One | Two | Many
deriving (Show, Eq)
toAnother :: Nat -> AnotherNat
toAnother x =
case x of
Zero
-> None
Succ Zero
-> One
Succ (Succ Zero)
-> Two
_
-> Many
fromAnother :: AnotherNat -> Nat
fromAnother None
= Zero
fromAnother One
= Succ Zero
fromAnother Two
= Succ (Succ Zero)
fromAnother Many
= error ”undefined”
Слова case и of – ключевые. Выгодным отличием case-выражений является то, что нам не приходит-
ся каждый раз выписывать имя функции. Обратите внимание на то, что в case-выражениях также можно
пользоваться обычными переменными и безымянными переменными.
Для проведения декомпозиции по нескольким переменным можно воспользоваться кортежами. Например
определим знакомую функцию равенства для Nat:
instance Eq Nat where
(==) a b =
case (a, b) of
(Zero,
Zero)
-> True
(Succ a’, Succ b’)
-> a’ == b’
_
-> False
Мы проводим сопоставление с образцом по кортежу (a, b), соответственно слева от знака -> мы прове-
ряем значения в кортежах, для этого мы также заключаем значения в скобки и пишем их через запятую.
Давайте определим функцию filter в ещё более композиционном стиле. Для этого мы заменим в исход-
ном определении where на let и декомпозицию в аргументах на case-выражение:
filter :: (a -> Bool) -> [a] -> [a]
filter
p
a =
case a of
[]
-> []
x:xs
->
let rest = filter p xs
in
if (p x)
then (x:rest)
else rest
4.3 Условные выражения
С условными выражениями мы уже сталкивались в сопоставлении с образцом. Например в определении
функции not:
not True
= False
not False = True
В зависимости от поступающего значения мы выбираем одну из двух альтернатив. Условные выражении
в сопоставлении с образцом позволяют реагировать лишь на частичное (с учётом переменных) совпадение
дерева значения в аргументах функции.
Часто нам хочется определить более сложные условия для альтернатив. Например, если значение на
входе функции больше 2, но меньше 10, верни A, а если больше 10, верни B, а во всех остальных случаях
верни C. Или если на вход поступила строка состоящая только из букв латинского алфавита, верни A, а
в противном случае верни B. Нам бы хотелось реагировать лишь в том случае, если значение некоторого
типа a удовлетворяет некоторому предикату. Предикатами обычно называют функции типа a -> Bool. Мы
говорим, что значение удовлетворяет предикату, если предикат для этого значения возвращает True.
62 | Глава 4: Декларативный и композиционный стиль
Охранные выражения
В декларативном стиле условные выражения представлены
жим у нас есть тип:
data HowMany = Little | Enough | Many
И мы хотим написать функцию, которая принимает число людей, которые хотят посетить выставку, а
возвращает значение типа HowMany. Эта функция оценивает вместительность выставочного зала. С помощью
охранных выражений мы можем написать её так:
hallCapacity :: Int -> HowMany
hallCapacity n
| n < 10
= Little
| n < 30
= Enough
| True
= Many
Специальный символ | уже встречался нам в определении типов. Там он играл роль разделителя аль-
тернатив в сумме типов. Здесь же он разделяет альтернативы в условных выражениях. Сначала мы пишем
| затем выражение-предикат, которое возвращает значение типа Bool, затем равно и после равно – возвра-
щаемое значение. Альтернативы так же как и в случае декомпозиции аргументов функции обходятся сверху
вниз, до тех пор пока в одной из альтернатив предикат не вернёт значение True. Обратите внимание на то,
что нам не нужно писать во второй альтернативе:
| 10 <= n && n < 30
= Enough
Если вычислитель дошёл до этой альтернативы, значит значение точно больше либо равно 10. Поскольку
в предыдущей альтернативе предикат вернул False.
Предикат в последней альтернативе является константой True, он пройдёт сопоставление с любым зна-
чением n. В данном случае, если учесть предыдущие альтернативы мы знаем, что если вычислитель дошёл
до последней альтернативы , значение n больше либо равно 30. Для повышения наглядности кода в Prelude
определена специальная константа-синоним значению True под именем otherwise.
Определим функцию filter для списков в более декларативном стиле, для этого заменим if-выражение
в исходной версии на охранные выражения:
filter :: (a -> Bool) -> [a] -> [a]
filter
p
[]
= []
filter
p
(x:xs)
| p x
= x : rest
| otherwise
= rest