要为 定义一个 monad (->) r
,我们需要两个操作return
和(>>=)
,并遵守三个定律:
instance Monad ((->) r) where
如果我们看一下退货的签名(->) r
return :: a -> r -> a
我们可以看到它只是一个常量函数,它忽略了它的第二个参数。
return a r = a
或者,
return = const
要构建(>>=)
,如果我们用 monad 专门化它的类型签名(->) r
,
(>>=) :: (r -> a) -> (a -> r -> b) -> r -> b
实际上只有一种可能的定义。
(>>=) x y z = y (x z) z
r
使用这个 monad 就像向每个函数传递一个额外的参数。您可以将其用于配置,或者将选项传递到程序的深处。
我们可以通过验证三个单子定律来检查它是否是单子:
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组合子。
有很多涉及 (->) 的类的实例,例如,你可以为 (b -> a)手写Monoida
的实例,给定一个 Monoid为:
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
.
在这里节省的成本很少,但是,例如,lambdabot 在#haskell IRC 频道上提供的用于生成无点代码的@pl 工具会滥用这些实例。