- Введение в класс типов Functor
- Операторы функции fmap: <$> и <$
- Реализация представителя Functor для двухпараметрических типов
- Законы для функторов
- Упаковка возвращаемого значения в однопараметрический контейнер
- Класс типов Monad и стрелка Клейсли
- Оператор монадического связывания - bind
- Интерфейс классов типов Monad и вспомогательные функции
- Реализация представителя класса типов Monad на примере контейнера Identity
- Законы для монад
Показаны сообщения с ярлыком Функциональное программирование на языке Haskell. Показать все сообщения
Показаны сообщения с ярлыком Функциональное программирование на языке Haskell. Показать все сообщения
Функциональное программирование на языке Haskell
Законы для монад
Чтобы монада работала как следует, чтобы она позволяла организовать цепочки вычислений требуется чтобы представитель класса Monad удовлетворял определенным законам.
Реализация представителя класса типов Monad на примере контейнера Identity
Identity представляет собой коробочку в которую упаковывают любой другой произвольный тип. Identity имеет всего один конструктор поэтому удобно определить ее с помощью ключевого слова newtype.
runIdentity - это метка доступа, которая является акцессором для значения a.
Для того чтобы сделать Identity монадой надо реализовать две функции return и bind.
Интерфейс классов типов Monad и вспомогательные функции
bind связывает нам два монадических вычисления, а облегченный связыватель (>>), говорит, что он из a вынет значение, но никуда не передаст. Фактически эта функция очень похожа на flip const - первый аргумент игнорируется, а второй просто возвращается.
При наступлении некоторых событий мы не можем продолжить монадическое вычисление и тогда автоматически вызывается функция fail.
Оператор монадического связывания - bind
Этот оператор настолько важен, что он присутствует в логотипе языка Haskell.
Оператор bind это функция двух аргументов:
Оператор bind это функция двух аргументов:
- аргумент a упакован в монадический контейнер;
- стрелка Клейсли из a в m b.
И возвращает нам в качестве значения монадический контейнер, только в него упаковано значение b.
Идея оператора bind заключается в том, что он должен для каждой конкретной монады вынуть из монадического контейнера значение a и передать его в стрелку Клейсли и вернуть ее значение. Оператор монадического связывания он как раз связывает два монадических вычисления.
Оператор монадического связывание имеет низкий первый приоритет и левую ассоциативность.
Оператор доллар просто брал функцию и аргумент и применял функцию к аргументу:
Он был правоассоциативен и имел низкий приоритет.
Функция & повторяет оператор $ но просто с переставленными аргументами:
Давайте посмотрим как эти цепочки работают на некоторых простых примерах:
Prelude> (+1) $ (*3) $ (+2) $ 5
22
Prelude> 5 & (+2) & (*3) & (+1)
Идея оператора bind заключается в том, что он должен для каждой конкретной монады вынуть из монадического контейнера значение a и передать его в стрелку Клейсли и вернуть ее значение. Оператор монадического связывания он как раз связывает два монадических вычисления.
Оператор монадического связывание имеет низкий первый приоритет и левую ассоциативность.
Оператор доллар просто брал функцию и аргумент и применял функцию к аргументу:
Он был правоассоциативен и имел низкий приоритет.
Функция & повторяет оператор $ но просто с переставленными аргументами:
Давайте посмотрим как эти цепочки работают на некоторых простых примерах:
Prelude> (+1) $ (*3) $ (+2) $ 5
22
Prelude> 5 & (+2) & (*3) & (+1)
22
Если стереть буковку m, то наш bind работает точно также как и &.
С другой стороны оператор монадического связывания bind похож на функцию fmap с переставленными аргументами:
Prelude> :t fmap
fmap :: Functor f => (a -> b) -> f a -> f b
Prelude> :t flip fmap
flip fmap :: Functor f => f a -> (a -> b) -> f b
Каждая монада является функтором. В случае fmap наша функция не меняет структуру контейнера. А оператор монадического связывания вполне может изменить структуру контейнера. Потому что функция (a -> m b) знает что-то о контейнере и для каждого контейнера она своя и соответственно она может выполнять гораздо более глубокие изменения внутри контейнерного типа нежели fmap.
Законы для функторов
Prelude> fmap id [1,2,3]
[1,2,3]
Prelude> fmap id (Just 42)
Just 42
Prelude> (fmap (+1) . fmap (^2)) [1,2,3]
[2,5,10]
Prelude> fmap ((+1) . (^2)) [1,2,3]
[2,5,10]
Реализация представителя Functor для двухпараметрических типов
Двухпараметрические типы: Either, пара. Они требуют двух типовых параметров.
Класс типов Functor в качестве своего типового параметра требует переменной, которая имеет один типовой параметр. Это проблема решается следующим образом, можно взять и связать один из этих параметров каким-то значением и после этого наш тип становится однопараметрическим.
Например тип Either Int это тип у которого один параметр. В левой ветви у него содержится Int, а в правой что-то с типом параметра.
Операторы функции fmap
У функции fmap есть оператор <$> - он вместе с применением функции поднимает ее в некоторый целевой контейнер.
Prelude> (*2) <$> [1,2,3]
[2,4,6]
Этот оператор имеет левую ассоциативность и 4 приоритет:
Prelude> :i <$>
(<$>) :: Functor f => (a -> b) -> f a -> f b
-- Defined in ‘Data.Functor’
infixl 4 <$>
Левая ассоциативность нужна чтобы организовывать цепочки применения этого оператора:
Prelude> (*2) <$> [1,2,3]
[2,4,6]
Prelude> (+5) <$> (*2) <$> [1,2,3]
[7,9,11]
В модуле Data.Functor определен еще один оператор <$ - он имеет вспомогательное значение:
Prelude> :i <$
class Functor (f :: * -> *) where
...
(<$) :: a -> f b -> f a
-- Defined in ‘GHC.Base’
infixl 4 <$
Этот оператор затирает значение хранящееся в контейнере некоторым единственным значением a:
Prelude> (30) <$ [1,2,3]
[30,30,30]
Главное требование к представителю класса Functor заключается в том, что структура контейнера не должна меняться.
Введение в класс типов Functor
Помимо простых типов данных есть более сложные конструкции: функции над типами или конструкторы типов с параметрами.
Подписаться на:
Сообщения (Atom)







