[英]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看成是需要一个功能a
和r
并返回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的绑定的定义非常相似,但不使用
Reader
和runReader
:
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 = ...
其中m
是Reader ra
, k
是(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
? 我们如何获得的价值
a
由Reader 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
,所以如果我们调用runReader
的m
,我们会得到(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:
典型的解决方案是以下之一:
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,调用者现在也必须计算它);
create a global variable that g will read to provide f with that argument (g is no longer pure); 创建一个全局变量,g将读取该变量以为f提供该参数(g不再是纯变量);
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.