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