Как использовать (->) экземпляры Monad и путаница с (->) - PullRequest
35 голосов
/ 15 марта 2011

На разные вопросы я обнаружил подсказки в комментариях относительно использования экземпляра (->) Monads, например. для реализации бессмысленного стиля.

Что касается меня, это слишком абстрактно. Хорошо, я видел экземпляры Arrow на (->), и мне кажется, что (->) можно использовать в нотациях экземпляров, но не в объявлениях типов (это само по себе будет проблемой для другого вопроса).

Кто-нибудь примеры использования (->) в качестве экземпляра Monad? Или хорошая ссылка?

Извините, если этот вопрос уже обсуждался здесь, но поиск "(->) Экземпляр монады" дает вам много совпадений, как вы можете себе представить ... поскольку почти каждый вопрос о Haskell где-то связан с (->) или "Монада".

Ответы [ 2 ]

31 голосов
/ 15 марта 2011

Для данного типа r функцию типа r -> a можно рассматривать как вычисление, обеспечивающее a с использованием среды, типизированной r.Учитывая две функции r -> a и a -> (r -> b), легко представить, что их можно составить, когда дано окружение (опять же, типа r).

Но подождите!Это именно то, о чем говорят монады!

Таким образом, мы можем создать экземпляр Monad для (->) r, который реализует f >>= g, передав r обоим f и g.Это то, что делает экземпляр Monad для (->) r.

Чтобы реально получить доступ к среде, вы можете использовать id :: r -> r, который теперь можно рассматривать как вычисление, выполняемое в среде r и предоставляющееr.Для создания локальных подпространств вы можете использовать следующее:

inLocalEnvironment :: (r -> r) -> (r -> a) -> (r -> a)
inLocalEnvironment xform f = \env -> f (xform env)

Этот шаблон передачи среды в вычисления, которые могут затем запрашивать ее и изменять ее локально, полезен не только для монады (->) rВот почему он абстрагируется в класс MonadReader, используя гораздо более разумные имена, чем те, что я использовал здесь:

http://hackage.haskell.org/packages/archive/mtl/2.0.1.0/doc/html/Control-Monad-Reader-Class.html

По сути, он имеет два экземпляра: (->) r, который мы видели здесь, и ReaderT r m, который является просто newtype оберткой вокруг r -> m a, так что это то же самое, что и монада (->) r, которую я описал здесь, за исключением того, что он выполняет вычисления в некоторыхдругая, преобразованная монада.

26 голосов
/ 15 марта 2011

Чтобы определить монаду для (->) r, нам нужны две операции return и (>>=), подчиняющиеся трем законам:

instance Monad ((->) r) where

Если мы посмотрим на сигнатуру возврата для (->) r

    return :: a -> r -> a

мы можем видеть только его постоянную функцию, которая игнорирует второй аргумент.

    return a r = a

Или, альтернативно,

    return = const

Для построения (>>=), если мы специализируем его сигнатуру типа с монадой (->) r,

    (>>=) :: (r -> a) -> (a -> r -> b) -> r -> b

, то на самом деле существует только одно возможное определение.

    (>>=) x y z = y (x z) z

Использование этой монады похоже на прохождение дополнительнойаргумент r для каждой функции.Вы можете использовать это для конфигурации или для передачи параметров глубоко в недра вашей программы.

Мы можем проверить, что это монада, проверив три закона монады:

1. return a >>= f = f a 

return a >>= f 
= (\b -> a) >>= f -- by definition of return
= (\x y z -> y (x z) z) (\b -> a) f -- by definition of (>>=)
= (\y z -> y ((\b -> a) z) z) f -- beta reduction
= (\z -> f ((\b -> a) z) z) -- beta reduction
= (\z -> f a z) -- beta reduction
= f a -- eta reduction

2. m >>= return = m

m >>= return
= (\x y z -> y (x z) z) m return -- definition of (>>=)
= (\y z -> y (m z) z) return -- beta reduction
= (\z -> return (m z) z) -- beta reduction
= (\z -> const (m z) z) -- definition of return
= (\z -> m z) -- definition of const
= m -- eta reduction

Окончательный закон монады:

3. (m >>= f) >>= g  ≡  m >>= (\x -> f x >>= g)

следует аналогичным, простым эквациональным рассуждениям.

Мы также можем определить ряд других классов для ((->) r), например,как Functor,

instance Functor ((->) r) where

и если мы посмотрим на сигнатуру

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

, мы увидим, что это просто композиция!

   fmap = (.)

Аналогично мы можем сделатьэкземпляр Applicative

instance Applicative ((->) r) where
   -- pure :: a -> r -> a
   pure = const

   -- (<*>) :: (r -> a -> b) -> (r -> a) -> r -> b
   (<*>) g f r = g r (f r)

Что хорошо в этих экземплярах, так это то, что они позволяют вам использовать все комбинаторы Monad и Applicative при манипулировании функциями.

Существует множество экземпляров классов, включающих (->), например, вы можете написать вручную экземпляр Monoid для (b -> a), если для него установлен Monoid a as:

enter code here
instance Monoid a => Monoid (b -> a) where
    -- mempty :: Monoid a => b -> a
    mempty _ = mempty
    -- mappend :: Monoid a => (b -> a) -> (b -> a) -> b -> a
    mappend f g b = f b `mappend` g b

, но с учетом экземпляра Monad / Applicative вы также можете определитьЭтот экземпляр с

instance Monoid a => Monoid (r -> a) where
    mempty = pure mempty
    mappend = liftA2 mappend

с использованием экземпляра Applicative для (->) r или с

instance Monoid a => Monoid (r -> a) where
    mempty = return mempty
    mappend = liftM2 mappend

с использованием экземпляра Monad для (->) r.

Здесь экономияминимальны, но, например, инструмент @pl для генерации бессмысленного кода, который предоставляется lambdabot на IRC-канале #haskell, злоупотребляет этими случаями совсем немного.

...