简体   繁体   English

阅读器monad,如何绑定阅读器?

[英]Reader monad, how bind readers works?

I'm a beginner in Haskell. 我是Haskell的初学者。 I learned How to create a Reader and How to query a shared variables. 我学习了如何创建阅读器以及如何查询共享变量。 I looked into the source code of Reader.hs in Hugs98 我在Hugs98中研究了Reader.hs的源代码

instance Monad (Reader r) where
return a = Reader $ \_ -> a

m >>= k  = Reader $ \r -> 
                  runReader (k (runReader m r)) r

Here I can see (return a) creates a Reader wrapping a function that takes a value and return a 在这里,我可以看到(返回a)创建了一个读取器,该读取器包装了一个接受值的函数并返回一个

m >>= k is what I can't understand. m >> = k是我无法理解的。 first how is that can be applied ? 首先如何应用? maybe example of two readers binded can help ? 也许将两个读者绑定在一起的示例可以提供帮助吗?

secondly the implementation is some how vague for me I don't understand the point of applying k to the result of (runReader mr) ? 其次,对我来说,实现有些模糊,我不明白将k应用于(runReader mr)结果的意义?

Thanks 谢谢

Reader is defined as: 读者定义为:

newtype Reader r a = Reader { runReader :: r -> a }

So it's really just a function of type r -> a with some extra encapsulation. 因此,它实际上只是类型r -> a的函数,带有一些额外的封装。 This makes sense, as the Reader really just provides an extra input to all the actions in the monad. 这是有道理的,因为Reader实际上只是为monad中的所有动作提供了额外的输入。

If we strip the encapsulation and only use the r -> a function, the types of the monadic functions are: 如果我们剥离封装并仅使用r -> a函数,则单子函数的类型为:

return :: a -> (r -> a) -- or: a -> r -> a
(>>=) :: (r -> a) -> (a -> (r -> b)) -> (r -> b) -- or: (r -> a) -> (a -> r -> b) -> r -> b

Looking at this it is a lot easier to see what is required of us. 看到这一点,很容易看出我们的要求。 If you look at the type a -> (r -> a) and see that this is equivalent to a -> r -> a , you can see that you can look at this function in two ways. 如果查看类型a -> (r -> a)并发现它等效于a -> r -> a ,则可以看到可以通过两种方式查看此函数。 One is that you take an argument of a and return a function of type r -> a , the other is to look at is as a function that takes an a and an r and returns an a . 一个是你拿的一个参数a和返回类型的函数r -> a ,另一种是在IS看成是需要一个功能ar并返回a You can implement return using either of these views: 您可以使用以下任一视图来实现return:

return a = \r -> a -- or: return a r = a

The bind is trickier, but the same logic applies. 绑定比较棘手,但是适用相同的逻辑。 In the first type signature I gave, it is not immediately evident that the third r in the type is actually also an input, while the second type signature makes this very easy to see. 在我给出的第一个类型签名中,并不能立即证明该类型中的第三个r实际上也是一个输入,而第二个类型签名使这一点很容易看到。 So let's start with implementing the second type signature: 因此,让我们从实现第二种类型签名开始:

(>>=) rToA aAndRToB r = ...

So we have a value of type r , a function of type r -> a and a function of type a -> r -> b and our goal is to make a value of type b out of that. 因此,我们有一个类型r的值,一个类型r -> a的函数和一个类型a -> r -> b的函数,我们的目标是从中得到类型b的值。 The only b in our input is in the a -> r -> b function so we will need to use that, but we do not have an a to feed it, so we need to get one. 输入中唯一的b在a- a -> r -> b函数中,因此我们将需要使用它,但是我们没有a来提供它,因此我们需要得到一个。 The r -> a function can provide one, if we have an r for it. r -> a函数可以提供一个,如果我们有一个r的话。 We do have an r , it's our third input. 我们确实有一个r ,这是我们的第三个输入。 So we can simply apply the functions until we get our b : 因此,我们可以简单地应用这些函数,直到获得b为止:

(>>=) rToA aAndRToB r = b where
  a = rToA r
  b = aAndRToB a r

Here you can see that we provide the r -value to every action (which is the goal of the Reader monad) while also chaining the a -value from one action to the next (which is the goal of (>>=) ). 在这里您可以看到,我们为每个动作提供r值(这是Reader单子的目标),同时还将a值从一个动作链接到下一个动作(即(>>=)的目标)。 You can also write this in a way that mimics the first type signature like this: 您还可以采用类似于第一类型签名的方式编写此代码,如下所示:

(>>=) rToA aToRToB = \r -> (aToRToB (rToA r)) r

which if you rename the variables looks very similar to the definition of Reader's bind, but without using Reader and runReader : 如果重命名变量,则该变量看起来与Reader的绑定的定义非常相似,但不使用ReaderrunReader

m >>= k = /r -> k (m r) r

>>= aka bind has the signature: ma -> (a -> mb) -> mb . >>= aka bind具有签名: ma -> (a -> mb) -> mb If we try to make this signature specific to Reader ie replacing m with Reader r we will find that it becomes: 如果我们尝试使此签名特定于Reader,即用Reader r替换m ,我们将发现它变为:

(Reader r) a -> (a -> (Reader r) b) -> (Reader r) b

Which is same as: 与以下内容相同:

Reader ra -> (a -> Reader rb) -> Reader rb

Now we need to write such a function: 现在我们需要编写这样的函数:

(>>=) mk = ... where m is Reader ra and k is (a -> Reader rb) and we need to return Reader rb (>>=) mk = ...其中mReader rak(a -> Reader rb) ,我们需要返回Reader rb

How can you create Reader rb as this is something that we have to return ? 您如何创建Reader rb因为这是我们必须返回的内容? Well, k is a function that will allow you to create Reader rb but k need some value of type a to return Reader rb . 好吧, k是一个允许您创建Reader rb的函数,但k需要某种类型a值才能返回Reader rb

How we can get a value of type a (so that we can use function k ) ? 我们如何获得类型a的值(以便我们可以使用函数k )? Looks like m parameter which is of type Reader ra can help us to get a value of type a . 看起来像类型为Reader ra m参数可以帮助我们获取类型a a的值。

How do we get value of a from Reader ra ? 我们如何获得的价值aReader ra runReader has a type Reader ra -> r -> a , so if we call runReader on m we will get (r -> a) but we were looking for value of a type and what we got is (r -> a) and it seems we don't have any r value to get a . runReader有型Reader ra -> r -> a ,所以如果我们调用runReaderm ,我们会得到(r -> a)但我们正在寻找的价值a类型,而我们得到的(r -> a)和看来我们没有任何r值来得到a It seems we are stuck as we don't have any other parameter to look for. 似乎我们被困住了,因为我们没有其他要查找的参数。

Let's assume we somehow has some r value (called r_val ) so that we can do: 假设我们以某种方式具有r值(称为r_val ),以便我们可以这样做:

let a_val = runReader m r_val gives us value of type a . let a_val = runReader m r_val给出类型a值。

from a we need to get Reader rb using k a我们需要使用k获取Reader rb

let reader_r_b_val = k a_val gives us value of type Reader rb and that's it, we got what we need to return, lets combine all the above 2 lets: let reader_r_b_val = k a_val给我们提供Reader rb类型的值,就是这样,我们得到了我们需要返回的值,让上述所有两个let结合起来:

k (runReader m r_val) which is Reader rb BUT we are not done yet, we need to do something of r_val which was just a placeholder. k (runReader m r_val)Reader rb但是我们还没有完成,我们需要做一些r_val只是一个占位符。 Lets say we take r_val as param 假设我们将r_val作为参数

\\r_val -> k (runReader m r_val) which is of type r -> Reader rb ... Hmmm but we only need to return Reader rb .. can we somehow wrap r -> Reader rb into a Reader rb ? \\r_val -> k (runReader m r_val)的类型为r -> Reader rb ...嗯,但是我们只需要返回Reader rb ..我们能以某种方式将r -> Reader rb包装到Reader rb吗?

Reader $ (\\r_val -> k (runReader m r_val)) has a type Reader r (Reader rb) .. looks like we are almost there we just need to convert Reader r (Reader rb) to Reader rb ie we need the to convert inner Reader rb to just b and for that we can use runReader so: Reader $ (\\r_val -> k (runReader m r_val))的类型为Reader r (Reader rb) ..看起来我们几乎到了,我们只需要将Reader r (Reader rb)转换为Reader rb即我们需要将内部Reader rb转换为b ,为此我们可以使用runReader这样:

Reader $ (\\r_val -> runReader (k (runReader m r_val)) r_val)

OK, so let's look at m >>= k . 好,让我们看一下m >>= k Here m is a reader, and k is a function that produces a reader. 这里, m是阅读器, k是产生阅读器的函数 So what does this do? 那么,这是做什么的呢?

runReader m r

OK, so this is running m with r as the input to be read. 好的,因此正在以r作为要读取的输入运行m

k (runReader m r)

This takes the output from running m and passes it to k . 这将从运行m获得输出,并将其传递给k This makes k return another reader. 这使k返回另一个读者。

runReader (k (runReader m r)) r

This takes the reader returned by k and runs that (with the same input r for reading). 这将使读取器返回k并运行该读取器(使用相同的输入r进行读取)。

You follow all that? 你遵循所有这些吗?

First, what the Reader is for. 首先,读者的目的是什么。

Consider that you have a pure function fxy, and a pure function g y. 考虑您有一个纯函数fxy和一个纯函数g y。 Now you figure out that g needs to use f inside, but it only has one argument that it can supply f with! 现在您知道g需要在内部使用f,但是它只有一个参数可以为f提供! A typical solution is one of the following: 典型的解决方案是以下之一:

  1. modify API so that g now has two arguments, both x and y (and the caller now has to compute it, even if g doesn't call f); 修改API,以便g现在有两个参数x和y(即使g不调用f,调用者现在也必须计算它);

  2. create a global variable that g will read to provide f with that argument (g is no longer pure); 创建一个全局变量,g将读取该变量以为f提供该参数(g不再是纯变量);

  3. create a global variable that f will read (f is no longer pure). 创建一个全局变量,f将读取(f不再是纯变量)。

Familiar? 熟悉吗? The latter two solutions are probably the most common, but they are ugly. 后两种解决方案可能是最常见的,但它们很难看。 The first solution requires a uniform way for the caller to interact with g, that's the difficulty. 第一种解决方案要求调用者与g进行交互的统一方式,这就是困难。 By wrapping g in a Reader monad, we provide such interface: the caller h :: a -> b either knows how to compute x and provide it (runReader (gy) x), or the caller can wrap itself into a Reader too, and delegate the computation of x to its caller (turns into h :: a -> Reader xb). 通过将g包裹在Reader monad中,我们提供了这样的接口:调用者h :: a-> b要么知道如何计算x并提供x(runReader(gy)x),要么调用者也可以将自身包装到Reader中,并将x的计算委托给其调用者(变成h :: a-> Reader xb)。

In essence, solution 1, introducing the extra argument in function g, means its signature is g :: y -> x -> z, which is a function g :: y -> (x -> z). 本质上,解决方案1在函数g中引入了额外的参数,这意味着其签名为g :: y-> x-> z,这是函数g :: y->(x-> z)。 Reader monad permits to abstract away the (x -> z) part, so you have g :: y -> Reader x z. Reader monad允许抽象出(x-> z)部分,因此您具有g :: y-> Reader x z。 The abstraction permits to bind functions that need x or pass x along to others in a uniform way . 抽象允许绑定需要x的函数或以统一的方式将x传递给其他函数。

Without Reader monad: 如果没有Reader monad:

h :: x -> z
h = \x -> g y x -- caller doesn't know how to compute x
  where y = .... -- some computation that h knows how to do

g y = \x -> f x y

(h could be written neater as hx = ..., but I deliberately express it as a lambda, so it will be easier to compare to below): (h可以写成更整洁的hx = ...,但我故意将其表示为lambda,因此与下面的内容进行比较会比较容易):

This is the same as: 这与:

h :: Reader x z
h = Reader $ \x -> g y x
  where y = ...

g :: y -> x -> z
g y = \x -> f x y

With Reader monad: 使用Reader monad:

h :: Reader x z
h = g y
  where y = ...

g :: y -> Reader x z
g y = Reader $ \x -> f x y

Tidy up: 整理:

h :: Reader x z
h = g y
  where y = ...

g :: y -> Reader x z
g y = do
        x <- ask
        return $ f x y

Now to (>>=). 现在到(>> =)。

(Reader f) >>= g = Reader $ \x -> -- this is the x we are given, 
                                  -- so need to pass it to f and g y
                     case g (f x) of -- g y is Reader x z,
                                     -- so need to call the wrapped x -> z 
                         Reader g' -> g' x

The pattern matching above is the same as: 上面的模式匹配与:

m >>= g = Reader $ \x -> runReader (g (runReader m x)) x

Let's derive the specific type scheme for Reader 's >>= , then simplify it a little. 让我们为Reader>>=导出特定的类型方案,然后对其进行一些简化。

-- General monad
(>>=) :: m a -> (a -> m b) -> m b

-- Reader monad
(>>=) :: Reader r a -> (a -> Reader r b) -> Reader r b

-- Let me put it in a (syntactically incorrect, but) more illustrative form
(>>=) :: (Reader r -> a) -> (a -> (Reader r -> b)) -> (Reader r -> b)

-- A reader is a function of type (r -> a), packed into a Reader context.
-- If we want to access the wrapped function, we can easily do it with runReader.

-- With this in mind, let's see how it would be without the extra context.
(>>=) :: (r -> a) -> (a -> (r -> b)) -> (r -> b)


We can think of >>= as a function that takes two parameters, m (a monadic value) and f (a function), and returns result (another monadic value). 我们可以将>>=视为一个具有两个参数m (一个单调值)和f (一个函数)并返回result (另一个单调值)的函数。

Let's now write an implementation for this simplified type structure. 现在让我们为这种简化的类型结构编写一个实现。

-- Takes m and f, returns result
m >>= f = result

-- The types
m      :: r -> a
f      :: a -> (r -> b)
result :: r -> b

-- Implementation
m >>= f = \x -> (f (m x)) x

-- A quick How-We-Got-Here
mResult = m x  -- :: a
fResult = f mResult  -- :: r -> b
result  = \x -> fResult x
        = \x -> (f mResult) x
        = \x -> (f (m x)) x


Time to bring Reader back in the game. 是时候让Reader重新加入游戏了。 The form f >>= m = result still stands, but the types change a bit, and with them the implementation will change a bit, too. 形式f >>= m = result仍然存在,但是类型有所变化,实现也将有所变化。

-- The types
m      :: Reader (r -> a)
f      :: a -> Reader (r -> b)
result :: Reader (r -> b)

-- Functions we easily used before, are now in a "Reader".
-- But we can easily unwrap and access them with "runReader".

-- Now "result" is not just a function, but one in a "Reader".
-- But we can easily wrap it with "Reader".

-- Apply these tools on our How-We-Got-Here analogy from before.
mResult = (runReader m) x  -- :: a
fResult = f mResult  -- :: Reader (r -> b)
result  = Reader $ \x -> (runReader fResult) x
        = Reader $ \x -> (runReader (f mResult)) x
        = Reader $ \x -> (runReader (f ((runReader m) x))) x


After all, the actual implementation. 毕竟是实际执行。

m >>= f = Reader $ \x -> (runReader (f ((runReader m) x))) x

-- Remove the unnecessary parens
m >>= f = Reader $ \x -> runReader (f (runReader m x)) x

-- Different letters
m >>= k = Reader $ \r -> runReader (k (runReader m r)) r

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM