haskell-notes

=> cat a (m b) -> cat b c -> cat a (m c)

f +> g = f *> (g >> idK)

Мы заменили функциональный тип на его обобщение. Для наглядности мы будем пользоваться специ-

альной формулировкой со стрелочным типом.

Для этого мы определим модуль Kleisli. hs

module Kleisli where

import Prelude hiding (id, (>> ))

class Category cat where

id

:: cat a a

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

class Kleisli m where

idK

:: a -> m a

(*> ) :: (a -> m b) -> (b -> m c) -> (a -> m c)

(+> ) :: Kleisli m => (a -> m b) -> (b -> c) -> (a -> m c)

f +> g = f *> (g >> idK)

— Экземпляр для функций

instance Category (-> ) where

id

= x -> x

f >> g

= x -> g (f x)

Мы не будем импортировать функцию id, а определим её в классе Category. Также в Prelude уже опре-

делена функция (>> ) мы спрячем её с помощью специальной директивы hiding для того, чтобы она нам не

мешалась. Далее мы будем дополнять этот модуль экземплярами класса Kleisli и примерами.

6.2 Примеры специальных функций

Частично определённые функции

Частично определённые функции – это такие функции, которые определены не для всех значений аргу-

ментов. Примером такой функции может быть функция поиска предыдущего числа для натуральных чисел.

Поскольку числа натуральные, то для нуля такого числа нет. Для описания этого поведения мы можем вос-

пользоваться специальным типом Maybe. Посмотрим на его определение:

data Maybe a = Nothing | Just a

deriving (Show, Eq, Ord)

88 | Глава 6: Функторы и монады: теория

a

f

b

Nothing

Рис. 6.2: Частично определённая функция

Частично определённая функция имеет тип a -> Maybe b (рис. 6.2), если всё в порядке и значение было

вычислено, она вернёт (Just a), а в случае ошибки будет возвращено значение Nothing. Теперь мы можем

определить нашу функцию так:

pred :: Nat -> Maybe Nat

pred Zero

= Nothing

pred (Succ a)

= Just a

Для Zero предыдущий элемент не определён .

Составляем функции вручную

Значение функции pred завёрнуто в упаковку Maybe, и для того чтобы воспользоваться им нам придётся

разворачивать его каждый раз. Как будет выглядеть функция извлечения дважды предыдущего натурального

числа:

pred2 :: Nat -> Maybe Nat

pred2 x =

case pred x of

Just (Succ a) -> Just a

_

-> Nothing

Если мы захотим определить pred3, мы заменим pred в case-выражении на pred2. Вроде не такое уж и

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

стиля. Нам бы хотелось написать так:

pred2 :: Nat -> Maybe Nat

pred2 = pred >> pred

pred3 :: Nat -> Maybe Nat

pred3 = pred >> pred >> pred

Но компилятор этого не допустит.

Композиция

Для того чтобы понять как устроена композиция частично определённых функций изобразим её вычисле-

ние графически (рис. 6.3). Сверху изображены две частично определённых функции. Если функция f вернула

значение, то оно подставляется в следующую частично определённую функцию. Если же первая функция не

смогла вычислить результат и вернула Nothing, то считается что вся функция (f*> g) вернула Nothing.

Теперь давайте закодируем это определение в Haskell. При этом мы воспользуемся нашим классом

Kleisli. Аналогом функции id для частично определённых функций будет функция, которая просто за-

ворачивает значение в конструктор Just.

instance Kleisli Maybe where

idK

= Just

f *> g = a -> case f a of

Nothing -> Nothing

Just b

-> g b

Смотрите, в case-выражении мы возвращаем Nothing, если функция f вернула Nothing, а если ей удалось

вычислить значение и она вернула (Just b) мы передаём это значение в следующую функцию, то есть

составляем выражение (g b).

Сохраним это определение в модуле Kleisli, а также определение для функции pred и загрузим модуль

в интерпретатор. Перед этим нам придётся добавить в список функций, которые мы не хотим импортировать

из Prelude функцию pred, она также уже определена в Prelude. Для определения нашей функции нам по-

требуется модуль Nat, который мы уже определили. Скопируем файл Nat. hs в ту же директорию, в которой

содержится файл Kleisli. hs и подключим этот модуль. Шапка модуля примет вид:

Примеры специальных функций | 89

a

f

b

b

g

c

Nothing

Nothing

b

a

g

f

c

Nothing

a

f*>g

c

Nothing

Рис. 6.3: Композиция частично определённых функций

module Kleisli where

import Prelude hiding(id, (>> ), pred)

import Nat

Добавим определение экземпляра Kleisli для Maybe в модуль Kleisli а также определение функции

pred. Сохраним обновлённый модуль и загрузим в интерпретатор.

*Kleisli> :load Kleisli

[1 of 2] Compiling Nat

( Nat. hs, interpreted )

[2 of 2] Compiling Kleisli

( Kleisli. hs, interpreted )

Ok, modules loaded: Kleisli, Nat.

*Kleisli> let pred2 = pred *> pred

*Kleisli> let pred3 = pred *> pred *> pred

*Kleisli> let two

= Succ (Succ Zero)

*Kleisli>

*Kleisli> pred two

Just (Succ Zero)

*Kleisli> pred3 two

Nothing

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

стично определённых функций закодировано в функции (*> ) теперь нам не нужно заворачивать значения и

разворачивать их из типа Maybe.

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