hli*_*liu 4 haskell reader-monad
在学习Reader Monad时,我发现它被定义为:
newtype Reader r a = Reader { runReader :: r -> a }
instance Monad (Reader r) where
return a = Reader $ \_ -> a
m >>= k = Reader $ \r -> runReader (k (runReader m r)) r
Run Code Online (Sandbox Code Playgroud)
我想知道为什么使用函数作为构造函数参数而不是其他东西,如元组:
newtype Reader r a = Reader { runReader :: (r, a) }
instance Monad (Reader r) where
-- Here I cannot get r when defining return function,
-- so does that's the reason that must using a function whose input is an "r"?
return a = Reader (r_unknown, a)
m >>= k = Reader (fst $ runReader m) (f (snd $ runReader m))
Run Code Online (Sandbox Code Playgroud)
根据Reader定义,我们需要一个"环境",我们可以用它来生成"价值".我认为Reader类型应该包含"environment"和"value"的信息,所以元组看起来很完美.
你没有在问题中提到它,但我想你特意想到使用一对进行定义,Reader因为将其视为提供固定环境的一种方式也是有意义的.假设我们在Readermonad中有更早的结果:
return 2 :: Reader Integer Integer
Run Code Online (Sandbox Code Playgroud)
我们可以使用此结果在固定环境中进行进一步计算(并且Monad方法保证它在整个链中保持固定(>>=)):
GHCi> runReader (return 2 >>= \x -> Reader (\r -> x + r)) 3
5
Run Code Online (Sandbox Code Playgroud)
(如果替换上面的表达式return,(>>=)并runReader在上面的表达式中简化它,您将看到它如何减少到2 + 3.)
现在,让我们按照您的建议并定义:
newtype Env r a = Env { runEnv :: (r, a) }
Run Code Online (Sandbox Code Playgroud)
如果我们有一个类型的环境r和以前的类型结果a,我们可以用Env r a它们来做...
Env (3, 2) :: Env Integer Integer
Run Code Online (Sandbox Code Playgroud)
......我们也可以从中获得新的结果:
GHCi> (\(r, x) -> x + r) . runEnv $ Env (3, 2)
5
Run Code Online (Sandbox Code Playgroud)
那么问题是我们是否可以通过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
Run Code Online (Sandbox Code Playgroud)
该Monoid约束是必须的,从而我们可以使用mempty(这解决了你注意到不必创建一个问题r_unknown无处)和mappend(这使得它能够对在不违反单子的方式的第一要素结合起来法律).Monad然而,这个实例与那个实例有很大的不同Reader.该对的第一个元素不是固定的(它可以改变,因为我们mappend生成其他值),我们不使用它来计算该对的第二个元素(在上面的定义中,y既不依赖于在... r上s.Writer是一个记录器; r这里的值是输出,而不是输入.
然而,有一种方法,你的直觉是合理的:我们不能使用一对像读者一样的monad,但我们可以做一个类似读者的co monad.非常松散地说,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 (>>=)
Run Code Online (Sandbox Code Playgroud)
我们可以给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)
Run Code Online (Sandbox Code Playgroud)
这允许我们2 + 3从一开始就用以下方式编写示例(=>>):
GHCi> runEnv $ Env (3, 2) =>> ((\(r, x) -> x + r) . runEnv)
(3,5)
Run Code Online (Sandbox Code Playgroud)
看到为什么这个工程的一个方法是指出,一个a -> Reader r b功能(即你给Reader的(>>=))基本上是相同的事情,一个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
Run Code Online (Sandbox Code Playgroud)
作为进一步的证据,这里是一个将一个变为另一个的函数:
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
Run Code Online (Sandbox Code Playgroud)
为了总结,这里是一个稍微长一点的例子,并排版本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
Run Code Online (Sandbox Code Playgroud)