haskell-notes

mapK f = sequence . map f

6.4 Функторы и монады

В этой главе мы выписали вручную все определения для класса Kleisli. Мы сделали это потому, что на

самом деле в арсенале стандартных средств Haskell такого класса нет. Класс Kleisli строит замкнутый мир

специальных функций a -> m b. Его цель построить язык в языке и сделать программирование со специ-

альными функциями таким же удобным как и с обычными функциями. Мы пользовались классом Kleisli

исключительно в целях облегчения понимания этого мира. Впрочем никто не мешает нам определить этот

класс и пользоваться им в наших программах.

А пока посмотрим, что есть в Haskell и как это соотносится с тем, что мы уже увидели. С помощью класса

Kleisli

мы научились делать три различных операции применения:

Применение:

• обычных функций одного аргумента к специальным значениям (функция +$).

• обычных функций произвольного числа аргументов к специальным значениям (функции +$ и $$)

• специальных функций к специальным значениям (функция *$).

В Haskell для решения этих задач предназначены три отдельных класса. Это функторы, аппликативные

функторы и монады.

Функторы

Посмотрим на определение класса Functor:

class Functor f where

fmap :: (a -> b) -> f a -> f b

Тип метода fmap совпадает с типом для функции +$:

(+$) :: Kleisli m => (a -> b) -> m a -> m b

Нам только нужно заменить m на f и зависимость от Kleisli на зависимость от Functor:

Итак в Haskell у нас есть базовая операция fmap применения обычной функции к значению из мира спе-

циальных функций. В модуле Control.Applicative определён инфиксный синоним для этой функции.

96 | Глава 6: Функторы и монады: теория

Аппликативные функторы

Посмотрим на определение класса Applicative:

class Functor f => Applicative f where

pure

:: a -> f a

( )

:: f (a -> b) -> f a -> f b

Если присмотреться к типам методов этого класса, то мы заметим, что это наши старые знакомые idK и

$$. Если для данного типа f определён экземпляр класса Applicative, то из контекста следует, что для него

также определён и экземпляр класса Functor.

Значит у нас есть функции fmap (или lift1) и (или $$). С их помощью мы можем составить функции

liftN, которые поднимают обычные функции произвольного числа аргументов в мир специальных значений.

Класс Applicative определён в модуле Control.Applicative, там же мы сможем найти и функции liftA,

liftA2, liftA3 и символьный синоним для функции fmap. Функции liftAn определены так:

liftA2 f a b

= f a b

liftA3 f a b c = f a b c

Видно что эти определения с точностью до обозначений совпадают с теми, что мы уже писали для класса

Kleisli.

Монады

Посмотрим на определение класса Monad

class Monad m where

return :: a -> m a

(>>=)

:: m a -> (a -> m b) -> m b

Присмотримся к типам методов этого класса:

return :: a -> m a

Их типа видно, что это ни что иное как функция idK. В классе Monad у неё точно такой же смысл. Теперь

функция >>=, она читается как функция связывания (bind).

(>>=)

:: m a -> (a -> m b) -> m b

Так возможно совпадение не заметно, но давайте “перевернём” эту функцию:

(=<< )

:: Monad m => (a -> m b) -> m a -> m b

(=<< ) = flip (>>=)

Поменяв аргументы местами, мы получили знакомую функцию *$. Итак функция связывания это функция

применения специальной функции к специальному значению. У неё как раз такой смысл.

В Prelude определены экземпляры класса Monad для типов Maybe и [].

Они определены по такому же принципу, что и наши определения для Kleisli только не для композиции, а

для применения.

Отметим, что в модуле Control.Monad определены функции sequence и mapM, они несут тот же смысл,

что и функции sequence и mapК, которые мы определяли для класса Kleisli.

Свойства классов

Посмотрим на свойства функторов и аппликативных функторов.

Функторы и монады | 97

Свойства класса 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]

Значение игнорируется, но способ комбинирования специальных функций учитывается. Так во втором

Читай продолжение на следующей странице
Добавить комментарии