haskell-notes

возвращает значение типа 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:420: Non-exhaustive patterns in function notT

Интерпретатор сообщил нам о том, что он не нашёл уравнения для переданного в функцию значения.

Безымянные функции

В композиционном стиле функции определяются по-другому. Это необычный метод, он пришёл в

Страницы: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162