我试图理解读者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 r a)
。(f (r e))
,它的目的是什么?非常感谢帮助我。
答案 0 :(得分:7)
我的第一个问题是,为什么Reader部分应用于绑定的左侧?
(Reader r)
代替(Reader r a)
。
不是。 Reader
的使用完全饱和,必须如此。但是,我可以理解您的困惑...请记住,在Haskell中,类型和值位于不同的名称空间中,并且使用data
或newtype
定义数据类型会在两个名称空间中将新名称带入范围。例如,请考虑以下声明:
data Foo = Bar | Baz
此定义绑定了三个名称Foo
,Bar
和Baz
。但是,等号左侧的部分绑定在类型命名空间中,因为Foo
是一个类型,右侧的构造函数绑定在值命名空间中,因为{{1 }和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)
!但是,在Haskell中完全允许这样做,因为名称空间是分开的。在这种情况下,每个Reader
都有一种/类型:
Reader
请注意,类型级Reader :: * -> * -> *
Reader :: (r -> a) -> Reader r a
有两个参数,但值级Reader
只有一个。当您对值进行模式匹配时,您正在使用值级构造函数(因为您正在解构使用相同构造函数构建的值),并且该值仅包含一个值(因为它必须,因为{{ 1}}是Reader
),因此模式只绑定单个变量。
定义的这一部分发生了什么:
Reader
,它的目的是什么?
newtype
本质上是一种组合许多函数的机制,这些函数都采用相同的参数。这是一种避免必须在任何地方处理值的方法,因为各种实例都会自动执行管道。
要了解(f (r e))
Reader
的定义,我们将>>=
的类型专门设为Reader
:
>>=
为了清晰起见,我们还可以将Reader
扩展为(>>=) :: Reader r a -> (a -> Reader r b) -> Reader r b
,只是为了更好地直观了解类型的类型:
Reader r a
为了讨论的目的,让我们在这里命名参数:
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
,因为它只需要我们已经拥有的f
,因此我们可以开始将这些函数连接在一起以生成我们需要的f
这有点令人困惑,因此可以直观地看到这种价值流:
r
在Haskell中,这看起来像这样:
b
...或者,稍微重命名以符合您问题中的定义:
+------------+
| input :: r |
+------------+
| |
v |
+--------------+ |
| f input :: a | |
+--------------+ |
| |
v v
+------------------------+
| g (f input) input :: b |
+------------------------+
现在,我们需要重新引入一些包装和解包,因为真正的定义是f >>= g = \input -> g (f input) input
类型,而不是r >>= f = \e -> f (r e) e
。这意味着我们需要添加Reader
包装器和(->)
unwrapper的一些用法,否则定义是相同的:
Reader
此时,你可以检查你的直觉:runReader
是一种在很多函数之间绕过一个值的方法,在这里我们组成了两个函数,Reader r >>= f = Reader (\e -> runReader (f (r e)) e)
和{{1 }}。因此,我们需要将值传递两次,我们这样做:在上面的定义中有Reader
的两种用法。
答案 1 :(得分:3)
要回答您的功能,Monad
会对种 * -> *
的类型进行分类。
[Int] :: *
=整数列表[] :: * -> *
= list Reader e a :: *
=环境e的读者导致Reader e :: * -> *
=阅读环境e Reader :: * -> * -> *
=读者当我们说 Reader
有一个monad实例时,我们很糟糕
任何环境的 Reader
都有一个monad实例。
(同样,当Writer w
为Monad
时,w
为Monoid
,Writer
不是
Monad
)。
要回答你的第二个问题,就更容易思考
Reader e a
与函数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
答案 2 :(得分:1)
没有部分申请。 Reader
是一个newtype
定义,它“完全包裹”一个值(runReader
),这是e -> a
类型的函数。因此,Reader r
只是模式匹配Reader
包装器中的函数。 r
绑定到e -> a
类型的函数。
f
是一个函数,r
也是如此。 r e
使用值r
调用函数e
,然后使用该函数调用的结果调用f
。