=> 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.