繁体   English   中英

读者Monad澄清

[英]Reader Monad clarification

我试图理解读者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 ra)
  2. 在这部分定义中发生了什么: (f (re)) ,它的目的是什么?

非常感谢帮助我。

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

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

data Foo = Bar | Baz

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

所有这些东西都有类型,这有助于可视化。 Foo一种 ,实质上是“类型级别的东西”,而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是接受各种值作为参数的价值级函数。

现在,回到Reader的定义。 暂时避免使用记录语法,我们可以按如下方式重新表述:

newtype Reader r a = Reader (r -> a)

这会绑定类型命名空间中的一个名称和值命名空间中的一个名称,但令人困惑的部分是它们都被命名为Reader 但是,在Haskell中完全允许这样做,因为名称空间是分开的。 在这种情况下,每个Reader都有一种/类型:

Reader :: * -> * -> *
Reader :: (r -> a) -> Reader r a

请注意,类型级Reader有两个参数,但值级Reader只接受一个。 当您对值进行模式匹配时,您正在使用值级构造函数(因为您正在解构使用相同构造函数构建的值),并且该值仅包含一个值(因为它必须是,因为Reader是一个newtype ),所以模式只绑定一个变量。


在这部分定义中发生了什么: (f (re)) ,它的目的是什么?

Reader本质上是一种组成许多函数的机制,这些函数都采用相同的参数。 这是一种避免必须在任何地方处理值的方法,因为各种实例将自动执行管道。

要理解>>= for Reader的定义,让我们专门研究>>= to Reader的类型:

(>>=) :: Reader r a -> (a -> Reader r b) -> Reader r b

为了清楚起见,我们还可以将Reader ra扩展为r -> a ,以便更好地直观了解类型的实际含义

(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)

为了讨论,让我们在这里命名参数:

(>>=) :: (r -> a) -> (a -> r -> b) -> (r -> b)
(>>=)    f           g             =  ...

让我们暂时考虑一下。 我们给出了两个功能, fg ,我们预计将产生,其产生类型的值的函数b从类型的值a 我们只有一种方法来产生一个b ,那就是通过调用g 但是为了打电话给g ,我们必须有一个a ,而我们只有一种方法来获得a :call 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一些用法,否则定义是相同的:

Reader r >>= f = Reader (\e -> runReader (f (r e)) e)

在这一点上,你可以检查你的直觉: Reader是一种在很多函数之间绕过一个值的方法,在这里我们组成了两个函数rf 因此,我们应该需要传递的值的两倍,这是我们做的:有两个用途e上述定义。

为了回答你的功能, Monad是类型的 * -> *

  • [Int] :: * =整数列表
  • [] :: * -> * = list
  • Reader ea :: * =具有环境e的读者导致a
  • Reader e :: * -> * =环境e的读者
  • Reader :: * -> * -> * =读者

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

(同样,当wMonoid时, Writer wMonadWriter不是Monad )。


要回答你的第二个问题,就Reader ea和函数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
  1. 没有部分申请。 Reader是一个newtype定义,它''包含' 一个值( runReader ),它是e -> a类型的函数。 因此, Reader r只是模式匹配Reader包装器中的功能。 r绑定到e -> a类型的函数。

  2. f是一个函数,就像r re使用值e调用函数r ,然后使用该函数调用的结果调用f

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM