[英]Reader Monad clarification
我试图理解读者monad但似乎无法理解bind(>> =)在这个monad中的作用。
这是我正在分析的实现:
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)
代替(Reader ra)
。 (f (re))
,它的目的是什么? 非常感谢帮助我。
我的第一个问题是,为什么Reader部分应用于绑定的左侧?
(Reader r)
代替(Reader ra)
。
事实并非如此。 Reader
使用完全饱和,必须如此。 但是,我可以理解你的困惑......请记住,在Haskell中,类型和值位于不同的名称空间中,并且使用data
或newtype
定义数据类型会在两个名称空间中将新名称带入范围。 例如,请考虑以下声明:
data Foo = Bar | Baz
这个定义绑定了三个名字, Foo
, Bar
和Baz
。 但是,等号左侧的部分绑定在类型命名空间中,因为Foo
是一个类型,右侧的构造函数绑定在值命名空间中,因为Bar
和Baz
本质上是值。
所有这些东西都有类型,这有助于可视化。 Foo
有一种 ,实质上是“类型级别的东西”,而Bar
和Baz
都有一种类型。 这些类型可以写成如下:
Foo :: *
Bar :: Foo
Baz :: Foo
......其中*
是那种类型。
现在,考虑一个稍微复杂的定义:
data Foo a = Bar Integer String | Baz a
再一次,这个定义绑定了三个名字: Foo
, Bar
和Baz
。 Foo
再次位于类型命名空间中, Bar
和Baz
位于值命名空间中。 然而,它们的类型更精细:
Foo :: * -> *
Bar :: Integer -> String -> Foo a
Baz :: a -> Foo a
这里, Foo
是一个类型构造函数,因此它本质上是一个类型级函数,它接受一个类型( *
)作为参数。 同时, Bar
和Baz
是接受各种值作为参数的价值级函数。
现在,回到Reader
的定义。 暂时避免使用记录语法,我们可以按如下方式重新表述:
newtype Reader r a = Reader (r -> a)
这会绑定类型命名空间中的一个名称和值命名空间中的一个名称,但令人困惑的部分是它们都被命名为Reader
! 但是,在Haskell中完全允许这样做,因为名称空间是分开的。 在这种情况下,每个Reader
都有一种/类型:
Reader :: * -> * -> *
Reader :: (r -> a) -> Reader r a
请注意,类型级Reader
有两个参数,但值级Reader
只接受一个。 当您对值进行模式匹配时,您正在使用值级构造函数(因为您正在解构使用相同构造函数构建的值),并且该值仅包含一个值(因为它必须是,因为Reader
是一个newtype
),所以模式只绑定一个变量。
在这部分定义中发生了什么:
(f (re))
,它的目的是什么?
Reader
本质上是一种组成许多函数的机制,这些函数都采用相同的参数。 这是一种避免必须在任何地方处理值的方法,因为各种实例将自动执行管道。
要理解>>=
for Reader
的定义,让我们专门研究>>=
to Reader
的类型:
(>>=) :: Reader r a -> (a -> Reader r b) -> Reader r b
为了清楚起见,我们还可以将Reader ra
扩展为r -> a
,以便更好地直观了解类型的实际含义 :
(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)
为了讨论,让我们在这里命名参数:
(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)
(>>=) f g = ...
让我们暂时考虑一下。 我们给出了两个功能, f
和g
,我们预计将产生,其产生类型的值的函数b
从类型的值a
。 我们只有一种方法来产生一个b
,那就是通过调用g
。 但是为了打电话给g
,我们必须有一个a
,而我们只有一种方法来获得a
:call f
! 我们可以调用f
,因为它只需要一个我们已经拥有的r
,所以我们可以开始将函数连接在一起以生成我们需要的b
。
这有点令人困惑,因此可以直观地看到这种价值流:
+------------+
| input :: r |
+------------+
| |
v |
+--------------+ |
| f input :: a | |
+--------------+ |
| |
v v
+------------------------+
| g (f input) input :: b |
+------------------------+
在Haskell中,这看起来像这样:
f >>= g = \input -> g (f input) input
...或者,重新命名一些东西以匹配您问题中的定义:
r >>= f = \e -> f (r e) e
现在,我们需要重新引入一些包装和解包,因为真正的定义是在Reader
类型上,而不是(->)
直接。 这意味着我们需要添加Reader
包装器和runReader
解包Reader
一些用法,否则定义是相同的:
Reader r >>= f = Reader (\e -> runReader (f (r e)) e)
在这一点上,你可以检查你的直觉: Reader
是一种在很多函数之间绕过一个值的方法,在这里我们组成了两个函数r
和f
。 因此,我们应该需要传递的值的两倍,这是我们做的:有两个用途e
上述定义。
为了回答你的功能, Monad
是类型的类 * -> *
。
[Int] :: *
=整数列表 [] :: * -> *
= list Reader ea :: *
=具有环境e的读者导致a Reader e :: * -> *
=环境e的读者 Reader :: * -> * -> *
=读者 当我们说Reader
有一个monad实例时 ,我们很糟糕,当我们的意思是任何环境的Reader
都有一个monad实例 。
(同样,当w
是Monoid
时, Writer w
是Monad
, Writer
不是Monad
)。
要回答你的第二个问题,就Reader ea
和函数e -> a
更容易思考。
函数具有相同的monad定义,但没有newtype
包装器和unwrappers。 认为Reader = (->)
:
instance Monad ((->) e) where
return x = \_ -> x -- alternatively, return = const !
r >>= f = \e -> f (r e) e
然而, join
定义可能是最有见地的:
join :: Reader e (Reader e a) -> Reader e a
但是有裸功能:
join :: (e -> e -> a) -> (e -> a)
join f e = f e e
如果我们为Reader
编写它,我们必须在正确的位置添加runReader
和Reader
(并将变量绑定移动到RHS上的lambda):
join f = Reader $ \e -> runReader (runReader f e) e
没有部分申请。 Reader
是一个newtype
定义,它''包含' 一个值( runReader
),它是e -> a
类型的函数。 因此, Reader r
只是模式匹配Reader
包装器中的功能。 r
绑定到e -> a
类型的函数。
f
是一个函数,就像r
。 re
使用值e
调用函数r
,然后使用该函数调用的结果调用f
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.