读者Monad澄清

时间:2018-01-21 18:05:33

标签: haskell monads reader-monad

我试图理解读者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)),它的目的是什么?
  3. 非常感谢帮助我。

3 个答案:

答案 0 :(得分:7)

  

我的第一个问题是,为什么Reader部分应用于绑定的左侧? (Reader r)代替(Reader r a)

不是。 Reader的使用完全饱和,必须如此。但是,我可以理解您的困惑...请记住,在Haskell中,类型和值位于不同的名称空间中,并且使用datanewtype定义数据类型会在两个名称空间中将新名称带入范围。例如,请考虑以下声明:

data Foo = Bar | Baz

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

所有这些东西都有类型,这有助于可视化。 Baz有一个,它本质上是“类型级别的东西”,FooBar都有一个类型。这些类型可以写成如下:

Baz

... Foo :: * Bar :: Foo Baz :: Foo 就是那种类型。

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

*

此定义再次绑定了三个名称:data Foo a = Bar Integer String | Baz a FooBar。同样,Baz位于类型命名空间中,FooBar位于值命名空间中。然而,它们的类型更精细:

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 wMonad时,wMonoidWriter不是 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撰写,我们必须添加runReaderReader 正确的位置(并将变量绑定到RHS上的lambda):

join f = Reader $ \e -> runReader (runReader f e) e

答案 2 :(得分:1)

  1. 没有部分申请。 Reader是一个newtype定义,它“完全包裹”一个值(runReader),这是e -> a类型的函数。因此,Reader r只是模式匹配Reader包装器中的函数。 r绑定到e -> a类型的函数。

  2. f是一个函数,r也是如此。 r e使用值r调用函数e,然后使用该函数调用的结果调用f