haskell-notes

мента потока и возвращает поток:

iterate f a = a :& f a :& f (f a) :& f (f (f a)) :& …

Так с помощью этой функции можно создать поток всех чисел Пеано от нуля или постоянный поток:

nats

= iterate Succ Zero

constStream a

= iterate (x -> x) a

Возможно вас удивляет тот факт, что в этом упражнении мы оперируем бесконечными значениями, но

пока мы не будем вдаваться в детали того как это работает, просто попробуйте определить этот модуль и

посмотрите в интерпретаторе, что получится.

Упражнения | 71

Глава 5

Функции высшего порядка

Функцией высшего порядка называют функцию, которая может принимать на вход функции или возвращать

функции в качестве результата. За счёт частичного применения в Haskell все функции, которые принимают

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

В этой главе мы подробно обсудим способы составления функций, недаром Haskell – функциональный

язык. В Haskell функции являются очень гибким объектом, они позволяют выделять сложные способы ком-

бинирования значений. Часто за счёт развитых средств составления новых функций в Haskell пользователь

определяет лишь базовые функции, получая остальные “на лету” применением двух-трёх операций, это вы-

глядит примерно как (2+3)*5, где вместо чисел стоят базовые функции, а операции + и * составляют новые

функции из простейших.

5.1 Обобщённые функции

В этом разделе мы познакомимся с несколькими функциями, которые принимают одни функции и состав-

ляют по ним другие. Эти функции используются в Haskell очень часто. Все они живут в модуле Data.Function.

Модуль Prelude экспортирует их из этого модуля.

Функция тождества

Начнём с самой простой функции. Это функция id. Она ничего не делает с аргументом, просто возвращает

его:

id :: a -> a

id x = x

Зачем нам может понадобиться такая функция? Сама по себе она бесполезна. Она приобретает ценность

при совместном использовании с другими функциями, поэтому пока мы не будем приводить примеров.

Константная функция

Следующая функция const принимает значение и возвращает постоянную функцию. Эта функция будет

возвращать константу для любого переданного в неё значения:

const :: a -> b -> a

const a _ = a

Функция const является конструктором постоянных функций, так например мы получаем пятёрки на

любой аргумент:

Prelude> let onlyFive = const 5

Prelude> :t onlyFive

onlyFive :: b -> Integer

Prelude> onlyFive ”Hi”

5

Prelude> onlyFive (1,2,3)

5

Prelude> map onlyFive ”abracadabra”

[5,5,5,5,5,5,5,5,5,5,5]

72 | Глава 5: Функции высшего порядка

С её помощью мы можем легко построить и постоянную функцию двух аргументов:

const2 a = const (const a)

Вспомним определение для && :

(&& ) :: Bool -> Bool -> Bool

(&& ) True

x

= x

(&& ) False

_

= False

С помощью функций id и const мы можем сократить число аргументов и уравнений:

(&& ) :: Bool -> Bool -> Bool

(&& ) a = if a then id else (const False)

Также мы можем определить и логическое “или”:

(||) :: Bool -> Bool -> Bool

(||) a = if a then (const True) else id

Функция композиции

Функция композиции принимает две функции и составляет из них последовательное применение функ-

ций:

(. ) :: (b -> c) -> (a -> b) -> a -> c

(. ) f g = x -> f (g x)

Это очень полезная функция. Она позволяет нанизывать функции друг на друга. Мы перехватываем выход

второй функции, сразу подставляем его в первую и возвращаем её выход в качестве результата. Например

перевернём список символов и затем сделаем все буквы заглавными:

Prelude> :m +Data.Char

Prelude Data.Char> (map toUpper . reverse) ”abracadabra”

”ARBADACARBA”

Приведём пример посложнее:

add :: Nat -> Nat -> Nat

add

a

Zero

= a

add

a

(Succ b) = Succ (add a b)

Если мы определим функцию свёртки для Nat, которая будет заменять в значении типа Nat конструкторы

на соответствующие по типу функции:

foldNat :: a -> (a -> a) -> Nat -> a

foldNat zero succ Zero

= zero

foldNat zero succ (Succ b) = succ (foldNat zero succ b)

То мы можем переписать с помощью функции композиции эту функцию так:

add :: Nat -> Nat -> Nat

add = foldNat

id

(Succ . )

Куда делись аргументы? Они выражаются через функции id и (. ). Поведение этой функции лучше про-

иллюстрировать на примере. Пусть у нас есть два числа типа Nat:

two

= Succ (Succ Zero)

three

= Succ (Succ (Succ Zero))

Вычислим

add two three

Вспомним о частичном применении:

Обобщённые функции | 73

add two three

=>

(add two) three

=>

(foldNat id (Succ . ) (Succ (Succ Zero))) three

Теперь функция свёртки заменит все конструкторы Succ на (Succ . ), а конструкторы Zero на id:

=>

((Succ . ) ((Succ . ) id)) three

Что это за монстр?

((Succ . ) ((Succ . ) id))

Функция (Succ . ) это левое сечение операции (. ). Эта функция, которая принимает функции и возвра-

щает функции. Она принимает функцию и навешивает на её выход конструктор Succ. Давайте упростим это

большое выражение с помощью определений функций (. ) и id:

((Succ . ) ((Succ . ) id))

=>

(Succ . ) (x -> Succ (id x))

=>

(Succ . ) (x -> Succ x)

=>

x -> Succ (Succ x)

Теперь нам осталось применить к этой функции наше второе значение:

Страницы: 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