问题 理解Reader monad


我正在阅读 Purescript以身作则 并介绍了介绍Reader monad的部分。这个例子是这样的:

createUser :: Reader Permissions (Maybe User)
createUser = do
  permissions <- ask
  if hasPermission "admin" permissions
    then map Just newUser
    else pure Nothing

对我来说令人困惑的部分是 ask 功能。签名是:

ask   :: forall r. Reader r r

似乎它凭空创造了一个阅读器

当我读到关于 State monad,它与它有相同的概念 get 功能。文字解释说:

状态被实现为State monad的数据构造函数隐藏的函数参数,因此没有明确的引用传递。

我猜这是关键,同样的事情发生在这里与读者,但我不明白它是如何工作的......

当上面的例子运行时 runReader,提供的值是如何突然出现的结果 ask? Haskell文档为 ask 说: 检索monad环境。 但我的困惑是 来自哪里?我看到它的方式,一个值传递给 runReader,得到存储 某处,并得到它 - 你打电话 ask......但这没有任何意义。

虽然这个例子是Purescript,但我猜测任何有Haskell识字的人也能够回答,因此Haskell标签。


11732
2017-10-13 21:20


起源

Reader a b是一个包装 a -> b。所以, ask :: forall a. Reader a a 最终只是一种类型的价值 forall a. a -> a,周围有一个包装。 - David Young


答案:


我目前没有PureScript环境,因此我将尝试从Haskell的角度回答,并希望它有所帮助。

一个 读者 实际上只是一个函数的“包装器”,所以当你得到一个 Reader r r,你真的只有一个读者 r 至 r;换句话说,一个功能 r -> r

能够 因为如果你是柏拉图主义者,我认为他们总是存在......

当你使用 do 符号,你在'monad'中,所以上下文 r 是隐含的。换句话说,你调用一个返回的函数 r 价值,当你使用 <- 箭头,您只需获得该上下文。


10
2017-10-13 21:37



Upvoted。召唤这样一个人的方式可能毫无价值 r -> r 从稀薄的空气中发挥作用就是说出咒语 id。 (和 id 是个 只要 这样的功能,多亏了参数。) - Benjamin Hodgson♦
好的,所以在我的情况下,Reader是Permissions - > Maybe User的包装器。当我跑 runReader createUser permissions, 如何 ask 在体内 createUser 知道返回相同 permissions 我过去了 runReader?我确定这个问题是彻头彻尾的荒谬,因为我看着这一切都错了......但请尽量帮助我解开我的大脑。 - kaqqao
@kaqqao的类型 Reader Permissions (Maybe User) 只是一个“包装” Permissions -> (Maybe User),所以你的整个 createUser 'value'实际上是一个函数(但函数是值,所以很酷)。你打电话时 runReader,你必须不仅通过 createUser,但也是一个值 r  - 在这种情况下a Permissions 值。 runReader 然后使用。调用包装函数 Permissions 你通过它的价值。 HTH。 - Mark Seemann
@MarkSeemann Aah,我明白了。 runReader 在调用其中的函数之前,有效地设置Reader实例上的状态/环境。 (不确定我的术语有多好)最终有意义。谢谢你! - kaqqao
@ftor我不认为这一点 Reader monad是 伪装 任何东西。该 Reader monad在数学上存在,因此它也可能存在于Haskell / TypeScript API中。这使您可以使用具有在monad上运行的所有函数的阅读器(即纯函数)。 (例如。 mapM, sequence, 等等...)。动机不是为了隐藏任何东西,而是为了给你 选项 作文。 - Mark Seemann


您可以通过执行一些替换来说服自己它的工作原理。首先看看签名 createUser。让我们“展开”的定义 Reader

createUser :: Reader Permissions (Maybe User)
{- definition of Reader -}
createUser :: ReaderT Permissions Identity (Maybe User)

ReaderT type只有一个数据构造函数: ReaderT (r -> m a), 意思是 createUser 是一个计算类型值的术语 ReaderT (Permissions -> Identity (Maybe User))。如您所见,它只是一个标记的函数 ReaderT。它不必凭空创造任何东西,但会获得类型的价值 Permissions 何时调用该函数。

现在让我们来看看你遇到麻烦的那条线。你知道吗 do 符号只是语法糖,表达式:

do permissions <- ask
   if hasPermission "admin" permissions
     then map Just newUser
     else pure Nothing

desugars to

ask >>= \permissions -> 
  if hasPermission "admin" permissions
  then map Just newUser
  else pure Nothing

要理解这一点,你必须查找它的定义 ask>>= 和 pure 对于 ReaderT。让我们进行另一轮替换:

ask >>= \permissions -> ...
{- definition of ask for ReaderT -}
ReaderT pure >>= \permissions -> ...
{- definition of >>= for ReaderT -}
ReaderT \r ->
  pure r >>= \a -> case (\permissions -> ...) a of ReaderT f -> f r
{- function application -}
ReaderT \r ->
  pure r >>= \a -> 
    case (if hasPermission "admin" a
          then map Just newUser
          else pure Nothing) of ReaderT f -> f r
{- definition of pure for Identity -}
ReaderT \r ->
  Identity r >>= \a -> 
    case (if hasPermission "admin" a
          then map Just newUser
          else pure Nothing) of ReaderT f -> f r
{- definition of >>= for Identity -}
ReaderT \r ->
  (\a -> 
    case (if hasPermission "admin" a
          then map Just newUser
          else pure Nothing) of ReaderT f -> f r) r
{- function application -}
ReaderT \r ->
  case (if hasPermission "admin" r
        then map Just newUser
        else pure Nothing) of ReaderT f -> f r

如你看到的, createUser 显然只是一个包裹的功能 ReaderT 通过表达式线程化值(“环境”)。 runReader 展开函数并使用提供的参数调用它:

runReader :: forall r a. Reader r a -> r -> a
runReader (ReaderT f) r = f r

2
2018-02-24 10:42