要为(->) r 定义一个monad,我们需要两个操作,return 和(>>=),遵守三个定律:
instance Monad ((->) r) where
如果我们查看(->) r的return签名
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
使用这个 monad 就像将一个额外的参数 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 组合子。
有很多涉及 (->) 的类的实例,例如,您可以为 (b -> a) 手写 Monoid 的实例,给定 a 上的 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
使用 (->) r 的 Monad 实例。
这里节省的费用很少,但是,例如,#haskell IRC 频道上的 lambdabot 提供的用于生成无点代码的 @pl 工具相当多地滥用这些实例。