Свойства класса 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 aliftA3 (.
) f g x :: m cg <*>
x::
m bf (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 3Just
3Prelude Control.Applicative> Nothing *> Just
3Nothing
Prelude Control.Applicative>
(const id) NothingJust
3Just
3Prelude Control.Applicative>
[1,2] <* [1,2,3][1,1,1,2,2,2]
Значение игнорируется, но способ комбинирования специальных функций учитывается. Так во втором
выражении не смотря на то, что мы не учитываем конкретное значение Nothing
, мы учитываем, что если одиниз аргументов частично определённой функции не определён, то не определено всё значение. Сравните с
результатом выполнения следующего выражения.
По той же причине в последнем выражении мы получили три копии первого списка. Так произошло
потому, что второй список содержал три элемента. К каждому из элементов была применена функция const
x, где x пробегает по элементам списка слева от (<*
).Аналогичный метод есть и в классе Monad
:class
Monad
mwhere
return
::
a -> m a(>>=
)::
m a -> (a -> m b) -> m b(>>
)::
m a -> m b -> m bfail
:: String ->
m am >>
k=
m >>= const kfail s
= error
sФункция >>
в классе Monad, которую мы прятали из-за символа композиции, является аналогом постоян-ной функции в классе Monad
. Она работает так же как и *> . Функция fail используется для служебных нуждHaskell при выводе ошибок. Поэтому мы её здесь не рассматриваем. Для определения экземпляра класса
Monad
достаточно определить методы return и >>=.Исторические замечания
Напрашивается вопрос. Зачем нам функции return и pure или *>
и >> ? Если вы заглянете в документа-цию к модулю Control.Monad
, то там вы найдёте функции liftM, liftM2, liftM3, которые выполняют те жеоперации, что и аналогичные функции из модуля Control.Applicative
.