我的第一个问题是,为什么 Reader 部分应用在 bind 的左侧?(Reader r)
而不是(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
is 在类型命名空间中,Bar
并且Baz
在 value 命名空间中。然而,它们的类型更复杂:
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 (r e))
什么:,它的目的是什么?
Reader
本质上是一种机制,用于组合许多都采用相同参数的函数。这是一种避免在任何地方都使用线程的方法,因为各种实例会自动执行管道。
为了理解>>=
for的定义Reader
,让我们专门化>>=
to的类型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
从 type 的值产生 type 的值a
。我们只有一种方法来产生 a 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
wrapper和runReader
unwrapper的用法,否则定义是一样的:
Reader r >>= f = Reader (\e -> runReader (f (r e)) e)
在这一点上,您可以检查您的直觉:Reader
是一种在许多函数之间传递值的方法,这里我们组合了两个函数,r
并且f
. 因此,我们需要将值传递两次,我们这样做:e
在上面的定义中有两种用法。