我的第一个问题是,为什么 Reader 部分应用在 bind 的左侧? (Reader r) 而不是 (Reader r a)。
不是。 Reader 的使用已经完全饱和,因为它必须如此。但是,我可以理解您的困惑……请记住,在 Haskell 中,类型和值位于不同的命名空间中,使用 data 或 newtype 定义数据类型会将新名称带入两个命名空间的范围内。例如,考虑以下声明:
data Foo = Bar | Baz
此定义绑定了三个名称,Foo、Bar 和 Baz。但是,等号左侧的部分绑定在类型命名空间中,因为Foo 是一个类型,而右侧的构造函数绑定在值命名空间中,因为Bar 和@ 987654332@ 本质上是值。
所有这些东西也都有类型,这有助于可视化。 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 是接受各种值作为参数的值级函数。
现在,回到Reader 的定义。暂时避免记录语法,我们可以将其重新编写如下:
newtype Reader r a = Reader (r -> a)
这会在类型命名空间中绑定一个名称,在值命名空间中绑定一个名称,但令人困惑的是它们都被命名为Reader!不过,这在 Haskell 中是完全允许的,因为命名空间是分开的。在这种情况下,每个Reader 也有一个种类/类型:
Reader :: * -> * -> *
Reader :: (r -> a) -> Reader r a
请注意,类型级别 Reader 接受两个参数,但值级别 Reader 只接受一个。当您对一个值进行模式匹配时,您正在使用值级构造函数(因为您正在解构使用相同构造函数构建的值),并且该值仅包含一个值(必须如此,因为 @987654357 @ 是 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,我们期望生成一个函数,该函数从 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 wrapper 和runReader unwrapper 的一些用法,否则定义相同:
Reader r >>= f = Reader (\e -> runReader (f (r e)) e)
此时,您可以检查一下您的直觉:Reader 是一种在许多函数之间传递值的方法,这里我们组合了两个函数,r 和 f。因此,我们需要将值传递两次,我们这样做:在上面的定义中e 有两种用法。