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