Свойства класса Functor
fmap id x
== x
-- тождество
fmap f . fmap g
== fmap (f . g)
-- композиция
Первое свойство говорит о том, что если мы применяем fmap к функции тождества, то мы должны снова
получить функцию тождества, или по другому можно сказать, что применение функции тождества к специ-
альному значению не изменяет это значение. Второе свойство говорит о том, что последовательное примене-
ние к специальному значению двух обычных функций можно записать в виде применения композиции двух
обычных функций к специальному значению.
Если всё это звучит туманно, попробуем переписать эти свойства в терминах композиции:
mf +> id
== mf
(mf +> g) +> h
== mf +> (g >> h)
Первое свойство говорит о том, что тождественная функция не изменяет значение при композиции. Вто-
рое свойство указывает на ассоциативность композиции одной специальной функции mf и двух обычных
функций g и h.
Свойства класса Applicative
Свойства класса Applicative, для наглядности они сформулированы не через методы класса, а через
производные функции.
fmap f x
== liftA f x
-- связь с Functor
liftA
id x
== x
-- тождество
liftA3 (. ) f g x
== f <*> (g <*> x)
-- композиция
liftA
f (pure x)
== pure (f x)
-- гомоморфизм
Первое свойство говорит о том, что применение специальной функции одного аргумента совпадает с
методом fmap из класса Functor. Свойство тождества идентично аналогичному свойству для класса Functor.
Свойство композиции сформулировано хитро, но давайте посмотрим на типы аргументов:
(. ) :: (b -> c) -> (a -> b) -> (a -> c)
f
:: m (b -> c)
g
:: m (a -> b)
x
:: m a
liftA3 (. ) f g x :: m c
g <*> x
:: m b
f (g <*> x)
:: m c
Слева в свойстве стоит liftA3, а не liftA2, потому что мы сначала применяем композицию (. ) к двум
функциям f и g, а затем применяем составную функцию к значению x.
Последнее свойство говорит о том, что если мы возьмём обычную функцию и обычное значение и подни-
мем их в мир специальных значений с помощью lift и pure, то это тоже самое если бы мы просто применили
бы функцию f к значению в мире обычных значений и затем подняли бы результат в мир специальных зна-
чений.
Полное определение классов
На самом деле я немного схитрил. Я рассказал вам только об основных методах классов Applicative
и Monad. Но они содержат ещё несколько дополнительных методов, которые выражаются через остальные.
Посмотрим на них, начнём с класса Applicative.
class Functor f => Applicative f where
-- | Поднимаем значение в мир специальных значений.
pure :: a -> f a
-- | Применение специального значения-функции.
(<*> ) :: f (a -> b) -> f a -> f b
-- | Константная функция. Отбрасываем первое значение.
98 | Глава 6: Функторы и монады: теория
(*> ) :: f a -> f b -> f b
(*> ) = liftA2 (const id)
-- | Константная функция, Отбрасываем второе значение.
(<*) :: f a -> f b -> f a
(<*) = liftA2 const
Два новых метода (*> ) и (<*) имеют смысл константных функций. Первая функция игнорирует значение
слева, а вторая функция игнорирует значение справа. Посмотрим как они работают в интерпретаторе:
Prelude Control.Applicative> Just 2 *> Just 3
Just 3
Prelude Control.Applicative> Nothing *> Just 3
Nothing
Prelude Control.Applicative> (const id) Nothing
Just 3
Just 3
Prelude Control.Applicative> [1,2] <* [1,2,3]
[1,1,1,2,2,2]
Значение игнорируется, но способ комбинирования специальных функций учитывается. Так во втором
выражении не смотря на то, что мы не учитываем конкретное значение Nothing, мы учитываем, что если один
из аргументов частично определённой функции не определён, то не определено всё значение. Сравните с
результатом выполнения следующего выражения.
По той же причине в последнем выражении мы получили три копии первого списка. Так произошло
потому, что второй список содержал три элемента. К каждому из элементов была применена функция const
x, где x пробегает по элементам списка слева от (<*).
Аналогичный метод есть и в классе Monad:
class
Monad m
where
return
:: a -> m a
(>>=)
:: m a -> (a -> m b) -> m b
(>> )
:: m a -> m b -> m b
fail
:: String -> m a
m >> k
= m >>= const k
fail s
= error s
Функция >> в классе Monad, которую мы прятали из-за символа композиции, является аналогом постоян-
ной функции в классе Monad. Она работает так же как и *> . Функция fail используется для служебных нужд
Haskell при выводе ошибок. Поэтому мы её здесь не рассматриваем. Для определения экземпляра класса
Monad достаточно определить методы return и >>=.
Исторические замечания
Напрашивается вопрос. Зачем нам функции return и pure или *> и >> ? Если вы заглянете в документа-
цию к модулю Control.Monad, то там вы найдёте функции liftM, liftM2, liftM3, которые выполняют те же
операции, что и аналогичные функции из модуля Control.Applicative.