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
  1. 我的第一个问题是,为什么 Reader 部分应用在左侧 绑定的手边? (Reader r) 而不是 (Reader r a).
  2. 定义的这一部分发生了什么:(f (r e)),它的目的是什么?

非常感谢你帮助我。

为了回答您的功能,Monad 是 class 类型 kind * -> *.

  • [Int] :: * = 整数列表
  • [] :: * -> * = 列表
  • Reader e a :: * = reader 环境 e 导致
  • Reader e :: * -> * = reader 环境 e
  • Reader :: * -> * -> * = reader

当我们说 Reader 有一个 monad 实例时,我们很糟糕 ,当我们的意思是 Reader 任何环境都有一个 monad 实例.

(类似地,Writer wMonad,而 wMonoidWriter 不是 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 编写它,我们必须在其中添加 runReaderReader 正确的位置(并将变量绑定移动到 RHS 上的 lambda):

join f = Reader $ \e -> runReader (runReader f e) e
  1. 没有部分应用。 Reader 是一个 newtype 定义 'wraps' 恰好 一个 值 (runReader),它是类型 [=13] 的函数=].因此,Reader r 只是模式匹配 Reader 包装器中的函数。 r 绑定到 e -> a 类型的函数。

  2. 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 中,类型和值位于不同的命名空间中,使用 datanewtype 定义数据类型会将新名称引入两个命名空间的范围。例如,考虑以下声明:

data Foo = Bar | Baz

此定义绑定了三个名称,FooBarBaz。但是,等号左侧的部分绑定在类型命名空间中,因为 Foo 是一个类型,而右侧的构造函数绑定在值命名空间中,因为 BarBaz 本质上是值。

所有这些东西也都有类型,这有助于可视化。 Foo 有一个 kind,它本质上是“类型级事物的类型”,BarBaz 都有一个类型。这些类型可以写成:

Foo :: *
Bar :: Foo
Baz :: Foo

…其中 * 是类型的种类。

现在,考虑一个稍微复杂的定义:

data Foo a = Bar Integer String | Baz a

再一次,这个定义绑定了三个名称:FooBarBaz。再一次,Foo 在类型命名空间中,BarBaz 在值命名空间中。然而,它们的类型更加复杂:

Foo :: * -> *
Bar :: Integer -> String -> Foo a
Baz :: a -> Foo a

这里,Foo是一个类型构造函数,所以它本质上是一个类型级函数,接受一个类型(*)作为参数。同时,BarBaz 是接受各种值作为参数的值级函数。

现在,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             =  ...

让我们考虑一下。我们有两个函数,fg,我们期望生成一个函数,该函数从 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 是一种在许多函数之间串接一个值的方法,这里我们组合了两个函数,rf。因此,我们应该需要将值传递两次,我们这样做了:在上面的定义中 e 有两种用法。