为什么要将Reader的构造函数参数定义为函数? [英] Why to define the constructor parameter of Reader as a function?

查看:151
本文介绍了为什么要将Reader的构造函数参数定义为函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在学习Reader Monad时,我发现它被定义为:

  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 
5
return ,>(>> =) code>和 runReader ,并简化它,你将会看到它是如何减少到 2 + 3

现在,让我们按照您的建议进行定义:

  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 the Reader 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, (>>=) and runReader in the expression above and simplify it, you will see exactly how it reduces to 2 + 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 type a, we can make an Env 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 a Monad 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 use mempty (which solves the problem that you noticed of having to create a r_unknown out of nowhere) and mappend (which makes it possible to combine the first elements of the pair in a way that doesn't violate the monad laws). This Monad instance, however, does something very different than what the Reader one does. The first element of the pair isn't fixed (it is subject to change, as we mappend 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 on r nor on s). Writer is a logger; the r 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 the Monad 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 a Comonad 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 to Reader's (>>=)) is essentially the same thing that an Env r a -> b one (i.e. what you give to Env'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 and Env 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屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆