Этот класс является классом Category
в мире наших специальных функций. Если мы сотрём все буквы m,то мы получим обычные типы для тождества и композиции. В этом мире должны выполняться те же правила:
f
*>
idK==
fidK *>
f==
ff *>
(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 whereidK
:: 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.
hsmodule Kleisli where
import Prelude hiding
(id, (>> ))class Category
cat whereid
::
cat a a(>>
) :: cat a b -> cat b c -> cat a cclass Kleisli
m whereidK
::
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
(-> ) whereid
=
\x -> xf >>
g=
\x -> g (f x)Мы не будем импортировать функцию id, а определим её в классе Category
. Также в Prelude уже опре-делена функция (>>
) мы спрячем её с помощью специальной директивы hiding для того, чтобы она нам немешалась. Далее мы будем дополнять этот модуль экземплярами класса Kleisli
и примерами.6.2 Примеры специальных функций
Частично определённые функции
Частично определённые функции – это такие функции, которые определены не для всех значений аргу-
ментов. Примером такой функции может быть функция поиска предыдущего числа для натуральных чисел.
Поскольку числа натуральные, то для нуля такого числа нет. Для описания этого поведения мы можем вос-
пользоваться специальным типом Maybe
. Посмотрим на его определение:data Maybe
a = Nothing | Just aderiving
(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 ofJust
(Succ a) -> Just a_
-> Nothing
Если мы захотим определить pred3, мы заменим pred в case
-выражении на pred2. Вроде не такое уж идлинное решение. Но всё же мы теряем все преимущества гибких функций, все преимущества бесточечного
стиля. Нам бы хотелось написать так:
pred2 :: Nat -> Maybe Nat
pred2 =
pred >> predpred3 :: Nat -> Maybe Nat
pred3 =
pred >> pred >> predНо компилятор этого не допустит.
Композиция
Для того чтобы понять как устроена композиция частично определённых функций изобразим её вычисле-
ние графически (рис. 6.3). Сверху изображены две частично определённых функции. Если функция f вернула