Как использовать (->) экземпляры Monad и путаницу о (->)
В разных вопросах я нашел подсказки в комментариях относительно использования экземпляра (->)
Monads, например. для реализации точечного стиля.
Что касается меня, это немного абстрактно. Хорошо, я видел экземпляры Arrow на (->)
, и мне кажется, что (->)
может использоваться в нотации экземпляра, но не в объявлениях типа (это было бы только вещество для другого вопроса).
Есть ли примеры с использованием (->)
в качестве экземпляра Monad? Или хорошая ссылка?
Извините, если этот вопрос, возможно, уже обсуждался здесь, но поиск "(->)
Monad instance" дает вам много хитов, как вы можете себе представить... поскольку почти каждый вопрос о Haskell где-то включает (->)
или "Monad".
Ответы
Ответ 1
Для заданного типа 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
monad я описанный здесь, за исключением того, что он выполняет вычисления в другой, преобразованной монаде.
Ответ 2
Чтобы определить монаду для (->) 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), учитывая моноид на 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
используя аппликативный экземпляр для (->) r
или
instance Monoid a => Monoid (r -> a) where
mempty = return mempty
mappend = liftM2 mappend
используя экземпляр Monad для (->) r
.
Здесь сбережения минимальны, но, например, инструмент @pl для создания точечного кода, который предоставляется lambdabot на IRC-канале #haskell, довольно редко злоупотребляет этими экземплярами.