为什么要将Reader的构造函数参数定义为函数? [英] Why to define the constructor parameter of Reader as a function?
问题描述
newtype Reader ra = Reader {runReader :: r - > a}
实例Monad(Reader r)其中
返回a = Reader $ \_ - > a
m>> = k = Reader $ \\ r - > runReader(k(runReader mr))r
我想知道为什么使用函数作为构造函数参数,而不是其他元素如元组:
newtype Reader ra = Reader {runReader ::(r,a)}
实例Monad(Reader r)其中
- 在定义返回函数
时,我无法得到r - 所以这就是必须使用输入为r的函数的原因?
返回一个=读取器(r_unknown,一)
M>> = K =阅读器(FST $ runReader米)(F(SND $ runReader米))
根据Reader定义,我们需要一个环境,我们可以使用它来生成一个值。我认为一个Reader类型应该包含environment和value的信息,所以这个元组看起来很完美。
在这个问题中没有提到它,但我想你特别想到使用一对来定义 Reader
,因为它也可以作为提供固定的环境。假设我们在 Reader
monad中有一个较早的结果:
return 2 :: Reader Integer Integer
我们可以使用这个结果来进一步计算固定环境(和 Monad
方法保证它在整个(>> =)
)链中保持固定:
GHCi> runReader(return 2>> = \ x - > Reader(\ r - > x + r))3
$ p $ (如果用
5
return
,>(>> =) code>和
runReader
,并简化它,你将会看到它是如何减少到2 + 3 $ c $ )
现在,让我们按照您的建议进行定义:
newtype Env ra = Env {runEnv ::(r,a)}
如果我们有
r
类型的环境和之前的类型为a
的结果,我们可以创建一个Env ra
Env(3,2):: Env Integer Integer
...我们也可以从中得到一个新的结果:
GHCi> (\(r,x)→x + r)。 runEnv $ Env(3,2)
5
问题在于我们可以通过
Monad
界面捕获此模式。答案是不。虽然 是一对Monad
对的实例,但它完全不同:newtype Writer ra = Writer {Writer ::(r,a)}
实例Monoid r => Monad(Writer r)其中
返回x =(mempty,x)
m>> = f = Writer
。 (\(r,x) - >(\(s,y) - >(mappend rs,y))$ fx)
$ runWriter m
需要
Monoid
约束,以便我们可以使用mempty
(它解决了你注意到不得不从不知名的地方创建
r_unknown
的问题)和mappend
(这样可以以不违反单子法的方式将这一对中的第一个元素组合起来)。然而,这个Monad
实例的功能与Reader
的功能完全不同。这一对中的第一个元素不是固定的(它可能会改变,因为我们mappend
其他生成的值),我们不用它来计算第二个元素(在上面的定义中,y
)既不依赖于r
也不依赖于取值
)。Writer
是一个记录器;这里的r
值是输出,而不是输入。
然而,其中一种方法是,你的直觉是合理的:我们不能使用一对读者般的monad,但我们可以让读者像monad一样。把它非常松散,
Comonad
是将Monad
界面颠倒时得到的结果:
- 这与您在Control.Comonad中找到的有点不同,
- 但它归结为同样的事情。
class Comonad w where
extract :: w a - > a - 与回报
(=>>):: w a - > (w a - > b) - >我们可以给> Env
我们放弃了Comonad
实例:
实例Comonad(Env r)其中
提取(Env(_,x))= x
w @(Env(r,_))=>> f = Env(r,fw)
允许我们写
2 + 3
从开始的例子(=>>)
:GHCI> runEnv $ Env(3,2)=>> ((\(r,x)→x + r)。runEnv)
(3,5)
了解这项工作原因的一种方法是注意
a - > Reader rb
函数(即你给读者
的(>> =)
)基本上与Env ra - > b
1(即你给Env
的(=>>)
):
a - >阅读器r b
a - > (r - > b) - 解开读取结果
r - > (a - > b) - 翻转函数
(r,a) - > b - 解除功能
Env r a - > b - 包装参数对
作为进一步的证据,这里有一个函数将一个变成其他:
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> - 或者等价地:
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 |阅读器(\ r→> x ^ r)>> = \ y - >
GHCi |读者(\r - > y - r)
GHCi | :}
5
GHCi> :{
GHCi |提取$
GHCi | Env(3,2)=>> (\ w - >
GHCi |(\(r,x) - > x ^ r)$ runEnv w)=>> (\ z - >
GHCi |(\(r,x) - > x -r)$ runEnv z)
GHCi | :}
5
When learning the Reader Monad, I find that it is defined as:
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
I want to known why using function as constructor parameter instead of something else such as a tuple:
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))
According to the Reader definition, we need a "environment" which we can use to generate a "value". I think a Reader type should contain the information of "environment" and "value", so the tuple seems perfect.
解决方案You didn't mention it in the question, but I guess you thought specifically of using a pair for defining
Reader
because it also makes sense to think of that as a way of providing a fixed environment. Let's say we have an earlier result in theReader
monad:return 2 :: Reader Integer Integer
We can use this result to do further calculations with the fixed environment (and the
Monad
methods guarantee it remains fixed throughout the chain of(>>=)
):GHCi> runReader (return 2 >>= \x -> Reader (\r -> x + r)) 3 5
(If you substitute the definitions of
return
,(>>=)
andrunReader
in the expression above and simplify it, you will see exactly how it reduces to2 + 3
.)Now, let's follow your suggestion and define:
newtype Env r a = Env { runEnv :: (r, a) }
If we have an environment of type
r
and a previous result of typea
, we can make anEnv r a
out of them...Env (3, 2) :: Env Integer Integer
... and we can also get a new result from that:
GHCi> (\(r, x) -> x + r) . runEnv $ Env (3, 2) 5
The question, then, is whether we can capture this pattern through the
Monad
interface. The answer is no. While there is aMonad
instance for pairs, it does something quite different: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
The
Monoid
constraint is needed so that we can usemempty
(which solves the problem that you noticed of having to create ar_unknown
out of nowhere) andmappend
(which makes it possible to combine the first elements of the pair in a way that doesn't violate the monad laws). ThisMonad
instance, however, does something very different than what theReader
one does. The first element of the pair isn't fixed (it is subject to change, as wemappend
other generated values to it) and we don't use it to compute the second element of the pair (in the definition above,y
does not depend neither onr
nor ons
).Writer
is a logger; ther
values here are output, not input.
There is one way, however, in which your intuition is justified: we can't make a reader-like monad using a pair, but we can make a reader-like comonad. To put it very loosely,
Comonad
is what you get when you turn theMonad
interface upside down:-- 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 (>>=)
We can give the
Env
we had abandoned aComonad
instance: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)
That allows us to write the
2 + 3
example from the beginning in terms of(=>>)
:GHCi> runEnv $ Env (3, 2) =>> ((\(r, x) -> x + r) . runEnv) (3,5)
One way to see why this works is noting that an
a -> Reader r b
function (i.e. what you give toReader
's(>>=)
) is essentially the same thing that anEnv r a -> b
one (i.e. what you give toEnv
's(=>>)
):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
As further evidence of that, here is a function that changes one into the other:
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
To wrap things up, here is a slightly longer example, with
Reader
andEnv
versions side-by-side: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
这篇关于为什么要将Reader的构造函数参数定义为函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!