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

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

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

Есть ли примеры использования (->) как пример монады? Или хорошая ссылка?

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

2 ответа

Решение

Для данного типа 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 Монада, которую я описал здесь, за исключением того, что она предоставляет вычисления в какой-то другой, преобразованной монаде.

Определить монаду для (->) 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 как:

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, злоупотребляет этими случаями довольно сильно.

Другие вопросы по тегам