Выбрать главу

дерева значения в аргументах функции.

Часто нам хочется определить более сложные условия для альтернатив. Например, если значение на

входе функции больше 2, но меньше 10, верни A, а если больше 10, верни B, а во всех остальных случаях

верни C. Или если на вход поступила строка состоящая только из букв латинского алфавита, верни A, а

в противном случае верни B. Нам бы хотелось реагировать лишь в том случае, если значение некоторого

типа a удовлетворяет некоторому предикату. Предикатами обычно называют функции типа a -> Bool. Мы

говорим, что значение удовлетворяет предикату, если предикат для этого значения возвращает True.

62 | Глава 4: Декларативный и композиционный стиль

Охранные выражения

В декларативном стиле условные выражения представлены охранными выражениями (guards). Предполо-

жим у нас есть тип:

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

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)