Этот класс является классом Category в мире наших специальных функций. Если мы сотрём все буквы m,
то мы получим обычные типы для тождества и композиции. В этом мире должны выполняться те же правила:
f
*> idK
== f
idK *> f
== f
f *> (g *> h) == (f *> g) *> h
Взаимодействие с внешним миром
С помощью класса Kleisli мы можем составлять из одних специальных функций другие. Но как мы
сможем комбинировать специальные функции с обычными?
Поскольку слева у нашей специальной функции обычный общий тип, то с этой стороны мы можем вос-
пользоваться обычной функцией композиции >> . Но как быть при композиции справа? Нам нужна функция
типа:
(a -> m b) -> (b -> c) -> (a -> m c)
Оказывается мы можем составить её из методов класса Kleisli. Мы назовём эту функцию композиции
(+> ).
(+> ) :: Kleisli m => (a -> m b) -> (b -> c) -> (a -> m c)
f +> g = f *> (g >> idK)
С помощью метода idK мы можем погрузить в мир специальных функций любую обычную функцию.
Композиция функций | 87
Три композиции
У нас появилось много композиций целых три:
аргументы
|
результат
обычная
>>
обычная
==
обычная
специальная
+>
обычная
==
специальная
специальная
*>
специальная
==
специальная
При этом важно понимать, что по смыслу это три одинаковые функции. Они обозначают операцию по-
следовательного применения функций. Разные значки отражают разные типы функций аргументов.
Обобщённая формулировка категории Клейсли
Отметим, что мы могли бы сформулировать класс Kleisli и в более общем виде с помощью класса
Category:
class Kleisli m where
idK
:: Category cat => cat a (m a)
(*> ) :: Category cat => cat a (m b) -> cat b (m c) -> cat a (m c)
(+> ) :: (Category cat, Kleisli m)
=> 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 вернула