Reader Monad 说明
Reader Monad clarification
我试图理解 reader 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 部分应用在左侧
绑定的手边?
(Reader r)
而不是 (Reader r a)
.
- 定义的这一部分发生了什么:
(f (r e))
,它的目的是什么?
非常感谢你帮助我。
为了回答您的功能,Monad
是 class 类型 kind * -> *
.
[Int] :: *
= 整数列表
[] :: * -> *
= 列表
Reader e a :: *
= reader 环境 e 导致
Reader e :: * -> *
= reader 环境 e
Reader :: * -> * -> *
= reader
当我们说 Reader
有一个 monad 实例时,我们很糟糕 ,当我们的意思是
Reader
任何环境都有一个 monad 实例.
(类似地,Writer w
是 Monad
,而 w
是 Monoid
,Writer
不是
Monad
).
要回答你的第二个问题,从以下方面考虑更容易
Reader e a
与函数 e -> a
.
相同
函数具有相同的 monad 定义,但没有 newtype
包装器和
展开。想想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
定义 'wraps' 恰好 一个 值 (runReader
),它是类型 [=13] 的函数=].因此,Reader r
只是模式匹配 Reader
包装器中的函数。 r
绑定到 e -> a
类型的函数。
f
是一个函数,r
也是。 r e
使用值 e
调用函数 r
,然后使用该函数调用的结果调用 f
。
My first question is, why is Reader partially applied on the left hand side of bind? (Reader r)
instead of (Reader r a)
.
不是。 Reader
的使用已经完全饱和,这是必然的。但是,我能理解您的困惑……请记住,在 Haskell 中,类型和值位于不同的命名空间中,使用 data
或 newtype
定义数据类型会将新名称引入两个命名空间的范围。例如,考虑以下声明:
data Foo = Bar | Baz
此定义绑定了三个名称,Foo
、Bar
和 Baz
。但是,等号左侧的部分绑定在类型命名空间中,因为 Foo
是一个类型,而右侧的构造函数绑定在值命名空间中,因为 Bar
和 Baz
本质上是值。
所有这些东西也都有类型,这有助于可视化。 Foo
有一个 kind,它本质上是“类型级事物的类型”,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
是接受各种值作为参数的值级函数。
现在,return定义Reader
。暂时避开记录语法,我们可以重新表述如下:
newtype Reader r a = Reader (r -> a)
这绑定了类型命名空间中的一个名称和值命名空间中的一个名称,但令人困惑的是它们都被命名为 Reader
!不过,这在 Haskell 中是完全允许的,因为命名空间是分开的。在这种情况下,每个 Reader
也有一个 kind/type:
Reader :: * -> * -> *
Reader :: (r -> a) -> Reader r a
请注意,类型级 Reader
有两个参数,但值级 Reader
只需要一个。当你对一个值进行模式匹配时,你正在使用值级构造函数(因为你正在解构一个使用相同构造函数构建的值),并且该值只包含一个值(因为它必须包含,因为 Reader
是一个 newtype
),所以模式只绑定一个变量。
What goes on in this part of the definition: (f (r e))
, what is it's purpose?
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
,我们期望生成一个函数,该函数从 a
类型的值生成 b
类型的值。我们只有 一种 方法来生成 b
,那就是调用 g
。但是为了调用 g
,我们必须有一个 a
,而我们只有一种方法可以得到一个 a
:调用 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 r >>= f = Reader (\e -> runReader (f (r e)) e)
在这一点上,您可以检查一下您的直觉:Reader
是一种在许多函数之间串接一个值的方法,这里我们组合了两个函数,r
和 f
。因此,我们应该需要将值传递两次,我们这样做了:在上面的定义中 e
有两种用法。
我试图理解 reader 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 部分应用在左侧
绑定的手边?
(Reader r)
而不是(Reader r a)
. - 定义的这一部分发生了什么:
(f (r e))
,它的目的是什么?
非常感谢你帮助我。
为了回答您的功能,Monad
是 class 类型 kind * -> *
.
[Int] :: *
= 整数列表[] :: * -> *
= 列表Reader e a :: *
= reader 环境 e 导致Reader e :: * -> *
= reader 环境 eReader :: * -> * -> *
= reader
当我们说 Reader
有一个 monad 实例时,我们很糟糕 ,当我们的意思是
Reader
任何环境都有一个 monad 实例.
(类似地,Writer w
是 Monad
,而 w
是 Monoid
,Writer
不是
Monad
).
要回答你的第二个问题,从以下方面考虑更容易
Reader e a
与函数 e -> a
.
函数具有相同的 monad 定义,但没有 newtype
包装器和
展开。想想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
定义 'wraps' 恰好 一个 值 (runReader
),它是类型 [=13] 的函数=].因此,Reader r
只是模式匹配Reader
包装器中的函数。r
绑定到e -> a
类型的函数。f
是一个函数,r
也是。r e
使用值e
调用函数r
,然后使用该函数调用的结果调用f
。
My first question is, why is Reader partially applied on the left hand side of bind?
(Reader r)
instead of(Reader r a)
.
不是。 Reader
的使用已经完全饱和,这是必然的。但是,我能理解您的困惑……请记住,在 Haskell 中,类型和值位于不同的命名空间中,使用 data
或 newtype
定义数据类型会将新名称引入两个命名空间的范围。例如,考虑以下声明:
data Foo = Bar | Baz
此定义绑定了三个名称,Foo
、Bar
和 Baz
。但是,等号左侧的部分绑定在类型命名空间中,因为 Foo
是一个类型,而右侧的构造函数绑定在值命名空间中,因为 Bar
和 Baz
本质上是值。
所有这些东西也都有类型,这有助于可视化。 Foo
有一个 kind,它本质上是“类型级事物的类型”,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
是接受各种值作为参数的值级函数。
现在,return定义Reader
。暂时避开记录语法,我们可以重新表述如下:
newtype Reader r a = Reader (r -> a)
这绑定了类型命名空间中的一个名称和值命名空间中的一个名称,但令人困惑的是它们都被命名为 Reader
!不过,这在 Haskell 中是完全允许的,因为命名空间是分开的。在这种情况下,每个 Reader
也有一个 kind/type:
Reader :: * -> * -> *
Reader :: (r -> a) -> Reader r a
请注意,类型级 Reader
有两个参数,但值级 Reader
只需要一个。当你对一个值进行模式匹配时,你正在使用值级构造函数(因为你正在解构一个使用相同构造函数构建的值),并且该值只包含一个值(因为它必须包含,因为 Reader
是一个 newtype
),所以模式只绑定一个变量。
What goes on in this part of the definition:
(f (r e))
, what is it's purpose?
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
,我们期望生成一个函数,该函数从 a
类型的值生成 b
类型的值。我们只有 一种 方法来生成 b
,那就是调用 g
。但是为了调用 g
,我们必须有一个 a
,而我们只有一种方法可以得到一个 a
:调用 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 r >>= f = Reader (\e -> runReader (f (r e)) e)
在这一点上,您可以检查一下您的直觉:Reader
是一种在许多函数之间串接一个值的方法,这里我们组合了两个函数,r
和 f
。因此,我们应该需要将值传递两次,我们这样做了:在上面的定义中 e
有两种用法。