您在问题中没有提到它,但我想您特别考虑过使用一对来定义Reader
,因为将其视为提供固定环境的一种方式也是有意义的。假设我们在Reader
monad 中有一个较早的结果:
return 2 :: Reader Integer Integer
我们可以使用这个结果对固定的环境进行进一步的计算(并且Monad
方法保证它在整个链中保持固定(>>=)
):
GHCi> runReader (return 2 >>= \x -> Reader (\r -> x + r)) 3
5
(如果您将return
,(>>=)
和的定义替换为runReader
上面的表达式并简化它,您将确切地看到它是如何简化为 的2 + 3
。)
现在,让我们按照您的建议定义:
newtype Env r a = Env { runEnv :: (r, a) }
如果我们有一个 type 的环境r
和一个 type 的先前结果a
,我们可以利用Env r a
它们...
Env (3, 2) :: Env Integer Integer
...我们也可以从中得到一个新的结果:
GHCi> (\(r, x) -> x + r) . runEnv $ Env (3, 2)
5
那么,问题是我们是否可以通过Monad
接口捕获这种模式。答案是不。虽然有一个对的Monad
实例,但它的作用却完全不同:
newtype Writer r a = Writer { Writer :: (r, a) }
instance Monoid r => Monad (Writer r) where
return x = (mempty, x)
m >>= f = Writer
. (\(r, x) -> (\(s, y) -> (mappend r s, y)) $ f x)
$ runWriter m
需要约束,Monoid
以便我们可以使用mempty
(它解决了您注意到必须突然创建一个的问题r_unknown
)和mappend
(这使得可以以不违反单子的方式组合对的第一个元素法律)。然而,这个Monad
实例所做的事情与这个实例所做的非常不同Reader
。对的第一个元素不是固定的(它可能会发生变化,因为我们mappend
为它生成了其他值)并且我们不使用它来计算对的第二个元素(在上面的定义中,y
两者都不依赖开r
也不开s
)。Writer
是一个记录器;这里的r
值是输出,不是输入。
然而,有一种方法可以证明你的直觉是正确的:我们不能使用一对来制作一个类似阅读器的单子,但我们可以制作一个类似阅读器的单子。简单地说,Comonad
就是将Monad
界面倒置时得到的结果:
-- This is slightly different than what you'll find in Control.Comonad,
-- but it boils down to the same thing.
class Comonad w where
extract :: w a -> a -- compare with return
(=>>) :: w a -> (w a -> b) -> w b -- compare with (>>=)
我们可以给Env
我们已经放弃的一个Comonad
实例:
newtype Env r a = Env { runEnv :: (r, a) }
instance Comonad (Env r) where
extract (Env (_, x)) = x
w@(Env (r, _)) =>> f = Env (r, f w)
这允许我们2 + 3
从头开始编写示例(=>>)
:
GHCi> runEnv $ Env (3, 2) =>> ((\(r, x) -> x + r) . runEnv)
(3,5)
了解它为什么有效的一种方法是注意到一个a -> Reader r b
函数(即你给's 的东西)与一个函数(即你给' Reader
s(>>=)
的东西)本质上是一样的:Env r a -> b
Env
(=>>)
a -> Reader r b
a -> (r -> b) -- Unwrap the Reader result
r -> (a -> b) -- Flip the function
(r, a) -> b -- Uncurry the function
Env r a -> b -- Wrap the argument pair
作为进一步的证据,这里有一个函数可以将一个变为另一个:
GHCi> :t \f -> \w -> (\(r, x) -> runReader (f x) r) $ runEnv w
\f -> \w -> (\(r, x) -> runReader (f x) r) $ runEnv w
:: (t -> Reader r a) -> Env r t -> a
GHCi> -- Or, equivalently:
GHCi> :t \f -> uncurry (flip (runReader . f)) . runEnv
\f -> uncurry (flip (runReader . f)) . runEnv
:: (a -> Reader r c) -> Env r a -> c
总结一下,这里有一个稍长的例子,并列有Reader
和版本:Env
GHCi> :{
GHCi| flip runReader 3 $
GHCi| return 2 >>= \x ->
GHCi| Reader (\r -> x ^ r) >>= \y ->
GHCi| Reader (\r -> y - r)
GHCi| :}
5
GHCi> :{
GHCi| extract $
GHCi| Env (3, 2) =>> (\w ->
GHCi| (\(r, x) -> x ^ r) $ runEnv w) =>> (\z ->
GHCi| (\(r, x) -> x - r) $ runEnv z)
GHCi| :}
5