возвращает значение типа 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
where rest = filter p xs
Или мы можем разместить охранные выражения по-другому:
filter :: (a -> Bool) -> [a] -> [a]
filter
p
[]
= []
filter
p
(x:xs)
| p x
= x : rest
| otherwise = rest
where rest = filter p xs
Отметим то, что локальная переменная rest видна и в той и в другой альтернативе. Вы спокойно можете
пользоваться локальными переменными в любой части уравнения, в котором они определены.
Определим с помощью охранных выражений функцию all, она принимает предикат и список, и проверяет
удовлетворяют ли все элементы списка данному предикату.
all :: (a -> Bool) -> [a] -> Bool
all p []
= True
all p (x:xs)
| p x
= all p xs
| otherwise = False
С помощью охранных выражений можно очень наглядно описывать условные выражения. Но иногда мож-
но обойтись и простыми логическими операциями. Например функцию all можно было бы определить так:
Условные выражения | 63
all :: (a -> Bool) -> [a] -> Bool
all
p
[]
= True
all
p
(x:xs)
= p x && all p xs
Или так:
all :: (a -> Bool) -> [a] -> Bool
all
p
xs = null (filter notP xs)
where notP x = not (p x)
Или даже так:
import Prelude(all)
Функция null определена в Prelude она возвращает True только если список пуст.
if-выражения
В композиционном стиле в качестве условных выражений используются уже знакомые нам if-выражения.
Вспомним как они выглядят:
a = if bool
then x1
else x2
Слова if, then и else – ключевые. Тип a, x1 и x2 совпадают.
Любое охранное выражение, в котором больше одной альтернативы, можно представить в виде if—
выражения и наоборот. Перепишем все функции их предыдущего подраздела с помощью if-выражений:
hallCapacity :: Int -> HowMany
hallCapacity n =
if (n < 10)
then Little
else (if n < 30
then Enough
else Many)
all :: (a -> Bool) -> [a] -> Bool
all p []
= True
all p (x:xs) = if (p x) then all p xs else False
4.4 Определение функций
Под функцией мы понимаем составной синоним, который принимает аргументы, возможно разбирает их
на части и составляет из этих частей новые выражения. Теперь посмотрим как такие синонимы определяются
в каждом из стилей.
Уравнения
В декларативном стиле функции определяются с помощью уравнений. Пока мы видели лишь этот способ
определения функций, примерами могут служить все предыдущие примеры. Вкратце напомним, что функция
определяется набором уравнений вида:
name декомпозиция1 = композиция1
name декомпозиция2 = композиция2
…
name декомпозицияN = композицияN
Где name – имя функции. В декомпозиции происходит разбор поступающих на вход значений, а в компо-
зиции происходит составление значения результата. Уравнения обходятся вычислителем сверху вниз до тех
пор пока он не найдёт такое уравнение, для которого переданные в функции значения не подойдут в указан-
ный в декомпозиции шаблон значений (если сопоставление с образцом аргументов пройдёт успешно). Как
только такое уравнение найдено, составляется выражение справа от знака равно (композиция). Это значение
будет результатом функции. Если такое уравнение не будет найдено программа остановится с ошибкой.
К примеру попробуйте вычислить в интерпретаторе выражение notT False, для такой функции:
64 | Глава 4: Декларативный и композиционный стиль
notT :: Bool -> Bool
notT True = False
Что мы увидим?
Prelude> notT False
*** Exception: < interactive>:1:4—20: Non-exhaustive patterns in function notT
Интерпретатор сообщил нам о том, что он не нашёл уравнения для переданного в функцию значения.
Безымянные функции
В композиционном стиле функции определяются по-другому. Это необычный метод, он пришёл в