where
rest = filter p xsИли мы можем разместить охранные выражения по-другому:
filter ::
(a -> Bool) -> [a] -> [a]filter
p
[]
= []
filter
p
(x:
xs)|
p x=
x : rest|
otherwise = restwhere
rest = filter p xsОтметим то, что локальная переменная rest видна и в той и в другой альтернативе. Вы спокойно можете
пользоваться локальными переменными в любой части уравнения, в котором они определены.
Определим с помощью охранных выражений функцию all, она принимает предикат и список, и проверяет
удовлетворяют ли все элементы списка данному предикату.
all ::
(a -> Bool) -> [a] -> Boolall p []
= True
all p (x:
xs)|
p x=
all p xs|
otherwise = FalseС помощью охранных выражений можно очень наглядно описывать условные выражения. Но иногда мож-
но обойтись и простыми логическими операциями. Например функцию all можно было бы определить так:
Условные выражения | 63
all ::
(a -> Bool) -> [a] -> Boolall
p
[]
= True
all
p
(x:
xs)=
p x && all p xsИли так:
all ::
(a -> Bool) -> [a] -> Boolall
p
xs =
null (filter notP xs)where
notP x = not (p x)Или даже так:
import Prelude
(all)Функция null определена в Prelude
она возвращает True только если список пуст.if-выражения
В композиционном стиле в качестве условных выражений используются уже знакомые нам if
-выражения.Вспомним как они выглядят:
a = if
boolthen
x1else
x2Слова if
, then и else – ключевые. Тип a, x1 и x2 совпадают.Любое охранное выражение, в котором больше одной альтернативы, можно представить в виде if
-выражения и наоборот. Перепишем все функции их предыдущего подраздела с помощью if
-выражений:hallCapacity :: Int -> HowMany
hallCapacity n =
if
(n < 10)then Little
else
(if n < 30then Enough
else Many
)all ::
(a -> Bool) -> [a] -> Boolall p []
= True
all p (x:
xs) = if (p x) then all p xs else False4.4 Определение функций
Под функцией мы понимаем составной синоним, который принимает аргументы, возможно разбирает их
на части и составляет из этих частей новые выражения. Теперь посмотрим как такие синонимы определяются
в каждом из стилей.
Уравнения
В декларативном стиле функции определяются с помощью уравнений. Пока мы видели лишь этот способ
определения функций, примерами могут служить все предыдущие примеры. Вкратце напомним, что функция
определяется набором уравнений вида:
name декомпозиция
1 = композиция1name декомпозиция
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Интерпретатор сообщил нам о том, что он не нашёл уравнения для переданного в функцию значения.
Безымянные функции
В композиционном стиле функции определяются по-другому. Это необычный метод, он пришёл в
Haskell из лямбда-исчисления. Функции строятся с помощью специальных конструкций, которые называ-
ются лямбда-функциями. По сути лямбда-функции являются безымянными функциями. Давайте посмотрим
на лямбда функцию, которая прибавляет к аргументу единицу:
\x ->
x + 1Для того, чтобы превратить лямбда-функцию в обычную функцию мысленно замените знак \ на имя
noName, а стрелку на знак равно:
noName x =
x + 1Мы получили обычную функцию Haskell, с такими мы уже много раз встречались. Зачем специальный
синтаксис для определения безымянных функций? Ведь можно определить её в виде уравнений. К тому же
кому могут понадобиться безымянные функции? Ведь смысл функции в том, чтобы выделить определённый
шаблон поведения и затем ссылаться на него по имени функции.
Смысл безымянной функции в том, что ею, также как и любым другим элементом композиционного
стиля, можно пользоваться в любой части обычных выражений. С её помощью мы можем создавать функции
“на лету”. Предположим, что мы хотим профильтровать список чисел, мы хотим выбрать из них лишь те, что
меньше 10, но больше 2, и к тому же они должны быть чётными. Мы можем написать:
f ::
[Int] -> [Int]f =
filter pwhere
p x = x > 2 && x < 10 && even x