[英]Reader Monad clarification
I am trying to make sense of the reader monad but can't seem to grasp what bind (>>=) does in this monad. 我试图理解读者monad但似乎无法理解bind(>> =)在这个monad中的作用。
Here's the implementation I am analyzing: 这是我正在分析的实现:
newtype Reader e a = Reader { runReader :: (e -> a) }
instance Monad (Reader e) where
return a = Reader $ \e -> a
(Reader r) >>= f = Reader $ \e -> runReader (f (r e)) e
(Reader r)
instead of (Reader ra)
. (Reader r)
代替(Reader ra)
。 (f (re))
, what is it's purpose? (f (re))
,它的目的是什么? Thanks a lot for helping me out. 非常感谢帮助我。
My first question is, why is Reader partially applied on the left hand side of bind?
我的第一个问题是,为什么Reader部分应用于绑定的左侧?
(Reader r)
instead of(Reader ra)
.(Reader r)
代替(Reader ra)
。
It isn't. 事实并非如此。 That use of
Reader
is fully saturated, as it must be. Reader
使用完全饱和,必须如此。 However, I can understand your confusion… remember that in Haskell, types and values are in different namespaces, and defining a datatype with data
or newtype
brings new names into scope in both namespaces. 但是,我可以理解你的困惑......请记住,在Haskell中,类型和值位于不同的名称空间中,并且使用
data
或newtype
定义数据类型会在两个名称空间中将新名称带入范围。 For example, consider the following declaration: 例如,请考虑以下声明:
data Foo = Bar | Baz
This definition binds three names, Foo
, Bar
, and Baz
. 这个定义绑定了三个名字,
Foo
, Bar
和Baz
。 However, the part on the left-hand side of the equals sign is bound in the type namespace, since Foo
is a type, and the constructors on the right hand side are bound in the value namespace, since Bar
and Baz
are essentially values. 但是,等号左侧的部分绑定在类型命名空间中,因为
Foo
是一个类型,右侧的构造函数绑定在值命名空间中,因为Bar
和Baz
本质上是值。
All of these things have types as well, which can be helpful to visualize. 所有这些东西都有类型,这有助于可视化。
Foo
has a kind , which is essentially the “type of a type-level thing”, and Bar
and Baz
both have a type. Foo
有一种 ,实质上是“类型级别的东西”,而Bar
和Baz
都有一种类型。 These types can be written as follows: 这些类型可以写成如下:
Foo :: *
Bar :: Foo
Baz :: Foo
…where *
is the kind of types. ......其中
*
是那种类型。
Now, consider a slightly more complicated definition: 现在,考虑一个稍微复杂的定义:
data Foo a = Bar Integer String | Baz a
Once again, this definition binds three names: Foo
, Bar
, and Baz
. 再一次,这个定义绑定了三个名字:
Foo
, Bar
和Baz
。 Once again, Foo
is in the type namespace and Bar
and Baz
are in the value namespace. Foo
再次位于类型命名空间中, Bar
和Baz
位于值命名空间中。 Their types, however, are more elaborate: 然而,它们的类型更精细:
Foo :: * -> *
Bar :: Integer -> String -> Foo a
Baz :: a -> Foo a
Here, Foo
is a type constructor, so it's essentially a type-level function that accepts a type ( *
) as an argument. 这里,
Foo
是一个类型构造函数,因此它本质上是一个类型级函数,它接受一个类型( *
)作为参数。 Meanwhile, Bar
and Baz
are value-level functions that accept various values as arguments. 同时,
Bar
和Baz
是接受各种值作为参数的价值级函数。
Now, return to the definition of Reader
. 现在,回到
Reader
的定义。 Avoiding record syntax for a moment, we can reformulate it as follows: 暂时避免使用记录语法,我们可以按如下方式重新表述:
newtype Reader r a = Reader (r -> a)
This binds one name in the type namespace and one name in the value namespace, but the confusing part is that they're both named Reader
! 这会绑定类型命名空间中的一个名称和值命名空间中的一个名称,但令人困惑的部分是它们都被命名为
Reader
! This is totally allowed in Haskell, though, since the namespaces are separate. 但是,在Haskell中完全允许这样做,因为名称空间是分开的。 Each
Reader
has a kind/type in this case, too: 在这种情况下,每个
Reader
都有一种/类型:
Reader :: * -> * -> *
Reader :: (r -> a) -> Reader r a
Note that the type-level Reader
takes two arguments, but the value-level Reader
only takes one. 请注意,类型级
Reader
有两个参数,但值级Reader
只接受一个。 When you pattern-match against a value, you are using the value-level constructor (since you're deconstructing a value that was built up using that same constructor), and the value only contains one value (as it must, since Reader
is a newtype
), so the pattern only binds a single variable. 当您对值进行模式匹配时,您正在使用值级构造函数(因为您正在解构使用相同构造函数构建的值),并且该值仅包含一个值(因为它必须是,因为
Reader
是一个newtype
),所以模式只绑定一个变量。
What goes on in this part of the definition:
(f (re))
, what is it's purpose?在这部分定义中发生了什么:
(f (re))
,它的目的是什么?
Reader
is essentially a mechanism for composing a lot of functions that all take the same argument. Reader
本质上是一种组成许多函数的机制,这些函数都采用相同的参数。 It's a way to avoid having to thread a value around everywhere, since the various instances will do the plumbing automatically. 这是一种避免必须在任何地方处理值的方法,因为各种实例将自动执行管道。
To understand the definition of >>=
for Reader
, let's specialize the type of >>=
to Reader
: 要理解
>>=
for Reader
的定义,让我们专门研究>>=
to Reader
的类型:
(>>=) :: Reader r a -> (a -> Reader r b) -> Reader r b
For the purpose of clarity, we can also expand Reader ra
to r -> a
, just to get a better intuition for what the type actually means : 为了清楚起见,我们还可以将
Reader ra
扩展为r -> a
,以便更好地直观了解类型的实际含义 :
(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)
Let's also name the arguments here for the purpose of discussion: 为了讨论,让我们在这里命名参数:
(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)
(>>=) f g = ...
Let's think about this for a moment. 让我们暂时考虑一下。 We are given two functions,
f
and g
, and we are expected to produce a function that produces a value of type b
from a value of type a
. 我们给出了两个功能,
f
和g
,我们预计将产生,其产生类型的值的函数b
从类型的值a
。 We only have one way to produce a b
, and that's by calling g
. 我们只有一种方法来产生一个
b
,那就是通过调用g
。 But in order to call g
, we have to have an a
, and we only have one way to get an a
: call f
! 但是为了打电话给
g
,我们必须有一个a
,而我们只有一种方法来获得a
:call f
! We can call f
, since it only needs an r
, which we already have, so we can start attaching the functions together to produce the b
we need. 我们可以调用
f
,因为它只需要一个我们已经拥有的r
,所以我们可以开始将函数连接在一起以生成我们需要的b
。
This is a little confusing, so it might help to see this flow of values visually: 这有点令人困惑,因此可以直观地看到这种价值流:
+------------+
| input :: r |
+------------+
| |
v |
+--------------+ |
| f input :: a | |
+--------------+ |
| |
v v
+------------------------+
| g (f input) input :: b |
+------------------------+
In Haskell, this looks like this: 在Haskell中,这看起来像这样:
f >>= g = \input -> g (f input) input
…or, renaming things a little to match the definition in your question: ...或者,重新命名一些东西以匹配您问题中的定义:
r >>= f = \e -> f (r e) e
Now, we need to reintroduce some wrapping and unwrapping, since the real definition is on the Reader
type, not (->)
directly. 现在,我们需要重新引入一些包装和解包,因为真正的定义是在
Reader
类型上,而不是(->)
直接。 This means we need to add some uses of the Reader
wrapper and the runReader
unwrapper, but otherwise the definition is the same: 这意味着我们需要添加
Reader
包装器和runReader
解包Reader
一些用法,否则定义是相同的:
Reader r >>= f = Reader (\e -> runReader (f (r e)) e)
At this point, you can check your intuition: Reader
is a way to thread a value around between a lot of functions, and here we're composing two functions, r
and f
. 在这一点上,你可以检查你的直觉:
Reader
是一种在很多函数之间绕过一个值的方法,在这里我们组成了两个函数r
和f
。 Therefore, we should need to pass the value in twice, which we do: there are two uses of e
in the above definition. 因此,我们应该需要传递的值的两倍,这是我们做的:有两个用途
e
上述定义。
To answer your function, Monad
is class over types of kind * -> *
. 为了回答你的功能,
Monad
是类型的类 * -> *
。
[Int] :: *
= list of integers [Int] :: *
=整数列表 [] :: * -> *
= list [] :: * -> *
= list Reader ea :: *
= reader with environment e resulting in a Reader ea :: *
=具有环境e的读者导致a Reader e :: * -> *
= reader with environment e Reader e :: * -> *
=环境e的读者 Reader :: * -> * -> *
= reader Reader :: * -> * -> *
=读者 We are lousy when we say Reader
has a monad instance , when we mean Reader
with any environment has a monad instance . 当我们说
Reader
有一个monad实例时 ,我们很糟糕,当我们的意思是任何环境的Reader
都有一个monad实例 。
(Similarly, Writer w
is a Monad
when w
is a Monoid
, Writer
isn't a Monad
). (同样,当
w
是Monoid
时, Writer w
是Monad
, Writer
不是Monad
)。
To answer your second question, it's easier to think in terms of the Reader ea
as the same as function e -> a
. 要回答你的第二个问题,就
Reader ea
和函数e -> a
更容易思考。
Function has the same monad definition, but without newtype
wrappers and unwrappers. 函数具有相同的monad定义,但没有
newtype
包装器和unwrappers。 Think Reader = (->)
: 认为
Reader = (->)
:
instance Monad ((->) e) where
return x = \_ -> x -- alternatively, return = const !
r >>= f = \e -> f (r e) e
Yet, the join
definition is probably most insightful: 然而,
join
定义可能是最有见地的:
join :: Reader e (Reader e a) -> Reader e a
But with bare function: 但是有裸功能:
join :: (e -> e -> a) -> (e -> a)
join f e = f e e
And if we write it for Reader
we have to add runReader
and Reader
in right places (and move variable binding to the lambda on RHS): 如果我们为
Reader
编写它,我们必须在正确的位置添加runReader
和Reader
(并将变量绑定移动到RHS上的lambda):
join f = Reader $ \e -> runReader (runReader f e) e
There's no partial application. 没有部分申请。
Reader
is a newtype
definition that 'wraps' exactly one value ( runReader
), which is a function of the type e -> a
. Reader
是一个newtype
定义,它''包含' 一个值( runReader
),它是e -> a
类型的函数。 So, Reader r
just pattern-matches the function out of the Reader
wrapper. 因此,
Reader r
只是模式匹配Reader
包装器中的功能。 r
is bound to the function of type e -> a
. r
绑定到e -> a
类型的函数。
f
is a function, as is r
. f
是一个函数,就像r
。 re
invokes the function r
with the value e
, and then f
is invoked with the result of that function call. re
使用值e
调用函数r
,然后使用该函数调用的结果调用f
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.